From 65e911880687ebda3b9146e9f4ab1796a9a01f60 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 6 Jul 2026 09:58:23 -0700 Subject: [PATCH] 401 auto re-login, My Location via native GPS, edge-to-edge, brain-car icon - Client: onUnauthorized hook; 401 clears the token and (in-app) bounces to sign-in - Map: "My location" uses the native UserLocation fix + flyTo; safe-area insets on the floating status bar / controls - Enable edgeToEdgeEnabled (Android 15 forces it) so headers/back-arrow sit correctly - App icon: brain-car launcher + adaptive icon Co-Authored-By: Claude Fable 5 --- app/app.json | 6 ++++++ app/assets/adaptive-icon.png | Bin 0 -> 9724 bytes app/assets/icon.png | Bin 0 -> 15027 bytes app/src/api/client.ts | 5 ++++- app/src/auth/AuthContext.tsx | 12 ++++++++++++ app/src/auth/authBus.ts | 6 ++++++ app/src/screens/MapScreen.tsx | 31 ++++++++++++++++++++++++------- parksmarter-client/src/client.ts | 3 +++ parksmarter-client/src/http.ts | 11 +++++++++++ 9 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 app/assets/adaptive-icon.png create mode 100644 app/assets/icon.png create mode 100644 app/src/auth/authBus.ts diff --git a/app/app.json b/app/app.json index 4ba1384..83b4399 100644 --- a/app/app.json +++ b/app/app.json @@ -7,8 +7,14 @@ "orientation": "portrait", "userInterfaceStyle": "automatic", "newArchEnabled": true, + "icon": "./assets/icon.png", "android": { "package": "top.mowden.bigbrainparking", + "edgeToEdgeEnabled": true, + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#0F2A33" + }, "permissions": [ "ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION", diff --git a/app/assets/adaptive-icon.png b/app/assets/adaptive-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f7e90a2e8ccba4975eb02ba814815df49f3f4633 GIT binary patch literal 9724 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 D(cpJM literal 0 HcmV?d00001 diff --git a/app/assets/icon.png b/app/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c5fabb3a419a45c9257010af9de8d9928da771b8 GIT binary patch literal 15027 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%0D3J`Xoq-G`nqhZFWZs*g${9c*`fAvMP;q^R z8|Hk-)kF~yMGjBl&eR3jj`Sne0XzU2b&(@Z^X297#E@BMX%5XKy*tB3!p0;_IIeaLoAn zk?T9}q5-}&u7z#t$KP~@J6hv6KT7>~bl$^-x^Mkcbel5L_J2d_zwTo^6NN+E_gs>4 z{l>rV|If#GrfB)>(W>Vkzm{`qU0p69G5g1v{=26oHD#n8R`&S+_gW_|T(EA|RF1n) z+LBLLE=TD<5Egw=8{sP_j63#{Yo}-X0td>GzX6n_i{;ZlCOIwY53D$C-0_1M?>H}i z53OS9O?v!B5Jj)9AtkTTxsoJahIpAvP}H^WFw6X z3jVdl&-z^44n|hi%O+TIV)B7Xc&gDpSbk=c99ioqH!P>z#RjK-6&~6Y8Bj0Fjkx(e zKhCg{7swT(aij|B$B>MLaVA_EpPzT^D(J9vYF?%^VIE`xOer$dONR>3djf7=_D^jn z!%!0%o+h#KTZ+bat%Oiw3o10NOI$VoGZp_OlfE6S=af^oOkGeuF{)9Ws%0^q*YMJ02;t`ziPrpD$I>W2pZ#mv z=9^boc>2uA{FIpjoTrBe;y(DgmgMotrs?4uQ6inFyZ1$-0ez%s)A#I5l;VoX1uds< zYwXn1#PL1THxFtP&VDknA0>+o6}}`qJhwm0@nxBxM>~EE&CPUrk*%%do0*-EA0x12 zGe MfvK^f|NyzL!z_eQnt&bZMnVF5|vu6+&c8ZIY!i|_j{K?H*vEa@Ly#HMeXnw zzQ&U>XI8MyCz)IjEX4q($oV_Ak=i?byF6Wu_mfb1IcoXH^{7h&JI#gWq*`y)MD^|) zKvfCc+PlEAd$FFu#9T-1;|gafAXq-qb<_P)+25R`Ouz8{TFQ~wYsi%zPChP2u3Yps z)-&92KR?QmR=b_%!H*XlIgJ93=#gp!f{Gg95&J|AZ#^vGCZ_lMmgng^_Gr$7gSwRWNHF%sjnf0tJ*a!@7>_kKH{vK zmFQgC7dueXDEroQkI_li&p(Ntez%sgWvr4mTDHZKNK3aN|NU38 zoF^&PUy&K;7rwiD?@RNWpTr-d5V+Vd=*aSn?#+7)SM0StGfU8n9zALPXWcWYZ^s|D zO3QNIqv+M;Zm3b^t?BLcw+S|G>@ziGw|4dJ*6uW6i!ItWIi2JRt?P&LMU`Pv;^ZOa z@pDc+hfX&v=lGaEe`r;a!rRhT_0`rid;F~F$>|r#g@P$d<@oEv67*hdndb7bx{xj3 z!Tw%dDAK5Xv`lTNY}uq_#XrS&XDsKz#L&nG3a-=>jw-QE>_f(XK93icX!772>sY>h zYd~zndH*@Z#=s4HPT@IKG)mPFk_KzY(z-^^@9;g ze0)U`{v>6yE{KBJ_od>op4ui;{8?NJ{-3M~t$45Ic$Rya#|?$>~`GEuHL&#u=j6*1(vygUi$i2X4Cr1*|i#<1m+FgfATn^*GkJZ zma|o-Th%9T8$}`E$qXeD?12(rcPR8foLSqCywG{m8l~8G&gnm#RikZ#&IEqV81p^4 z|4Sg2w@OD@VtCZ@f=p$Fr=69~UQ^;lJQkEjFjm#N}=BY}xcOLBYyYZ?FGaCON zVPEBO)7cbyW|+XIq2k-pCs{#pwgyNC)XVmakN$epU4CO1PPrW$Ia`t9+sWT2O!GrU zWKu`{iJlLej6Zc9mc5u;FDsd*XHhEvMV5C1Wulm6nDDfi*53VV4levhOT`!!{F|&V ztGxU+q$Xp?s0!&gN87)&4atnnONd#k8;7>C^T__H0eR(l%#PmFx zVRu1q!~GU|S<^pbmieI02v)&HR8KuV-gDG-k|xc~is(MM=yz>RBPT3Ib_jK$+$pep zr0v$3--)bQ5K7F56l@(_tgb&+Qj~S?R2VXoqJ!^b%mr3WM>|%jR3Lx6C3eRTWG`)} zOCLAPv6i+9EDATevv1Enx!{A80Pj(aL{yE0SL z)_S3xL7eX$2odB*UB6Efd%Clr+GsU~YDK7N`7w8TOPe~0HoS-GdI;@e?`pkT=f=^0 zMIEI;qn)9Q!~@h-6_P`PB&H@2byfatnY=u@0PVA(%kvXzvE&l3`jOHjWqr(1TMC^_ zi-g_&Tu`sx*Wd6VwSVD<1638;q$-VVI^yR_dSlg3~|kwSqJKyvcxly(PL zf>=o9309a$MiUx}(F8?H$%7x!TWI~(fSUVOGIB4W-d4hL_vTscP}J(m&+Tp1yXnx+ zB2g;2Uo;sIEYlc<&=$I&&fX+r`Vln{K}4mMtz2is5@05lpTQu0Q$$bc1JFnD2cEE2 zX_SnXi&oML5_(rsiAJPAI19!C|dE)j8PicT3cPHE?;@=I*;AcR6`Ggmk zT7Kg_&lf-KQ>P+hu^9FAN!cXMO5^&G2u*z3@W7LC0jB*EZf|n@m&N0#rTK>#<2MWS zCcFIG(#*8H@suB^j&jGkx1pY9E*WzD?Q)Lz5sD^N{87(mv%>17NDAc$I;AKYYhAW` zr_m}a`4a>N5yfgQ_me}s&JUx63!(+337;nc7*E{tJ6xm)v2D?hyiMG322DuCR3SQI`F$W?p}JFAXY>S zSVadgCT+!6_#YeGh1(EJ5G|IN{g&Magrx+HEY$hD)b0B73W3l{0bB$LPRQzs_CG#D z3mEb*PAVa(w|Q%L#R1&7u!srgD?Q3oZ-#vN2Nqo4wG|j4SkE(ITToYU^xmoW^#rls zVzjXWfF9{}&DL8hlWZXXZ7EJ{-Rn1Rm=iA8TeO3gT|H8I#;gWSsMr*a8tp^2gg;$n zhNU{6zB?g=fxGzMwtS|}c8fhsm|Y)aAeT2k(JxPW9AKj8G%oTGSn#$`MUGJ52u${JDUm-Dz%PMK=J%W#5k;?xqF zA5v5Ill5jaU%ulcvUoZbei>V^CW%P!1KF0DMz(4hkdbS`=ePuEQ{mh=wpH|G`F1t1 zVv7`^+5&ak=W85$MV96ZGSfbXsNs$BUKSDkUrCeQ%+wE79ll z0PR=ipUqM3$TZVcQ`jlDNHo>=*g|wkz#?z!q>I{hPM$C3=pBfzeF0S7mSUyk4A5`x zgUiR{XxotB$DiNC$vZOR@=6-dbK`S-k5q5WafAwSN(7<$GLI_tmiwg3t$a=%GHy3_ z6Bk+|Lw3V^*hR*SlVpyd+UBA39l!OL-?!vgJwj@11TM&+L&gZ2bTP=|!vK97*nA1X*Bn6S!Ao|gF+Cq$)q0%EtAQ{{ zDNbqTa1ccJ2ENC+Lg+fe29DwYXDb%6l`rJ7Hh?NWt1*$SsS1q_#JlhKfWjZmF=*zf zk~%jTFptjw2(1_^RA!l4VUNxES~jQy9_!9pnae@R#zF^T9Ahakmv_l;oGiD&95?YH zy04$1Un2Mt5eq0t=JPIkxKNd{)c(E6)>?&JOsZH8=S~K+R{8USSj6@qK4rMV6~)vI zljT<=ack~XpzfEnJ^JV>9ar=)GtN&b-1sKa8Yt48>RP}s4qb(MmM8~))(J3|aiwW! z!(<1#0!Km1b{@&$te3*8xZtQuUNjGca*$_Gwq;VD8Hw|a=@4PBvS#cz9-0m+Q^Cb3 zHYz9kBl&|1{idf0T1kj=_Z4RvbKnd^0KmGA^-dKcxbo?mLl+}c^|^8x(=UIXAclJJ zL5ecNmy43Y2y~#1m7ogixS-@5`bjcX34w}T?$16;h)+bC6qWq)uR+gs1nN-7LM>#Q zywKeSI7Y|N?MMXbE7n{tL}F+lMz&b=Yg%GAY>{m{vV7{(byS2#vv$?7tX05Tm57Kw z`6w3{h@qMrxEVsztLV{kgDI+7e(EPed~$W8l@uI7S30SV5~k{^;FBYP@@!ou=*oir j88_4~B{uoFjl(zD_n`5Z+Ny6zJ%i;Y8?(C`Xs7-ki!q8Z literal 0 HcmV?d00001 diff --git a/app/src/api/client.ts b/app/src/api/client.ts index 9b18f61..9ff8659 100644 --- a/app/src/api/client.ts +++ b/app/src/api/client.ts @@ -2,16 +2,19 @@ import Constants from 'expo-constants'; import * as Localization from 'expo-localization'; import { ParkSmarterClient, type EnvironmentName } from 'parksmarter-client'; import { secureTokenStore } from './secureTokenStore'; +import { authBus } from '@/auth/authBus'; const env = (Constants.expoConfig?.extra?.psEnvironment as EnvironmentName) ?? 'prodv2'; /** * The single app-wide API client. React Native ships a global `fetch`, so no - * fetchImpl override is needed. Tokens persist in the OS keystore. + * fetchImpl override is needed. Tokens persist in the OS keystore. On a 401 the + * client clears the token and we bounce the user to sign-in via authBus. */ export const ps = new ParkSmarterClient({ environment: env, tokens: secureTokenStore, localeCode: (Localization.getLocales()[0]?.languageCode ?? 'en').toLowerCase(), + onUnauthorized: () => authBus.onUnauthorized?.(), }); diff --git a/app/src/auth/AuthContext.tsx b/app/src/auth/AuthContext.tsx index 270d824..3b5e098 100644 --- a/app/src/auth/AuthContext.tsx +++ b/app/src/auth/AuthContext.tsx @@ -6,6 +6,7 @@ import React, { useState, } from 'react'; import { ps } from '@/api/client'; +import { authBus } from '@/auth/authBus'; import type { ApplicationValidityResponse } from 'parksmarter-client'; type AuthStatus = 'loading' | 'signedOut' | 'signedIn'; @@ -25,6 +26,17 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const [validity, setValidity] = useState(null); const [error, setError] = useState(null); + // Any 401 from the API (expired/rotated token) bounces us back to sign-in. + useEffect(() => { + authBus.onUnauthorized = () => { + setError('Your session expired — please sign in again.'); + setStatus('signedOut'); + }; + return () => { + authBus.onUnauthorized = undefined; + }; + }, []); + // On launch: bootstrap (seeds SessionId + feature flags) and probe for an // existing token by attempting an authenticated read. useEffect(() => { diff --git a/app/src/auth/authBus.ts b/app/src/auth/authBus.ts new file mode 100644 index 0000000..075d9fd --- /dev/null +++ b/app/src/auth/authBus.ts @@ -0,0 +1,6 @@ +/** + * Tiny bridge so the API client (created at module load) can notify the React + * auth layer when a 401 happens, without a circular import. AuthProvider + * registers a handler; the client calls it via app/src/api/client.ts. + */ +export const authBus: { onUnauthorized?: () => void } = {}; diff --git a/app/src/screens/MapScreen.tsx b/app/src/screens/MapScreen.tsx index 2eecf3d..0603f95 100644 --- a/app/src/screens/MapScreen.tsx +++ b/app/src/screens/MapScreen.tsx @@ -9,6 +9,7 @@ import { } from '@maplibre/maplibre-react-native'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { ps } from '@/api/client'; import { useLocation, getLastKnownSavedLocation, type Coords } from '@/features/location/useLocation'; import type { RootStackParamList } from '@/navigation/RootNavigator'; @@ -26,6 +27,7 @@ type Nav = NativeStackNavigationProp; export function MapScreen() { const navigation = useNavigation