From 24173412e86cb6087c4c34260248738746f6e573 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 3 Apr 2026 09:54:43 +0200 Subject: [PATCH] Automotive, CarSession --- app/build.gradle.kts | 7 +- automotive/build.gradle.kts | 1 - .../res/drawable/ic_launcher_foreground.xml | 8 +- .../main/res/mipmap-anydpi/ic_launcher.xml | 6 - .../res/mipmap-anydpi/ic_launcher_round.xml | 6 - .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 2192 -> 1668 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 3566 -> 3372 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 1634 -> 1078 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 2178 -> 2164 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 2828 -> 2146 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 4788 -> 4682 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 4258 -> 3318 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 7664 -> 7268 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 5422 -> 4256 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 10894 -> 10040 bytes common/car/build.gradle.kts | 1 - .../kouros/navigation/car/RouteModelTest.kt | 82 +++++++++++--- .../com/kouros/navigation/car/CarSession.kt | 10 ++ .../kouros/navigation/car/ClusterSession.kt | 103 ++++++++++++++---- .../navigation/car/NavigationCarAppService.kt | 4 +- .../navigation/car/NavigationSession.kt | 72 ++++++++---- .../kouros/navigation/car/SurfaceRenderer.kt | 40 ++++--- .../com/kouros/navigation/car/map/MapView.kt | 5 +- .../navigation/car/navigation/Simulation.kt | 2 +- .../car/screen/NavigationListener.kt | 1 + .../navigation/car/screen/NavigationScreen.kt | 58 +++++----- .../navigation/car/screen/PlaceListScreen.kt | 8 +- .../car/screen/RequestPermissionScreen.kt | 9 +- .../car/screen/RoutePreviewScreen.kt | 17 ++- .../navigation/car/screen/ScreenUtils.kt | 15 ++- .../data/datastore/DataStoreManager.kt | 73 ++++++------- .../data/tomtom/TomTomRepository.kt | 2 +- .../navigation/data/tomtom/TomTomRoute.kt | 9 +- .../com/kouros/navigation/model/RouteModel.kt | 93 +++++++++------- .../navigation/model/SettingsViewModel.kt | 3 - .../navigation/utils/NavigationUtils.kt | 3 +- .../main/res/drawable/electric_car_24px.xml | 10 ++ .../src/main/res/drawable/ic_recenter_24.xml | 25 +++++ 38 files changed, 437 insertions(+), 236 deletions(-) delete mode 100644 automotive/src/main/res/mipmap-anydpi/ic_launcher.xml delete mode 100644 automotive/src/main/res/mipmap-anydpi/ic_launcher_round.xml create mode 100644 common/car/src/main/java/com/kouros/navigation/car/CarSession.kt create mode 100644 common/data/src/main/res/drawable/electric_car_24px.xml create mode 100644 common/data/src/main/res/drawable/ic_recenter_24.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3af789b..e5b5403 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { applicationId = "com.kouros.navigation" minSdk = 33 targetSdk = 36 - versionCode = 90 - versionName = "0.2.3.90" + versionCode = 91 + versionName = "0.2.3.91" base.archivesName = "navi-$versionName" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -101,9 +101,10 @@ dependencies { implementation(libs.maplibre.compose) implementation(libs.accompanist.permissions) - implementation(project(":common:car")) implementation(project(":common:data")) + implementation(libs.androidx.car.app) + implementation(libs.androidx.app.projected) implementation(libs.play.services.location) implementation(libs.androidx.compose.runtime) implementation(libs.androidx.navigation.compose) diff --git a/automotive/build.gradle.kts b/automotive/build.gradle.kts index 342e444..5d00284 100644 --- a/automotive/build.gradle.kts +++ b/automotive/build.gradle.kts @@ -37,7 +37,6 @@ android { dependencies { implementation(libs.androidx.app.automotive) - implementation(libs.androidx.car.app) implementation(libs.androidx.material3) implementation(libs.androidx.runtime.livedata) implementation(project(":common:car")) diff --git a/automotive/src/main/res/drawable/ic_launcher_foreground.xml b/automotive/src/main/res/drawable/ic_launcher_foreground.xml index 1b2a683..764aeca 100644 --- a/automotive/src/main/res/drawable/ic_launcher_foreground.xml +++ b/automotive/src/main/res/drawable/ic_launcher_foreground.xml @@ -4,10 +4,10 @@ android:viewportWidth="960" android:viewportHeight="960" android:tint="#1A7416"> - + diff --git a/automotive/src/main/res/mipmap-anydpi/ic_launcher.xml b/automotive/src/main/res/mipmap-anydpi/ic_launcher.xml deleted file mode 100644 index 6f3b755..0000000 --- a/automotive/src/main/res/mipmap-anydpi/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/automotive/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/automotive/src/main/res/mipmap-anydpi/ic_launcher_round.xml deleted file mode 100644 index 6f3b755..0000000 --- a/automotive/src/main/res/mipmap-anydpi/ic_launcher_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/automotive/src/main/res/mipmap-hdpi/ic_launcher.webp b/automotive/src/main/res/mipmap-hdpi/ic_launcher.webp index e0d523c38c3b985b48a64bcaca0d6ece3110362d..3a95d863949306b82b128469b5d6b86cde28ffc6 100644 GIT binary patch literal 1668 zcmV-~27CEZNk&F|1^@t8MM6+kP&iC*1^@srN5ByfHHU(>Z5W3??HvjcF#*<)f}XIo z8Opf|xY_?NPJX)}rB^O<`2tR5ox4k!4tG5n2ETv)|36m#>O5Dn3Iot(imRv@iWP1H zR-koleFJYZ0AoPwGh~5mf$b3Kq0eFrLE$)(ZKXP?6OY3h?!E8f&hERrYXD9o*_M?_ zop`X1Z@Fopcz3_7`b($Pwk^x=xVu!6Z(xx_B-2@eN^(i=lK!6=Yye%yZboj~IHG3z z9{U%hM*IIxO3^vzoO8}OkaNyCXZnHv|GPWCE#qB9(#Q*SF!NWZ87wFY_~TJjfMf93 zue~C0RDeWM!AUeSDH=FVfujHphrPivxEl&s$7FSOC0P7PJS{*Ipg>_GNlE{eyy6ZI zw8kI+03!c)WZSlFV@dxVxQ(Pp&N1U`zX9jo4Fl1jP`IQSVc57x$SJEguR~-lOQcOv ztS^V!m7`~mY5%UnU-Gb`DCL((&mt*KferRK1-5g$w4X3EM1k$%93uO0$=MnE4SRcHB zrA5btmq)cWuk{~6tF|c>Ri#o}<%Xm=_L-6P|8I-!tM{1W3+c{5s&ga7S=dbJ_Cbz+ z=kf5Umukx1vatJJ3ww1hhR_fIghuWy?ByTd*!!O8B}x_wk0moXt;UyqVEJg$nZ~Qq zgjzOR)AroEDcYd`U=c+~RnVK0(_O;pB|d#+qB`i&h+xTWjP~w#8@1hz0xyOF2oQ;I zT`*R!F>vsG)>krg^>JM^djUcLEO5d_gSOoxL)j1nfiMJLZnoXSv--E3tu9(&2mzBB z%5Jm6m|gGA^}-}@c0n(Py!Zhx9A&bQKnQ@j%I~_DDfP*6flOsLomK7ad+)yYsJ(L+ z0QaRKOGfPi8Om<8O$D=CHmfEowAFUYZqcZX*n963p@2jtHk?@n``?r0xJ>2t{a1xH zoF$1#Ace_2?=hh#hd>yB^IE2ob$fy}ds3CNv25P|UKv#MmP9la2N&>NL{+Y8KVIknLeCjqs=@Qb>00MJ=N`T53yVkZyK zhSqQlVZh4OtK@x#G63nq?7Wvsty%*RLr81*fYzEl0#gBbov{2fb=hb{fe3DW@%tYh z0anf4W_pcXnfugc^%{U^LaFb^Z;yaee(OT;9e{9zj%NsS7l6M*()#d-`uU*Gr1k3m zec)Q56BdtJbxE&ZT?4`ZSUPHp&9?Odb6d{x`RSAe6~Y0!z4pv%Y2BX3??RcE9aRlh z1r11G!od2e-6aC_RrOj-S^IO z{3MXX#Eqb5!TGKDdw3qmb;2KW^T|&K2t}wesMq~6|K_t*dG$Wvq$_j6;JkDq#|bV# z@A#@c+UF&I;A?F(dI8)V$Dc4dzr7!KvD7;_(NTgKH9qohVz3uWleZt5o+lUe@+@AJ z%*4V``Cz`?^X|?PjCpqR-BsVr+wT|jQl7VC5czWFy{d2M`u)Yxn|=KI-+p^^F~A#L z=Yen|PP#4GXx1_1_kUDV)P|%vjTyiHwVI~4B#lW7`TakZ);%LB?5Czb4za<5StR{* zDI;<8t^Z}Ts7f>dGJuF7jl1f8(m27~2}(FT4P6 zU(9@NJNm-f`u0-L&Mipr9(ZaifG~grfFytv^S)OAgnTY9+dC2q1ghT~xAz~v$Dbhc O9u`;8sNOk*bEpupBt{JY literal 2192 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 CAClq# literal 3566 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 literal 1634 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 YK5nc$`(vOq0{{hp{-fO9q=EO9Aj?QSvH$=8 literal 2828 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 Mdf9A>q6+5$0I91>&j0`b literal 4788 zcmV;l5=-q;Nk&Gj5&!^KMM6+kP&iDV5&!@%U%(d-HCf&O(3~XaU1Hm|ZQD++wv(yW zxJGU5G^@uo+#cJuJ=4=M|Be09{d@2C-mmA&`9-G}+t%b{Cu{F^PwZ6eq_dx@%D?y<4#{IonQZ!%A)y-Zr^;U14vIwryJ-+Zx3A#ktL_&EBtXv1T@QQn3~%pB1Yn znx(F7d)h{NPkj$2mTbvvOR+_9Hx4rmGxJQK`~$Xa+e(}L>-`nDM3A;X+!IoXdvLgy zxTmfUv|s@s*hmwnc2jF@+xDJq+qP}nw#~1#ZQEAxAWD){x0!_?d?!R6oDT8_bR&ma z0|Kr6cM}Y2jY%O$VPT5ejj)0Z!fHL`kzTD1t8IQ>`f_tbAlruWUiLbaU%oC8)_d7? ztF^mDyL-IZF!}Z&Q&YXd@6oFjl+x<^S=3vxHr|$<=xy2EwJdwTl6Lilm*C7`Yv6Yv(9mX7jnLH zKlIR3>eU|eybU|4mSvyjll?97q{1Q88F?HD;W3HFrz9SiJmCog0wjb_`zU}j1;&^| zm~zYf-NBYRXCK*jetvi5t)r%zmfl~uJ6@V9t&#d7Q;)|20(26>XBe+SbO8a(gb@0a zSslIEGyLX4p{8{0HfmPghA$QAPIkLQSrV` zTFcVYODJ=tbC_mE1Sbq&f+*mGrZmPW$|#}CmEM+~L1|OrJ`B^-b(tVh!-b!x&KhF+ zq79GD1e5M}FH4g(^tCP%B&a@2sHag{%dxS=tI2vBc8{Hq391t^YpyC@P1i+GsuyaE z8iwAJqiB2C??M<4MpO_z5LxnjgE>bTEwh$wxkOh!IWUF)5nDDHENGli-pBneW+2Hf zI;xxSUH}BBT9B%*Zh{ZxX_YAT=m(fGVv zL@?d;Hw;mXJT9FgZz$T2?x%X}(Y*{EowMvP^9aO)%>1|v#gosf1k`6U znPpdjb~O=ma5b%Jf<#F*Ul%KoAe;uyTDH`1j0%sUYHnQ2=|t>eV0gxn2uZ~XWci)7 zKK*!AL)AF3R3TP#^fNoALe<=|$ICIezvF~)(d%&k7J}ME6r&E8vUrC;{irE_cd#om5$GT+*e!z9oTKdc9%J8|j9`VE z^E>c>k!eh3J>D(fZQ9Vzc!@4Qa7CH#3!I1b|~ z5+x~@q##ebd|g!Y+*7hg8;Vqu9iecWCNp%1PZ{mt#0S+a2qH-8+yFPmgve&iowE6+ zHI>Z~LE9@e?4FasH5QXqwgDAB$9LPv(uAi$bM_~R&(@)a zK^lZ4p63)@N+@_l_eHsfo38*6QYhel_UXPNx9o2?9AvTKs*1)#K*4(AcreG?J^}sw z3vxbOIz8MZeS2-7o=gfs0b4B9U!9yRDTDjRZakxP_AKuKkK9+UuBM7j)<&BGjnLIHqqg@(Oj#Z?U$ z2P=NBhKt}+&ba=-#~%n{M}!)BIaq|6tmE}30Ej3QzMcSx7Pa=DPlXsvh0m}`K9s`} zOmQ$McDKkd@c#!uHVyMZTGf5y+eA zf_eNyvA6?D^ApS*H_VQ6@K(icx4j6Gg5Jy^Tq&tG+--WS~WH-{x+jmm-ZaA^2Mf2=CZw8LI{XFvJaaA z)z|}Zc;L>lxjuxE$4jC5d!$beU9}t%?Dicqm#uvaqJSL2CRJL;u#_sR!)5?@VW+5j z`=h@&_u>G6ko$h$iJWRS#4yz_IZ7H7$dPt7#Vd&jFBkbOoif4gj+AjMn$sa8*bMhwqfq zHXN#2>d%ZoaRS99a44lL-{HRajXW-69_tcp_D|NYLwx~2Jk@e{M#8iNT6j$!ffw?K zGzAV9?HO8jPGybx^YbNRN%x3y-kVp7{`A-_hwSZy7k1jb31>^^5@wLGi_c=E={Q9Sq(qsv;)qItfQ2K^W`UZ+$ZG_{{>>-Og7)1mo%+_ z^SYE-120uD3S4Qj;W(W0We1~S) z%yeN?n$Q4Bc>K|L#CfRsJB4ZQ-#cOp{QhB3->*kpfF`Ht`5qLM9+m6t`$B~{+QAnzZgY3yx_|M5Mr*EPNLVkiI*2nIk^`pM#T=Oo7f5#3KJT+hJN zbWHocn~XQ6$qki_PlYK%(>Hd=7>nU2Yzn4uJ=H$E=p3>lm(BKrLk5c!r8e%kobPXa zGw9*g$R2^%*H^SY_$_B4M*&bglY6w!(l|?E1|r0(ncx3*J>KY)2XPTUkvFl$?s#QV`A7qQG-lSaC5Qy_b0LYxr#0?%i^=orO%pHV z7xG2Qr%7gcd@Tk*v$WIIH~P76mA*8Z@SjZX*+sj`Ol8KQ<; zq!})&^}hhyH5-D5i`gsgls1_96d@+C#z!xkShQcE(V5+|n}5 z@K~&;P3`f&=i|nkM5F}e_16JFBLKQ2nyvn_FUmAL|JSe%HKUD>%lM-W!t;L(ja^aZ zyk7qcfbQqI&g*?DNdZ7~b{8Z7&;)=^veouWS@($<5j1B!lKP@nGeb7fQ9@a2T81b2 zeEt*9pl3X4escHvnd5f%4FEdt=89MHsf1;Y=l8O6b#C$W{B$xmJ|<;~a2?}Os5B5+ z@q2^G;)0CDlN_=qIA3>Yu;84cD?|BA78h7^m3p91#-m?^w@BPfj>UX&dA@T1G&Y1Q zk#QEs*OKYn{WJog-Rj<5FHSxRSD7ann&wPUHP^&TvyzVJ>Q%gdzwBdM9>dovK$#v; zFwf- z2!NcK-=Y=(E&dSc<@v^Bqi~5`SrLT@47z4Q&`f7|&u5d2!li3|b-wuzr#V~efg%-1 zS^&gnclk)n`0l3}0NPIf@xQn2;qY?e*52Y!l!Z1v-!Dk{#rYbCI_4#b`_>lAC&yR+ zx``IOjfGgwf_Ob(<~OK2P3H?fevGo)AKsI<&SsmUBnpkwG;cuQ-mF1$&8MF#Z{5wd z`{VlyJbsSp{4`g40Lo1uud!x&elMHJEgAvP>JRfd{){x=A779SorSGbO_8~*iN;{i z@&AUJ>2wBzrpcq?cbl#n?{Io?-p8NOe;DzVhaC&wjU!0X^6F z_IjP`djFkUYp!iY8a@|^5*m%h5PrS}4I1clK7Qv5H)u2(7B5MOFAZ+3x#kcrpAyik z^AQKyeCv~l)DNV6ee1pXpXOsz0y@p%@;_t^?_O@S+wMM2Fr_mS^(NcxyGA-rnh`J3 z#pDVT8e{D7lo}r?hu!7>x`~^nIf{BZaWmbPXdKSG;C1i&F#xnXjel>gzn(Lu-(1um zV-`1GHPgBKX&Mm#?WY8Eni9}sk$*lXtp4rwI`gN;XI!?2FWhH~|D5{fY}aPI!mXKX zOyGuF6Pe+TTOV5HMjLnSG_YoSOr!r?w};QopPrvt!<);s+c?0-ZZhIfryZj{#+AU2 z13(M_xre>ljPKCwZvEZl4^sjwTv)d;A~p zoHyU(4*+Q2-`yT`wZndMKY=3kV~N^H_S5=wfTG!6J}S=UKh&2_Gh%=J0iaDc-Sju& zX}&a+|JlC#hSv8pi59x54NL%tKBM)g^f>d=TWN3K^-*to=j6Nk&HQ3;+OEMM6+kP&iEC3;+NxkH8}kHHX@^Z5ZqS!|l{LA{4f5+hp&g zJ%ci;+AkM*7wvD+fT4z}wf(E*<_2>&!yIPLFo&6$nVEAv$@%^EZ}0s-DagqAms`hm z;gq>`SrZ1=H8PPT%Up%7ab2@zL3Q=70uE)Wt2rR&0?LevXQs?JYgCgmb1z|PQKi+V zN0qtbl2HNGsJe+8S#TtoN9I(ujw4BGB>yczjRFQ(2K7B#&I(Ba0H$s8|96<#mSkqO z!!a|p-4AvbkR(6*KO@kA+enJ!I%XW`ofpWhwttnBU$VHnI}|`h=1w{#;4YE7TkP)c z4yzgN_x|A=O0m`dJ@-ds{{r?*iZ(z*0Tyos6RxMYO9!;F5u0s{WEzuAhDGk$1QcQs zIq_ON6)fpm6Vm{e4FybyMMSp(V$C7?AJ>s=o7SZM;<*t#+_lfMZQHhf9M`t(B&T)j zp$RR3p+gq$KK>(TFgt)_+uE^b{kCn}_8(^mcB)j?2%IXJOIFIi4@=wWe}A})+(uHA zbj>ouZ9c#jQF9J3<^(RxIbOz+z5dQp&M}R`_Gc*c*E&mtmN7Ql8Glx=un2KEW;nCU z+&|uZwZ242_{Oxvw>cu666u*pbs}|%)RDOdefsY6QOmwbl9^Wa5b2gk;9Qh|LC!DE zaMsJdNf5y_$+;*If}9X#lweLH&^Rn_bLdp;4f{ucN0KBHH zA&!yav!kS`*1U6RXxthS7%4hG%L`Nh_&8ByNt=6Wbx$qtuGKwU-(Db?Fv%}aZ;z^2 zqmbsj#o}(3uL`HcwQ)6|!*=FTQTwY?m#8Jaq zdZo&&WJZ;RCvf>WETHnN=#5&d=gc}QPvFYL5cj-Z(DIasU7&I067iqLzDib0i-5Vq za3!-{8iXp%3gcSbLlMmpF0~1iBuS786lno)CquUE%de^8S}IUev)#!Q92T?=;lje2 zxrb#<@5Yr4mJTQN?+se7Y=5e1jmCR{H7UIspGCFV2ysPSkkmi^&8Lj)YI0WT5s1cH zH^<2F6IZ42*7BW5uP(CE_!zb$-XfYgTsj0ApB1Yb-&G2b;HMHrP65rBWV}LC!J0}G zdFCF+v3wKGrgxGj5y#jDS|QEcBjn}f-S8iHvLp&;QIoSt0Wyy9Vq25D5%SaYY@+a! z@32Qpj}ZD_Ez=)b-;+=xV!R*8=LN7 zOi?XZdIHk4US|>{gNe_XnKauWPg+C^Y^gFUbCCbDJDZ5#c&m5vU=wktd9Vd}TD!m) z#gb-slMVpn^xc}E8N{p1hM*<>N91I%#*jQUu^n`sQ4o)(a5vJ$P`i@rkP&O{5@E;l z5y+#N1X07YunHt@Sf1P_Bj2fApwwI)W~|47IX?-ioF&3J&yfRyn)+vE9~kguyP$>@ zRc6fqmFZ9PHu~kZk|xnRf|`AhfnjB494Y+P4|!kUvY$%)nd8C18HAg6FE zz}C-8Y`qxJ?CxTLL`kra&Odv&Zv3)K#*8&_5&%|?(1ii3!{b6VBgh);N#e)@5+z>! zJrU##gh%;nN_~;n0ZxIn5nai4W9D!iqbZJ>ZxR?QkJE80r%nxn}|_Xnw&@?oX=9a(siBm%~$ zY#0zmud`(#4VTJwWWB-wUva5yJ>VZfb)<9&%1~zw1T~$2^%(I3S*{W>b2u)U93@27 zlQFAN#n^Zfmv&$b`4eM9>vwTR4N$OUsD^V&inS8ewSY5e+T4?>1_uBo`W>~kPlpgm zr$}l808AaM?!lWtLCp}=whpJImUW26z=K|FT%5fGhy(JTL|$wgN0YOJ7`uswSZI!> zR(K;mrSW77WB#FbnbhQ~37WJ3Fv3?{vigPiGzgHr-SqBc^$R}PrLv-}0Q=2w(f}}; zEV|NHAypZZMgb%JW;JU8ww^MVGNS0n>3M1#L7;7*0fX2bcRg?CDKgjw+Vkv=ySk(S zK*qQ50}HxRUAdn)@OJLe*|k^i`r2!dJ^KU;kMw)SaT>(NFlO4t~&v zl%Oct?li1bD%tPCV&SVS+zNPEg{iue*yb?UH(ZRZhe)0zT!*zMTvuVdN&R7B{=d%J zlekWjB?0!9O|VZ04P>Kk8GzI+mxC&O6)Q(*=iI33))AEKJ>&0oEIsKWq(ejMmU9Ig zD$fcsmnt(}ppQ7`ujZ_ghG!Cxs)}w3}XmNLE3As7zM0lfg2q0}^P3tg2UToP8>AEzAh|b7G77lZlvL}t6VuRf-xAU~4 z1WUviq@1(e5hewhKE$?5-nKnk?r|XkM5Nw{E-I2s=q&mVY?Ob3jK;Fvo<6)X}* zORO7tbi5{mw#rb6bt6X$Hi>5^^x{}L95ZA|t9u|>?IQo|I|e`1>*sKW*B!Z$L2}*E z#M46p-$Bp&40K zdDir5-E81!xi>J34%EAK#a+(GNJuDIhi}h_@1>XPu6O)$Kq`<)S2MOt39Oxj>FS9&H0BOT|dG8`SvH{ z_3c(N1KZ&MFf+|70f1_JG}ZK?N)i`DMow{#j-~D#K)v=71vxi})IEybwj)tVqQc47 zJQb+Zp7zw4_lPM7P6s>yMf@)wi$r>J6eI#u46i$t+wZtv0#8x_KbQglPKF0+088cm zxM-FNIK6bj6W%x9p!jrKJ^fTi0{|O9ob>%N19>pRss z`V?AJ?9WH5l6>A4<4+3MB}JX!`*8t;%8b_4E~V~!bSf~(IrJQZzfDttcIE!3=xo~x z|F2dqF$#ipMgV=>-`5WiiSwsWxxXH_to1kDi|zm5Ox+rh4sv+9A!Kg3-@k{Zqt&C( z{I{s~Hx&lkNt{2$Z6AV+*3&ct03LuGz!djO03d7vaT7?U{QUcK?O!ALkB^DG+jBzZ zCiwp^=ixni)&x@50K&(AywO}@R0IRCls5pzh~SR>Cmi+vivmPVAdbMlhaN5Ue4}g% zX4C~B1<(MP0UQy*7wz{A#Qy)nvUg2H@cINk@Uju=0^kA&z0?5OAOOQzPgy&4YR~~S zfb8%Y>(E#5CY=UAa2Pzc7|=>Rxe?612{R674i5UunmmV56Q-72gnlK-TJVF zv6c{*4QS=0jZ4T5+$FP?S^&-lbS&+2F76hy0(aNk*@D%nh}hk=PRBU4fibK$fZcNl z-JKN>IXxW_jfJ>t?A2IGF93(A^Kl6|bNfgV05)x;e0@JNmYMAuBn>m1mU;KKKiHdW z+qTuV(XaPcVllI1W{bffrh9HuW^Ui)*0!x`i~IkV;4THYMHJx@k$MM2)We_@cdNkJ z-@hjWH56H>>|B<3EIp>@+0_f;+rdMgSd#^87Y* zgb^|t5Q70+P$6k0AOH_ci3pUS4FO0%#G0T(1VRuoC3t`feoofOIU$IkJP|A6FL2v4 za5`4f2ssg3o*dVaY@1f+pUO+Zwr$%yKdd52cGCW>U;+eS;*oRCd^imN08QrZ?!Muk zSmX#jg31YGcxWB{!QlZXsBzoIky5U%x7`Q$eQNf75W{`Hdo|Lx7Dg35_uUocS4v(G zWA`0!bt3njaaTlU9B{w^S9^}8RyBqNpDS4LK1a(6o`1Z1{{C+wi3J9n(GSsWKZ56e z1n>O_e)XE0n|N3$jK)_X}1^ z=r?laON;ijm+4H9k)HcpoeyJGZ(iR-pJp8J(mI_l;s1gl-VDmlQtle?bS7&hFcK6! zF(W;OK(kgnPiJ^g*;xn>`1mmXud8!`6@PrbLcFl60aLsXG#3|E0{7GGKK31Qx zsccmMq&E`5l=(AVK7XF~Vfd&U38gMg3iX7m)p7c`T1}xA3&j}8m%x0cI4QQf%FZc0 z+r#S(KSaOg>Wt8ULGe@0&Fb{U43{HnH=|iXD_v*ZYAS!0wz`%`XiKpKF?H7J+Fy)X zG-}D^OV?UBkj#gnx-vJfWAXLN{@+r(B;yDGZEiG;OX=z}$W?vDK;v@8SWt2m-yG8E zfB$kA?;yqzZUElY?%Ld`wp(6Srw=49Z&vSV4RMmK=+DpNZCSrBCNLV>U57ITTEtd~ zFfUzuElcVg$^D#0lgclo4oj13G@5Z7v<&m2x6z{fH6qmt{{A|VL%WRWzzAqu5>g>_HtsKusJpsxT{sdt&nNS%SHwbjy(T>M}wP5FBZh$TQ7&I4ibVQgwIaHMYH$a27i)TS8E$ z#3^?s&dQ;x@0Oe*T#Sm@1*VG|wQ(fq)1|k#!R%~Xw31J|%du`k_q;8n6>>zIuZEPY z|C7+!>JlQGIa!U@naa;nh>#~C3$uQzea5sWm;5cl=naA|R%3UNgJ*$Cb?hz9__y8i zwTL`|7=5L_AjP+an!6`Z8Zg#-*g!so-k89e!Z;WkC5n^79A3`AI47`%7=hDBeP@A7 zOhRj7oV=`|J|i;7ReY<5zZ>6~u;+lu`BuFNU{8;Ug@)k80a@k5KhQ6NQ_N(MKP zj0k~Dt7r~p6A{$3<1pIJ8ml9)vgQ-cd6sk}usH?e|0Hn^EwK?8?OpjEay_Xc@uC5BKCCGhRvC~8o2D5))|K_h7e)^y|ed+6~! zMDK9}zIO}t6s0;iiiW#`E&}b&8yx^+V6-LZL+y=>Lvb~lE`$oU7>7(hfxsJ$4={bD zJ%LY&aRU!~o*(P!#HT^^8rL7utGhoK{9u1sEZ?kjRul%1wrdQ zZBZ&c?!u(6B$XNulsv~dXiGv;_&F>e0`K9{^EnmPdmwJ7W6;KeOpbK~C4#aMRw};) zaXuY_rh%mKOITUc5yvERIzkVvAr5^Tw4c*mY}QFDP#le-HV)`6oclhC^W|Rcam#E) zdIl-EUjV=eRspMfPhR^Ad$=_sY$}EJ$X+C_4*u?ViH)3w_2Nj!hqN2iGqo zaV|)*9S1FY^-%SjOWgr&e=(?j^RJZs=2xL{Mf*k=d23*L{KD~qRO5;?lm)HgC{c~O zT(pAY(=Y^KVGfPE@?10_9haU4q13o}If0iCiBPnsNXMmf*+{;S$q`b=HBOPS6lL6= z%+E_QubJrOBwoH*l6g%_0>>hm_hrjxu_S^ASCDyMv6gld1oL9;jyX+wY6<4WhCsI$ zfy-Qw1KGAz=jBu>)^q4l7v~mQY&{%M)K&Hl)>Ud@PvW(P%;6rT09P!eTVX%=ivHcaJBrfOEgNF%mbaJm)2kmN zOFkQ8^-}CNMv98NW}!G>-{H-uGH0NCz~vi`azL(p(6!sVk^CJB4aYicIN% zLvj}%QJuNbu(-PS90{C-UHN)I#aRg$;P7SHG;z%sC`^y3PYAB=J@M_R)&;=YpE^U_ zVJkOF7h$m0qV#bm4qC9Ct{Qemvr!_3i%iCLB{d|Ob5X4eh!W!teC2hIV@m6n;0b4o z?bX16iZvO0WA@^iQH?6h$6XydruHSqm7r3eCJCGg>}^toCFTvWRRZWv-{zK=k~T!# z)7~!vt?);&d@mS$64hgbZr$xP9K81?V8~TG+#gfP+Sn2w7E<%!^N#aA?k_p>1;gjz z$X zp~3Fv&aFA;YGsyubXI}SSx=}8Acy?xqsZE*t*W*zVVzT~(oSR(;~E&)B@!SbDz98_}N z30l*Aj)QT~l0cydsj++43Xh>@h#!U6rhng!t|A^3+e zvb*HEVnNgAu0BDp0U?YUd)O5&DfjtutLLCAY(S2ES%HUToPO9!-tV%uLG=ujXWv?n z!5D*?=u^n(6w$F9G}x2m+SivUAF&3+XIeN!CLwn|6Nj_w<%C>rl+?S_9FB4ntt_

mq>aI6Zpnah)>LP=Q`UqR>eW4L@?wbvaU#3KHjR?br22p%_HyeYDinnf`C7STZ4(7~gG zESfr9m+;T4J|j7cZxgb*$W$K|QkA)R$%_AcJ;f-F#)w?qY+Iy%aIIb~qr}#BFd-yc zO6AY&*_O*Wr4;3k0$5Hyp4NIa9OuHkPf*+ZVluri@AbX3nD+^e7V2xv?M~!45bgjd zc9D@lUW?_$T)s37a4Xqce!UD_jAqNIRc=2*gSzKfd%0Ny zya~0;cr<76>#nt0D4_;GbrnEXdakigH>36D$2k}F&_}w*J?fofIiKG#`}*bCM*P3G zYcvtyB|r$v`~QC}-a+iG{KvQZ8DHOjFLC=??Q?7{9YyNy-otX_v&7b7Eji}E_@BYq zlas)?&v@&3<^Qvni!IzrN0EP*ynU_rw+}y7@aOxJ7cL{n8#|P#5f~o-w+tHYLBC)y zFWc(vp|?~&CbQ*hE>rwxGu2N?7QTC%Ojk{ko@YtKX}ma!D+O_!=h&`Otj8{vW0$qq z&Qs3qib1?6$`u#Mc-`j7M8{j?{>@-IQ@r|$)uXl69&(4v%00-h{^*UHb4mj^5P;tt?0eJJp40gC7uKWuxtR94T)P& zcL-k-G+#Bq|99VyE)25Bcya&^sK5cC4JF6_TX73tUF|)wA-n&0yWNuH02rWd1)%AH z`Fx?)p6EpSYXR`Or_H1X6F;?p)B*a;sMZcYyzZCwQ)HW|lz}9P8lWU?jFc$T0Qo&i E^5uygT>t<8 diff --git a/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 60383ba3b0d27b2166fae9643139aa975e321a85..8677fcd316e9d4c5372e47b868c667d664752e2d 100644 GIT binary patch literal 7268 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 literal 7664 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 diff --git a/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index 3464553e3fc7e4289285988ec89f719d6cedaeb1..f416d789e0ee400d98baab16325443f0ff4b8229 100644 GIT binary patch literal 4256 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 Cgi9d+ literal 5422 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 YM9c*eanAR;0y*bI%;l0Rkge|`0CwR=v;Y7A diff --git a/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index dafc41734f39e98fa567c31178726c6addfaed7d..2980d7799f2719a7998d51cc3549e1d2e6d5ca48 100644 GIT binary patch literal 10040 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 OHxJ18kipj@$eIj>GiOKu literal 10894 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 kxm+CH=0OsKr(f|Me diff --git a/common/car/build.gradle.kts b/common/car/build.gradle.kts index 29b90ca..c2f0950 100644 --- a/common/car/build.gradle.kts +++ b/common/car/build.gradle.kts @@ -42,7 +42,6 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.ui) implementation(libs.maplibre.compose) - implementation(libs.androidx.app.projected) implementation(project(":common:data")) implementation(libs.androidx.runtime.livedata) implementation(libs.androidx.compose.foundation) diff --git a/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt b/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt index ee0092a..228687e 100644 --- a/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt +++ b/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt @@ -2,11 +2,11 @@ package com.kouros.navigation.car import android.location.Location import android.location.LocationManager -import androidx.car.app.navigation.model.Maneuver import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import com.kouros.navigation.data.Constants.homeHohenwaldeck import com.kouros.navigation.data.RouteEngine +import com.kouros.navigation.data.route.ManeuverType import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.model.RouteModel @@ -32,6 +32,37 @@ class RouteModelTest { val routeModel = RouteModel() val location = Location(LocationManager.GPS_PROVIDER) + val distance = listOf( + 1025.5, + 989.8, + 963.5, + 923.7, + 915.8, + 914.6, + 871.0, + 822.7, + 769.7, + 713.8, + 644.8, + 577.6, + 501.7, + 489.7, + 452.5, + 437.4, + 398.0, + 390.1, + 341.3, + 266.6, + 219.5, + 140.7, + 77.4, + 55.1, + 40.0, + 30.0, + 19.0, + 4.0 + ) + @Before fun setup() { val appContext = InstrumentationRegistry.getInstrumentation().targetContext @@ -49,8 +80,8 @@ class RouteModelTest { @Test fun checkRoute() { assertEquals(true, routeModel.isNavigating()) - assertEquals(routeModel.curRoute.summary.distance, 11116.0, 10.0) - assertEquals(routeModel.curRoute.summary.duration, 1581.0, 10.0) + assertEquals(routeModel.curRoute.summary.distance, 11108.0, 10.0) + assertEquals(routeModel.curRoute.summary.duration, 1094.0, 10.0) } @Test @@ -59,11 +90,11 @@ class RouteModelTest { location.longitude = 11.579034 routeModel.updateLocation(location, NavigationViewModel(TomTomRepository())) val stepData = routeModel.currentStep() - assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT) + assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_RIGHT.value) assertEquals(stepData.instruction, "Silcherstraße") assertEquals(stepData.leftStepDistance, 20.0, 5.0) val nextStepData = routeModel.nextStep() - assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT) + assertEquals(nextStepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_RIGHT.value) assertEquals(nextStepData.instruction, "Schmalkaldener Straße") } @@ -74,11 +105,11 @@ class RouteModelTest { location.longitude = 11.576652 routeModel.updateLocation(location, NavigationViewModel(TomTomRepository())) val stepData = routeModel.currentStep() - assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT) + assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_RIGHT.value) assertEquals(stepData.instruction, "Schmalkaldener Straße") assertEquals(stepData.leftStepDistance, 0.0, 1.0) val nextStepData = routeModel.nextStep() - assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT) + assertEquals(nextStepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_RIGHT.value) assertEquals(nextStepData.instruction, "Ingolstädter Straße") } @@ -89,13 +120,13 @@ class RouteModelTest { routeModel.updateLocation(location, NavigationViewModel(TomTomRepository())) val stepData = routeModel.currentStep() if (routeModel.navState.nextStep) { - assertEquals(stepData.currentManeuverType, Maneuver.TYPE_STRAIGHT) + assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_STRAIGHT.value) assertEquals(stepData.instruction, "Ingolstädter Straße") val nextStepData = routeModel.nextStep() - assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT) + assertEquals(nextStepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_LEFT.value) assertEquals(nextStepData.instruction, "Schenkendorfstraße") } else { - assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT) + assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_LEFT.value) } assertEquals(stepData.leftStepDistance, 301.0, 1.0) } @@ -107,14 +138,14 @@ class RouteModelTest { location.bearing = 180.0F routeModel.updateLocation(location, NavigationViewModel(TomTomRepository())) val stepData = routeModel.currentStep() - assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT) + assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_LEFT.value) assertEquals(stepData.instruction, "Schenkendorfstraße") assertEquals(stepData.leftStepDistance, 170.0, 10.0) assertEquals(stepData.lane.size, 4) assertEquals(stepData.lane.first().valid, true) assertEquals(stepData.lane.last().valid, false) val nextStepData = routeModel.nextStep() - assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_KEEP_LEFT) + assertEquals(nextStepData.currentManeuverType, ManeuverType.TYPE_KEEP_LEFT.value) assertEquals(nextStepData.instruction, "Schenkendorfstraße") } @@ -124,7 +155,7 @@ class RouteModelTest { location.longitude = homeHohenwaldeck.longitude routeModel.updateLocation(location, NavigationViewModel(TomTomRepository())) val stepData = routeModel.nextStep() - assertEquals(stepData.currentManeuverType, Maneuver.TYPE_DESTINATION_LEFT) + assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_DESTINATION_LEFT.value) } @Test @@ -142,11 +173,11 @@ class RouteModelTest { if (index in 61..61) { routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) val stepData = routeModel.currentStep() - assertEquals(stepData.lane.size, 3) + assertEquals(stepData.lane.size, 2) assertEquals(stepData.lane.first().valid, true) assertEquals(stepData.lane.first().indications.first(), "STRAIGHT") } - if (index in 74..74) { + if (index in 74..75) { routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) val stepData = routeModel.currentStep() assertEquals(stepData.lane.size, 3) @@ -180,9 +211,9 @@ class RouteModelTest { val stepData = routeModel.currentStep() //println("${stepData.instruction} ${System.currentTimeMillis() - start}") if (stepData.lane.isNotEmpty()) { - println(stepData.street) + // println(stepData.street) stepData.lane.forEach { - println("${it.indications} ${it.valid}") + // println("${it.indications} ${it.valid}") } } // val nextData = routeModel.nextStep() @@ -206,4 +237,19 @@ class RouteModelTest { val step = routeModel.currentStep() assertEquals(step.leftStepDistance, 26.0, 1.0) } -} \ No newline at end of file + + @Test + fun leftStepDistance() { + for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) { + val curLocation = location(waypoint[0], waypoint[1]) + if (routeModel.isNavigating()) { + if (index in 16..43) { + routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) + val stepData = routeModel.currentStep() + assertEquals(stepData.leftStepDistance, distance[index-16], 1.0) + } + } + } + } +} + diff --git a/common/car/src/main/java/com/kouros/navigation/car/CarSession.kt b/common/car/src/main/java/com/kouros/navigation/car/CarSession.kt new file mode 100644 index 0000000..ea98230 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/CarSession.kt @@ -0,0 +1,10 @@ +package com.kouros.navigation.car + +import androidx.car.app.Session +import kotlinx.coroutines.flow.Flow + +abstract class CarSession : Session() { + + abstract fun invalidateNavigationScreen() + +} \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/ClusterSession.kt b/common/car/src/main/java/com/kouros/navigation/car/ClusterSession.kt index 6c5a3c3..4336412 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/ClusterSession.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/ClusterSession.kt @@ -17,6 +17,7 @@ package com.kouros.navigation.car import android.content.Intent import android.content.res.Configuration +import android.location.Location import android.util.Log import androidx.car.app.CarContext import androidx.car.app.CarToast @@ -27,6 +28,9 @@ import androidx.car.app.model.CarIcon import androidx.car.app.model.OnClickListener import androidx.car.app.navigation.model.Trip import androidx.core.graphics.drawable.IconCompat +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.lifecycleScope @@ -34,46 +38,74 @@ import com.kouros.data.R import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.screen.NavigationListener import com.kouros.navigation.car.screen.NavigationScreen +import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Place +import com.kouros.navigation.data.datastore.DataStoreManager.PreferencesKeys.CAR_LOCATION +import com.kouros.navigation.data.datastore.dataStore +import com.kouros.navigation.data.tomtom.TomTomRepository +import com.kouros.navigation.model.NavigationViewModel import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch /** Session class for the Navigation sample app. */ -internal class ClusterSession : Session(), NavigationListener { - var mNavigationScreen: NavigationScreen? = null +internal class ClusterSession : CarSession(), NavigationListener { + lateinit var mNavigationScreen: NavigationScreen - var mNavigationCarSurface: SurfaceRenderer? = null + lateinit var surfaceRenderer: SurfaceRenderer - var mSettingsAction: Action? = null - var routeModel = RouteCarModel() + var routeModel = RouteCarModel() lateinit var viewModelStoreOwner: ViewModelStoreOwner + lateinit var navigationViewModel: NavigationViewModel + + lateinit var deviceLocationManager: DeviceLocationManager + + private val lifecycleObserver: LifecycleObserver = object : DefaultLifecycleObserver { + override fun onPause(owner: LifecycleOwner) { + Log.d(Constants.TAG, "NavigationSession paused") + super.onPause(owner) + } + + override fun onResume(owner: LifecycleOwner) { + Log.d(Constants.TAG, "NavigationSession resumed") + super.onResume(owner) + } + + override fun onDestroy(owner: LifecycleOwner) { + if (::deviceLocationManager.isInitialized) { + deviceLocationManager.stopLocationUpdates() + } + carContext + .stopService( + Intent( + carContext, + NavigationNotificationService::class.java + ) + ) + } + } + + init { + lifecycle.addObserver(lifecycleObserver) + } + override fun onCreateScreen(intent: Intent): Screen { Log.i(TAG, "In onCreateScreen()") setupViewModelStore() - mSettingsAction = - Action.Builder() - .setIcon( - CarIcon.Builder( - IconCompat.createWithResource( - carContext, R.drawable.alt_route_48px - ) - ) - .build() - ) - .setOnClickListener( - OnClickListener {}) - .build() - mNavigationCarSurface = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner) - - // mNavigationScreen = -// new NavigationScreen(getCarContext(), mSettingsAction, this, mNavigationCarSurface); + surfaceRenderer = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner, this) + navigationViewModel = NavigationViewModel(TomTomRepository()) + mNavigationScreen = + NavigationScreen(carContext, surfaceRenderer, this, navigationViewModel) val action = intent.action if (CarContext.ACTION_NAVIGATE == action) { + Log.i(TAG, "In onCreateScreen() Navigation intent") CarToast.makeText( carContext, "Navigation intent: " + intent.dataString, @@ -81,8 +113,29 @@ internal class ClusterSession : Session(), NavigationListener { ) .show() } + initializeManagers() + return mNavigationScreen + } - return mNavigationScreen!! + /** + * Initializes managers for rendering, sensors, and location. + */ + private fun initializeManagers() { + deviceLocationManager = DeviceLocationManager( + carContext = carContext, + lifecycleOwner = this, + shouldUseCarLocationFlow = flowOf(false), + onLocationUpdate = ::updateLocation, + onInitialLocation = { location -> + + }) + deviceLocationManager.startLocationUpdates() + } + + + fun updateLocation(location: Location) { + Log.d(TAG, "updateLocation $location") + surfaceRenderer.updateLocation(location, "") } override fun onCarConfigurationChanged(newConfiguration: Configuration) { @@ -125,4 +178,8 @@ internal class ClusterSession : Session(), NavigationListener { } } } + + override fun invalidateNavigationScreen() { + mNavigationScreen.invalidate() + } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt index cc9cce1..1a3a671 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt @@ -14,7 +14,7 @@ import com.kouros.navigation.data.Constants.TAG class NavigationCarAppService : CarAppService() { - val intentActionNavNotificationOpenApp = + val intentActionNavNotificationOpenApp = "com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP" val channelId: String = "NavigationSessionChannel" @@ -26,7 +26,7 @@ class NavigationCarAppService : CarAppService() { @SuppressLint("PrivateResource") override fun createHostValidator(): HostValidator { - return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR + return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR } diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt index f2ed5ce..6c5ebff 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt @@ -1,5 +1,6 @@ package com.kouros.navigation.car +import android.Manifest import android.Manifest.permission import android.content.Intent import android.content.pm.PackageManager @@ -53,6 +54,8 @@ import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.valhalla.ValhallaRepository import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.model.RouteModel +import com.kouros.navigation.model.SettingsViewModel +import com.kouros.navigation.repository.SettingsRepository import com.kouros.navigation.utils.GeoUtils import com.kouros.navigation.utils.GeoUtils.snapLocation import com.kouros.navigation.utils.NavigationUtils.getViewModel @@ -65,6 +68,7 @@ import kotlinx.coroutines.launch import java.time.Duration import java.time.LocalDateTime import java.time.ZoneOffset +import kotlin.collections.listOf import kotlin.math.absoluteValue @@ -74,7 +78,7 @@ import kotlin.math.absoluteValue * car hardware sensors, routing engine selection, and screen navigation. * Implements NavigationScreen.Listener for handling navigation events. */ -class NavigationSession : Session(), NavigationListener, NavigationObserverCallback { +class NavigationSession : CarSession(), NavigationListener, NavigationObserverCallback { // Flag to enable/disable contact access feature val useContacts = false @@ -82,7 +86,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb // Model for managing route state and navigation logic for Android Auto lateinit var routeModel: RouteCarModel - var route = "" + var route = "" // Main navigation screen displayed to the user lateinit var navigationScreen: NavigationScreen @@ -175,24 +179,12 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb var lastTrafficDate: LocalDateTime = LocalDateTime.MIN lateinit var observerManager: NavigationObserverManager - val repository = getSettingsRepository(carContext) + lateinit var repository: SettingsRepository - val settingsViewModel = getSettingsViewModel(carContext) + lateinit var settingsViewModel: SettingsViewModel init { lifecycle.addObserver(lifecycleObserver) - repository.routingEngineFlow.asLiveData().observe(this, Observer { - onRoutingEngineStateUpdated(it) - routingEngine = it - }) - - repository.trafficFlow.asLiveData().observe(this, Observer { - showTraffic = it - }) - repository.distanceModeFlow.asLiveData().observe(this, Observer { - distanceMode = it - }) - } /** @@ -244,10 +236,11 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb /** * Creates the initial screen for the session. - * Sets up ViewModel store, initializes components, checks permissions, + * Sets up ViewModel store, initializes settings, components, checks permissions, * and returns appropriate starting screen. */ override fun onCreateScreen(intent: Intent): Screen { + initializeSettings() setupViewModelStore() initializeViewModels() initializeManagers() @@ -255,6 +248,26 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb return checkPermissionsAndGetScreen() } + /* + * Initializes the settings repository and ViewModel. + */ + private fun initializeSettings() { + repository = getSettingsRepository(carContext) + settingsViewModel = getSettingsViewModel(carContext) + + repository.routingEngineFlow.asLiveData().observe(this, Observer { + onRoutingEngineStateUpdated(it) + routingEngine = it + }) + + repository.trafficFlow.asLiveData().observe(this, Observer { + showTraffic = it + }) + repository.distanceModeFlow.asLiveData().observe(this, Observer { + distanceMode = it + }) + } + /** * Sets up ViewModelStoreOwner and manages its lifecycle. */ @@ -309,7 +322,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb } } }) - surfaceRenderer = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner) + surfaceRenderer = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner, this) carSensorManager = CarSensorManager( carContext = carContext, @@ -335,7 +348,6 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb textToSpeechManager = TextToSpeechManager(carContext) - val repository = getSettingsRepository(carContext) repository.guidanceAudioFlow.asLiveData().observe(this, Observer { guidanceAudio = it }) @@ -381,6 +393,10 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb screenManager.push(navigationScreen) return RequestPermissionScreen( carContext, + listOf( + permission.ACCESS_COARSE_LOCATION, + permission.ACCESS_FINE_LOCATION, + ), permissionCheckCallback = { screenManager.pop() } ) } @@ -488,7 +504,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb * Updates the surface renderer with snapped location and street name. * Checks if maximal route deviation is exceeded and reroutes if needed. */ - private fun snapLocation(location: Location, streetName: String) : Boolean { + private fun snapLocation(location: Location, streetName: String): Boolean { val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations()) val distance = location.distanceTo(snappedLocation) when { @@ -514,7 +530,8 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb */ fun updateNavigationScreen() { if (routeModel.isNavigating() && routeModel.navState.destination.name.isEmpty() - && routeModel.navState.destination.street.isEmpty()) { + && routeModel.navState.destination.street.isEmpty() + ) { return } val travelEstimateTrip = routeModel.travelEstimateTrip(carContext, distanceMode) @@ -728,7 +745,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb coordinates.add(listOf(it.lon, it.lat)) } val speedData = GeoUtils.createPointCollection(coordinates, "radar") - surfaceRenderer.speedCamerasData.value = speedData + surfaceRenderer.speedCameraData.value = speedData } /** @@ -755,7 +772,10 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb val preview = place.route navigationViewModel.previewRoute.value = "" val location = if (place.stopOver && prevDestination.latitude != 0.0) { - listOf(location(place.longitude, place.latitude), location(prevDestination.longitude, prevDestination.latitude)) + listOf( + location(place.longitude, place.latitude), + location(prevDestination.longitude, prevDestination.latitude) + ) } else { listOf(location(place.longitude, place.latitude)) } @@ -853,7 +873,11 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb } } - companion object { + override fun invalidateNavigationScreen() { + navigationScreen.invalidate() + } + + companion object { // URI host for deep linking var uriHost: String = "navigation" diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt index a714a6f..0334915 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt @@ -8,6 +8,7 @@ import android.location.Location import android.util.Log import androidx.car.app.AppManager import androidx.car.app.CarContext +import androidx.car.app.Session import androidx.car.app.SurfaceCallback import androidx.car.app.SurfaceContainer import androidx.compose.foundation.layout.PaddingValues @@ -32,19 +33,15 @@ import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.data.Constants.TAG import com.kouros.navigation.data.Constants.TILT import com.kouros.navigation.data.DarkMode -import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.ViewStyle import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.calculateTilt import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.duration -import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.location import com.kouros.navigation.utils.previewZoom import com.kouros.navigation.utils.settingsViewModel -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraState import org.maplibre.compose.style.BaseStyle @@ -60,8 +57,8 @@ import java.time.LocalDateTime class SurfaceRenderer( private var carContext: CarContext, lifecycle: Lifecycle, - //private var routeModel: RouteCarModel, - private var viewModelStoreOwner: ViewModelStoreOwner + private var viewModelStoreOwner: ViewModelStoreOwner, + private var navigationSession: CarSession ) : DefaultLifecycleObserver { // Last known location for bearing calculations @@ -99,7 +96,7 @@ class SurfaceRenderer( val trafficData = MutableLiveData(emptyMap()) // Speed camera locations as GeoJSON - val speedCamerasData = MutableLiveData("") + val speedCameraData = MutableLiveData("") // Current speed in km/h val speed = MutableLiveData(0F) @@ -182,7 +179,7 @@ class SurfaceRenderer( this.setViewTreeLifecycleOwner(lifecycleOwner) this.setViewTreeSavedStateRegistryOwner(lifecycleOwner) setContent { - MapView() + MapView() } } presentation = Presentation(carContext, virtualDisplay.display) @@ -227,10 +224,20 @@ class SurfaceRenderer( } /** - * Called when user scrolls the map (not currently implemented). + * Called when user scrolls the map . */ override fun onScroll(distanceX: Float, distanceY: Float) { synchronized(this@SurfaceRenderer) { + viewStyle = ViewStyle.PAN_VIEW + if (distanceX != 0.0F) { + lastLocation.longitude += (distanceX / 1000) / cameraPosition.value!!.zoom + } + if (distanceY != 0.0F) { + lastLocation.latitude += (distanceY / 1000) / cameraPosition.value!!.zoom + } + val pos = Position(lastLocation.longitude, lastLocation.latitude) + updateCameraPosition( target = pos) + navigationSession.invalidateNavigationScreen() } } @@ -238,7 +245,9 @@ class SurfaceRenderer( * Called when user scales (zooms) the map (not currently implemented). */ override fun onScale(focusX: Float, focusY: Float, scaleFactor: Float) { - + synchronized(this@SurfaceRenderer) { + Log.d(TAG, "onScale") + } } } @@ -260,7 +269,7 @@ class SurfaceRenderer( val position: CameraPosition? by cameraPosition.observeAsState() val route: String? by routeData.observeAsState() val traffic: Map? by trafficData.observeAsState() - val speedCameras: String? by speedCamerasData.observeAsState() + val speedCamera: String? by speedCameraData.observeAsState() val paddingValues = getPaddingValues(height, viewStyle) val cameraState = cameraState(paddingValues, position, tilt) val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode) @@ -273,7 +282,7 @@ class SurfaceRenderer( route, traffic, viewStyle, - speedCameras, + speedCamera, showBuildings ) ShowPosition(cameraState, position, paddingValues, dark) @@ -342,9 +351,9 @@ class SurfaceRenderer( viewStyle = ViewStyle.PAN_VIEW } val newZoom = if (zoomSign < 0) { - cameraPosition.value!!.zoom - 1.0 + cameraPosition.value!!.zoom - 0.2 } else { - cameraPosition.value!!.zoom + 1.0 + cameraPosition.value!!.zoom + 0.2 } if (viewStyle == ViewStyle.VIEW) { tilt = calculateTilt(newZoom, tilt) @@ -416,7 +425,8 @@ class SurfaceRenderer( * Updates camera position with new bearing, zoom, and target. * Posts update to LiveData for UI observation. */ - fun updateCameraPosition(bearing: Double = 0.0, zoom: Double, target: Position, tilt: Double) { + fun updateCameraPosition(bearing: Double = 0.0, zoom: Double = cameraPosition.value!!.zoom , + target: Position, tilt: Double = 0.0) { synchronized(this) { cameraPosition.postValue( cameraPosition.value!!.copy( diff --git a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt index e233816..73aa347 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt @@ -1,6 +1,7 @@ package com.kouros.navigation.car.map import android.location.Location +import android.util.Log import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues @@ -52,6 +53,7 @@ import org.maplibre.compose.location.LocationPuck import org.maplibre.compose.location.LocationPuckColors import org.maplibre.compose.location.LocationPuckSizes import org.maplibre.compose.location.UserLocationState +import org.maplibre.compose.map.GestureOptions import org.maplibre.compose.map.MapOptions import org.maplibre.compose.map.MaplibreMap import org.maplibre.compose.map.OrnamentOptions @@ -100,7 +102,8 @@ fun MapLibre( OrnamentOptions(isScaleBarEnabled = false) ), cameraState = cameraState, - baseStyle = baseStyle + baseStyle = baseStyle, + ) { getBaseSource(id = "openmaptiles")?.let { tiles -> if (!showBuildings) { diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt index c568736..76179b2 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt @@ -54,7 +54,7 @@ class Simulation { longitude = point[0] bearing = curBearing speedAccuracyMetersPerSecond = 1.0f // ~1 m/s - speed = 13.0f // ~50 km/h + speed = 5.0f time = System.currentTimeMillis() elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos() } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationListener.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationListener.kt index 801535e..0c66e5b 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationListener.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationListener.kt @@ -18,4 +18,5 @@ interface NavigationListener { fun navigateToPlace(place: Place) fun recalcRoute(destination: Place) + } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt index fcaaf7e..d245965 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt @@ -4,6 +4,7 @@ import android.os.CountDownTimer import android.os.Handler import android.util.Log import androidx.car.app.CarContext +import androidx.car.app.CarToast import androidx.car.app.Screen import androidx.car.app.model.Action import androidx.car.app.model.Action.FLAG_IS_PERSISTENT @@ -20,6 +21,7 @@ import androidx.car.app.navigation.model.Destination import androidx.car.app.navigation.model.MapWithContentTemplate import androidx.car.app.navigation.model.MessageInfo import androidx.car.app.navigation.model.NavigationTemplate +import androidx.car.app.navigation.model.PanModeListener import androidx.car.app.navigation.model.RoutingInfo import androidx.car.app.navigation.model.Step import androidx.car.app.navigation.model.TravelEstimate @@ -39,7 +41,6 @@ import com.kouros.navigation.data.ViewStyle import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.getSettingsViewModel -import com.kouros.navigation.utils.location import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -81,6 +82,8 @@ open class NavigationScreen( private lateinit var steps: MutableList private var junctionImage: CarIcon? = null private var backGroundColor = CarColor.BLUE + + private var showAlternativeRoute = false val observerRecentPlaces = Observer> { newPlaces -> recentPlaces.addAll(newPlaces) if (newPlaces.isNotEmpty() && !tripSuggestionCalled) { @@ -99,6 +102,9 @@ open class NavigationScreen( navigationViewModel.recentPlaces.observe(this, observerRecentPlaces) tripSuggestion = it }) + repository.alternativeRoutesFlow.asLiveData().observe(this, Observer { + showAlternativeRoute = it + }) lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onStop(owner: LifecycleOwner) { arrivalTimer?.cancel() @@ -145,17 +151,17 @@ open class NavigationScreen( .setDestinationTravelEstimate(destinationTravelEstimate) .setActionStrip(actionStripBuilder.build()) .setMapActionStrip( - mapActionStrip( + mapActionStrip(carContext, surfaceRenderer.viewStyle, { zoomPlus() }, { zoomMinus() }, { - createAction( - carContext = carContext, R.drawable.ic_pan_24, - 0, - onClickAction = { + Action.Builder() + .setIcon(createCarIcon(carContext, R.drawable.ic_recenter_24)) + .setFlags(0) + .setOnClickListener { surfaceRenderer.setStandardView() invalidate() } - ) + .build() }) ) .setBackgroundColor(backGroundColor) @@ -166,24 +172,25 @@ open class NavigationScreen( * Creates and returns a template for the default view state. */ private fun navigationView(actionStripBuilder: ActionStrip.Builder): Template { + val mapActionStrip = mapActionStrip(carContext, + surfaceRenderer.viewStyle, + { zoomPlus() }, { zoomMinus() }, { + createAction( + carContext = carContext, R.drawable.ic_recenter_24, + 0, + onClickAction = { + surfaceRenderer.viewStyle = ViewStyle.VIEW + invalidate() + }) + }) return NavigationTemplate.Builder() .setBackgroundColor(backGroundColor) .setActionStrip(actionStripBuilder.build()) - .setMapActionStrip( - mapActionStrip( - surfaceRenderer.viewStyle, - { zoomPlus() }, { zoomMinus() }, { - createAction( - carContext = carContext, R.drawable.ic_pan_24, - 0, - onClickAction = { - surfaceRenderer.viewStyle = ViewStyle.VIEW - invalidate() - }) - }) - ) + .setMapActionStrip(mapActionStrip) + .setPanModeListener { isInPanMode: Boolean -> + Log.d(TAG, "PanMode $isInPanMode") + } .build() - } /** @@ -231,11 +238,11 @@ open class NavigationScreen( .setBackgroundColor(backGroundColor) .setActionStrip(actionStripBuilder.build()) .setMapActionStrip( - mapActionStrip( + mapActionStrip(carContext, surfaceRenderer.viewStyle, { zoomPlus() }, { zoomMinus() }, { createAction( - carContext = carContext, R.drawable.ic_pan_24, + carContext = carContext, R.drawable.ic_recenter_24, 0, onClickAction = { surfaceRenderer.viewStyle = ViewStyle.VIEW @@ -292,7 +299,7 @@ open class NavigationScreen( val builder = MapWithContentTemplate.Builder() .setContentTemplate(contentTemplate) .setActionStrip( - mapActionStrip( + mapActionStrip(carContext, ViewStyle.VIEW, { settingsAction() }, { @@ -304,7 +311,7 @@ open class NavigationScreen( }, { createAction( - carContext = carContext, R.drawable.ic_zoom_out_24, + carContext = carContext, R.drawable.ic_recenter_24, FLAG_IS_PERSISTENT, onClickAction = { surfaceRenderer.viewStyle = ViewStyle.VIEW @@ -358,6 +365,7 @@ open class NavigationScreen( surfaceRenderer, place, navigationViewModel, + showAlternativeRoute ) ) { obj: Any? -> if (obj != null) { diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt index 04d1ebf..3a15baf 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt @@ -46,10 +46,15 @@ class PlaceListScreen( private var routingEngine = 0 + private var showAlternativeRoute = false + init { repository.routingEngineFlow.asLiveData().observe(this, Observer { routingEngine = it }) + repository.alternativeRoutesFlow.asLiveData().observe(this, Observer { + showAlternativeRoute = it + }) lifecycle.addObserver(object : DefaultLifecycleObserver { override fun onStop(owner: LifecycleOwner) { navigationViewModel.recentPlaces.value = emptyList() @@ -137,10 +142,11 @@ class PlaceListScreen( .pushForResult( RoutePreviewScreen( carContext, - RoutePreviewType.MULTI_ROUTE, + if (showAlternativeRoute) RoutePreviewType.MULTI_ROUTE else RoutePreviewType.SINGLE_ROUTE, surfaceRenderer, place, navigationViewModel, + showAlternativeRoute ) ) { obj: Any? -> if (obj != null) { diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RequestPermissionScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RequestPermissionScreen.kt index dfc3e1c..ff29ab0 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RequestPermissionScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RequestPermissionScreen.kt @@ -16,9 +16,8 @@ import androidx.car.app.model.Template /** Screen for asking the user to grant location permission. */ class RequestPermissionScreen( carContext: CarContext, - var permissionCheckCallback: PermissionCheckCallback, - //var mContactsPermissionCheckCallback: LocationPermissionCheckCallback, - val permissions: MutableList = ArrayList() + val permissions: List = ArrayList(), + var permissionCheckCallback: PermissionCheckCallback ) : Screen(carContext) { /** Callback called when the permission is granted. */ @@ -29,7 +28,7 @@ class RequestPermissionScreen( override fun onGetTemplate(): Template { - var message = "" + var message = "This app needs access to location" if (permissions.contains(permission.ACCESS_FINE_LOCATION)) message = "This app needs access to location and to car speed" if (permissions.contains("android.car.permission.CAR_SPEED")) @@ -83,7 +82,7 @@ fun checkPermission(carContext: CarContext, permission: String) : Boolean { screenManager.pop() return@RequestPermissionScreen }, - permissions + permissions = permissions ) ) } else { diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt index 3e77566..c9e8371 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt @@ -55,6 +55,7 @@ class RoutePreviewScreen( private var surfaceRenderer: SurfaceRenderer, private var destination: Place, private val navigationViewModel: NavigationViewModel, + private var showAlternativeRoute: Boolean ) : Screen(carContext) { private var isFavorite = false @@ -71,7 +72,7 @@ class RoutePreviewScreen( var loading = true - var showAlternativeRoute = true + var flag = FLAG_DEFAULT private val backPressedCallback = object : OnBackPressedCallback(false) { override fun handleOnBackPressed() { @@ -84,7 +85,7 @@ class RoutePreviewScreen( routeModel.startNavigation(route) surfaceRenderer.setPreviewRouteData(routeModel) loading = false - if (routeModel.route.routes.size == 1) { + if (routeModel.route.routes.size == 1 && showAlternativeRoute) { routeType = RoutePreviewType.SINGLE_ROUTE showAlternativeRoute = false } @@ -116,6 +117,10 @@ class RoutePreviewScreen( routingEngine = it }) + + repository.alternativeRoutesFlow.asLiveData().observe(this, Observer { + showAlternativeRoute = it + }) lifecycleScope.launch { navigationViewModel.loadPreviewRoute( carContext, @@ -178,7 +183,7 @@ class RoutePreviewScreen( listContent.build() } else { val navigateAction = - createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT,{ + createAction(carContext, R.drawable.navigation_48px, flag,{ onNavigate(routeModel.navState.currentRouteIndex) }) val selectRouteAction = createAction(carContext, R.drawable.alt_route_48px, FLAG_IS_PERSISTENT, { @@ -202,7 +207,7 @@ class RoutePreviewScreen( .setContentTemplate(content) .setMapController( MapController.Builder().setMapActionStrip( - mapActionStrip(ViewStyle.PREVIEW, {zoomPlus()}, { zoomMinus()}, { + mapActionStrip(carContext, ViewStyle.PREVIEW, {zoomPlus()}, { zoomMinus()}, { zoomMinus() } )).build() @@ -225,6 +230,7 @@ class RoutePreviewScreen( carContext, R.drawable.ic_zoom_in_24, FLAG_IS_PERSISTENT, onClickAction = { + flag = FLAG_IS_PERSISTENT surfaceRenderer.handleScale(1) invalidate() } @@ -239,6 +245,7 @@ class RoutePreviewScreen( carContext, R.drawable.ic_zoom_out_24, FLAG_IS_PERSISTENT, onClickAction = { + flag = FLAG_IS_PERSISTENT surfaceRenderer.handleScale(-1) invalidate() } @@ -297,7 +304,7 @@ class RoutePreviewScreen( } private fun createRow(route: Routes, index: Int): Row { - val navigateAction = createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT ) { + val navigateAction = createAction(carContext, R.drawable.navigation_48px, flag ) { this.onNavigate(index) } val routeText = createRouteText(route) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/ScreenUtils.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/ScreenUtils.kt index 137c762..5f02389 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/ScreenUtils.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/ScreenUtils.kt @@ -12,6 +12,7 @@ import androidx.car.app.model.CarIcon import androidx.car.app.model.Row import androidx.core.graphics.createBitmap import androidx.core.graphics.drawable.IconCompat +import com.kouros.data.R import com.kouros.navigation.data.Constants.CHARGING_STATION import com.kouros.navigation.data.Constants.FUEL_STATION import com.kouros.navigation.data.Constants.PHARMACY @@ -82,20 +83,28 @@ fun createActionStripBuilder(action1: () -> Action, action2: () -> Action): Acti * Creates an ActionStrip builder for map-related actions like zoom and pan. */ fun mapActionStrip( + carContext: CarContext, viewStyle: ViewStyle, zoomPlus: () -> Action, zoomMinus: () -> Action, - panAction: () -> Action + recenterAction: () -> Action ): ActionStrip { val actionStripBuilder = ActionStrip.Builder() .addAction(zoomPlus()) .addAction(zoomMinus()) + actionStripBuilder.addAction( + Action.Builder(Action.PAN) + .setIcon(createCarIcon(carContext, R.drawable.ic_pan_24)) + .setFlags(0) + .build() + ) if (viewStyle == ViewStyle.PAN_VIEW) { actionStripBuilder .addAction( - panAction() + recenterAction() ) } + return actionStripBuilder.build() } @@ -106,7 +115,7 @@ fun createAction( carContext: CarContext, @DrawableRes iconRes: Int, flag: Int = FLAG_DEFAULT, - onClickAction: () -> Unit + onClickAction: () -> Unit, ): Action { return Action.Builder() .setIcon(createCarIcon(carContext, iconRes)) diff --git a/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt index 0640d5f..52444ca 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt @@ -16,17 +16,16 @@ import kotlinx.coroutines.flow.map private const val DATASTORE_NAME = "navigation_settings" +val Context.dataStore: DataStore by preferencesDataStore(name = DATASTORE_NAME) + + /** * Central manager for app settings using DataStore */ class DataStoreManager(private val context: Context) { - companion object { - val Context.dataStore: DataStore by preferencesDataStore(name = DATASTORE_NAME) - } - // Keys - object PreferencesKeys { + companion object PreferencesKeys { val SHOW_3D = booleanPreferencesKey("Show3D") @@ -66,186 +65,186 @@ class DataStoreManager(private val context: Context) { val show3DFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.SHOW_3D] == true + preferences[SHOW_3D] == true } val darkModeFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.DARK_MODE] + preferences[DARK_MODE] ?: 0 } val avoidMotorwayFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.AVOID_MOTORWAY] == true + preferences[AVOID_MOTORWAY] == true } val avoidTollwayFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.AVOID_TOLLWAY] == true + preferences[AVOID_TOLLWAY] == true } val avoidFerryFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.AVOID_FERRY] == true + preferences[AVOID_FERRY] == true } val useCarLocationFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.CAR_LOCATION] == true + preferences[CAR_LOCATION] == true } val routingEngineFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.ROUTING_ENGINE] + preferences[ROUTING_ENGINE] ?: 2 } val lastRouteFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.LAST_ROUTE] + preferences[LAST_ROUTE] ?: "" } val tomTomApiKeyFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.TOMTOM_APIKEY] + preferences[TOMTOM_APIKEY] ?: "" } val recentPlacesFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.RECENT_PLACES] + preferences[RECENT_PLACES] ?: "" } val distanceModeFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.DISTANCE_MODE] + preferences[DISTANCE_MODE] ?: 0 } val guidanceAudioFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.GUIDANCE_AUDIO] + preferences[GUIDANCE_AUDIO] ?: 0 } val trafficFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.TRAFFIC] == true + preferences[TRAFFIC] == true } val tripSuggestionFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.TRIP_SUGGESTION] == true + preferences[TRIP_SUGGESTION] == true } val engineTypeFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.ENGINE_TYPE] + preferences[ENGINE_TYPE] ?: EngineType.COMBUSTION.ordinal } val alternativeRoutesFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.ALTERNATIVE_ROUTES] == true + preferences[ALTERNATIVE_ROUTES] == true } // Save values suspend fun setShow3D(enabled: Boolean) { context.dataStore.edit { preferences -> - preferences[PreferencesKeys.SHOW_3D] = enabled + preferences[SHOW_3D] = enabled } } suspend fun setDarkMode(mode: Int) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.DARK_MODE] = mode + prefs[DARK_MODE] = mode } } suspend fun setAvoidMotorway(enabled: Boolean) { context.dataStore.edit { preferences -> - preferences[PreferencesKeys.AVOID_MOTORWAY] = enabled + preferences[AVOID_MOTORWAY] = enabled } } suspend fun setAvoidTollway(enabled: Boolean) { context.dataStore.edit { preferences -> - preferences[PreferencesKeys.AVOID_TOLLWAY] = enabled + preferences[AVOID_TOLLWAY] = enabled } } suspend fun setAvoidFerry(enabled: Boolean) { context.dataStore.edit { preferences -> - preferences[PreferencesKeys.AVOID_FERRY] = enabled + preferences[AVOID_FERRY] = enabled } } suspend fun setCarLocation(enabled: Boolean) { context.dataStore.edit { preferences -> - preferences[PreferencesKeys.CAR_LOCATION] = enabled + preferences[CAR_LOCATION] = enabled } } suspend fun setRoutingEngine(mode: Int) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.ROUTING_ENGINE] = mode + prefs[ROUTING_ENGINE] = mode } } suspend fun setLastRoute(route: String) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.LAST_ROUTE] = route + prefs[LAST_ROUTE] = route } } suspend fun setTomtomApiKey(apiKey: String) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.TOMTOM_APIKEY] = apiKey + prefs[TOMTOM_APIKEY] = apiKey } } suspend fun setRecentPlaces(apiKey: String) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.RECENT_PLACES] = apiKey + prefs[RECENT_PLACES] = apiKey } } suspend fun setDistanceMode(mode: Int) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.DISTANCE_MODE] = mode + prefs[DISTANCE_MODE] = mode } } suspend fun setGuidanceAudio(mode: Int) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.GUIDANCE_AUDIO] = mode + prefs[GUIDANCE_AUDIO] = mode } } suspend fun setTraffic(enabled: Boolean) { context.dataStore.edit { preferences -> - preferences[PreferencesKeys.TRAFFIC] = enabled + preferences[TRAFFIC] = enabled } } suspend fun setTripSuggestion(enabled: Boolean) { context.dataStore.edit { preferences -> - preferences[PreferencesKeys.TRIP_SUGGESTION] = enabled + preferences[TRIP_SUGGESTION] = enabled } } suspend fun setEngineType(mode: Int) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.ENGINE_TYPE] = mode + prefs[ENGINE_TYPE] = mode } } suspend fun setAlternativeRoutes(enabled: Boolean) { context.dataStore.edit { preferences -> - preferences[PreferencesKeys.ALTERNATIVE_ROUTES] = enabled + preferences[ALTERNATIVE_ROUTES] = enabled } } diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt index 6fc716d..f2f3bf4 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt @@ -21,7 +21,7 @@ const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incident private const val tomtomFields = "{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}" -val useLocal = BuildConfig.DEBUG +val useLocal = BuildConfig.DEBUG val useLocalTraffic = BuildConfig.DEBUG diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt index 6fe5ecc..9984065 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt @@ -78,7 +78,6 @@ class TomTomRoute { ) { val lanes = mutableListOf() var startIndex = 0 - var lastLane: Lane? = null section.lanes?.forEach { itLane -> val lane = Lane( location = location( @@ -91,13 +90,7 @@ class TomTomRoute { endIndex = section.endPointIndex ) startIndex = section.startPointIndex - if (lastLane == null - || (!(lastLane.valid && lane.valid - && lastLane.indications == lane.indications)) - ) { - lanes.add(lane) - } - lastLane = lane + lanes.add(lane) } intersections.add(Intersection(waypoints[startIndex], lanes)) } diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt index 7ad710c..5a462f4 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt @@ -76,48 +76,9 @@ open class RouteModel { navState = navState.copy(lastLocation = navState.currentLocation) } - fun nextStep(): StepData { - val distanceToNextStep = routeCalculator.leftStepDistance() - val nextStep = navState.route.nextStep(1) - var streetName = nextStep.street - var maneuverType = currentStep.maneuver.type - if (distanceToNextStep < NEXT_STEP_THRESHOLD) { - streetName = nextStep.maneuver.street - maneuverType = nextStep.maneuver.type - } - - val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType) - // Construct and return the final StepData object - return StepData( - instruction = streetName, - street = "", - leftStepDistance = distanceToNextStep, - currentManeuverType = maneuverType, - icon = maneuverIcon, - arrivalTime = routeCalculator.arrivalTime(), - leftDistance = routeCalculator.travelLeftDistance(), - exitNumber = nextStep.maneuver.exit, - message = nextStep.maneuver.message - ) - } - - private fun currentLanes(): List { - var lanes = emptyList() - if (navState.route.legs().isNotEmpty()) { - currentStep.intersection.forEach { - if (it.lane.isNotEmpty()) { - val distance = - navState.lastLocation.distanceTo(location(it.location[0], it.location[1])) - if (distance < NEXT_STEP_THRESHOLD) { - lanes = it.lane - return@forEach - } - } - } - } - return lanes - } - + /* + * Returns the current step + */ fun currentStep(): StepData { val distanceToNextStep = routeCalculator.leftStepDistance() // Determine the maneuver type and corresponding icon @@ -148,6 +109,54 @@ open class RouteModel { ) } + /* + * Returns the next step + */ + fun nextStep(): StepData { + val distanceToNextStep = routeCalculator.leftStepDistance() + val nextStep = navState.route.nextStep(1) + var streetName = nextStep.street + var maneuverType = currentStep.maneuver.type + if (distanceToNextStep < NEXT_STEP_THRESHOLD) { + streetName = nextStep.maneuver.street + maneuverType = nextStep.maneuver.type + } + + val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType) + // Construct and return the final StepData object + return StepData( + instruction = streetName, + street = "", + leftStepDistance = distanceToNextStep, + currentManeuverType = maneuverType, + icon = maneuverIcon, + arrivalTime = routeCalculator.arrivalTime(), + leftDistance = routeCalculator.travelLeftDistance(), + exitNumber = nextStep.maneuver.exit, + message = nextStep.maneuver.message + ) + } + + /* + * Returns the current lanes + */ + private fun currentLanes(): List { + var lanes = emptyList() + if (navState.route.legs().isNotEmpty()) { + currentStep.intersection.forEach { + if (it.lane.isNotEmpty()) { + val distance = + navState.lastLocation.distanceTo(location(it.location[0], it.location[1])) + if (distance < NEXT_STEP_THRESHOLD) { + lanes = it.lane + return@forEach + } + } + } + } + return lanes + } + /** * Checks for navigating */ diff --git a/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt index efc1e9d..6c5dcd8 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt @@ -1,10 +1,7 @@ package com.kouros.navigation.model -import androidx.datastore.preferences.core.edit import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.kouros.navigation.data.datastore.DataStoreManager.Companion.dataStore -import com.kouros.navigation.data.datastore.DataStoreManager.PreferencesKeys import com.kouros.navigation.repository.SettingsRepository import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn diff --git a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt index cb59527..96e297d 100644 --- a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt +++ b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt @@ -29,6 +29,7 @@ import kotlin.math.ln import kotlin.math.pow import kotlin.math.roundToInt import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -138,7 +139,7 @@ fun duration( lastLocationUpdate: LocalDateTime ): Duration { if (preview) { - return 3.seconds + return 10.milliseconds } val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) { 2.seconds diff --git a/common/data/src/main/res/drawable/electric_car_24px.xml b/common/data/src/main/res/drawable/electric_car_24px.xml new file mode 100644 index 0000000..57baad3 --- /dev/null +++ b/common/data/src/main/res/drawable/electric_car_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/ic_recenter_24.xml b/common/data/src/main/res/drawable/ic_recenter_24.xml new file mode 100644 index 0000000..52cfb95 --- /dev/null +++ b/common/data/src/main/res/drawable/ic_recenter_24.xml @@ -0,0 +1,25 @@ + + + + +