feat(FoodTruckScreen): replace vertical overlay list with horizontal scroll strip#49
Merged
roncodes merged 5 commits intodev-v0.0.19from Mar 23, 2026
Merged
Conversation
…trip
The previous implementation rendered available food trucks as a vertically
stacked list inside a top overlay panel. This approach does not scale as
more trucks are added — the panel grows downward, obscuring the map and
degrading the user experience.
This commit replaces the vertical list with a horizontally scrollable
FlatList strip anchored to the bottom of the screen, above the tab bar.
Each card in the strip displays the truck's vehicle avatar image above its
display name (plate number), preserving the existing Pressable handler that
animates the map to the truck's location and opens the Catalog screen.
Changes:
- Add FlatList (horizontal) and View from react-native
- Add FastImage for performant, cached vehicle avatar rendering
- Add useSafeAreaInsets (react-native-safe-area-context) and
useSafeTabBarHeight hook to correctly position the strip above the
tab bar on both iOS and Android
- Remove the old availableFoodTrucks.map() vertical list rows from
the top overlay panel; the panel now only contains the info banner
and the current zone indicator
- Add renderFoodTruckCard() — renders a fixed-width card (110px) with
the vehicle avatar (64x64, FastImage, fallback to local van asset)
above the truck display name (name ?? plate_number ?? i18n key)
- Add bottom overlay View with pointerEvents='box-none' so touches in
the empty space between cards fall through to the MapView
- Add nestedScrollEnabled={true} on FlatList to prevent Android from
swallowing the horizontal scroll gesture inside the MapView
- Add StyleSheet entries: horizontalScrollContainer,
horizontalScrollContent, truckCardImage
- Remove unused faTruck icon import
- Remove unused restoreFleetbasePlace and getCoordinatesObject imports
…atar rendering
The previous implementation incorrectly placed the horizontal food truck
scroll as a separate absolute overlay at the bottom of the screen. This
commit corrects the placement and rendering as intended:
- The FlatList now lives inside the existing top overlay panel
(YStack bg='$background' borderRadius='$5'), directly beneath the
zone row — exactly where the old vertical list was.
- Each chip is small (72px wide): the vehicle avatar image (44x44, no
background fill — image sits flush on the panel surface) above the
truck display name in a compact two-line label.
- Avatar rendering is fixed: uses React Native's built-in Image
component (no FastImage dependency required) sourcing
foodTruck.vehicle.avatar_url directly from the serialized vehicle
object, with the bundled light_commercial_van.png as fallback.
- The zone row gains a conditional borderBottomWidth={1} only when
trucks are present, so the divider appears cleanly between the zone
row and the scroll strip.
- Removed the bottom absolute overlay View and all associated
useSafeTabBarHeight / useSafeAreaInsets positioning logic — no
longer needed.
- Removed FastImage import and dependency.
- Restored faTruck import (used in VehicleMarker label).
…for rendering
Follow the exact pattern established in VehicleMarker and TrackingMarker:
- Instantiate a Vehicle model: new Vehicle(foodTruck.vehicle, fleetbaseAdapter)
- Resolve avatar via: vehicle.getAttribute('avatar_url')
- Build avatarSource: avatarUrl ? { uri: avatarUrl } : require(fallback)
- Render with FastImage + FastImage.resizeMode.contain, consistent with
the non-SVG path in TrackingMarker (line 180)
- Resolve display name via vehicle.getAttribute('plate_number') as
the SDK fallback, matching how the rest of the screen accesses vehicle
attributes through the model layer
Remove the raw object property access (vehicle.avatar_url) and the
plain React Native Image component that were incorrectly used before.
…dering
Creates src/components/VehicleAvatar.tsx — a dedicated component for
rendering a vehicle's avatar image, extracted from the rendering logic
already established in TrackingMarker.
Implementation mirrors TrackingMarker exactly:
- Accepts a Vehicle model instance and an optional size prop.
- Resolves avatarSource via vehicle.getAttribute('avatar_url'), with the
bundled light_commercial_van.png as fallback (same as VehicleMarker).
- Uses the isRemoteSvg check (isObject + uri.endsWith('.svg')) to branch:
SVG path → SvgCssUri + loading Spinner overlay (same as TM line 142-167)
Raster path → FastImage + FastImage.resizeMode.contain (same as TM line 168-182)
- Both paths wrap the image in a fixed-size View with alignItems/
justifyContent center, consistent with TrackingMarker's View wrappers.
Updates FoodTruckScreen.tsx:
- Removes the inline FastImage import and direct avatar rendering from
renderFoodTruckChip.
- Imports VehicleAvatar and uses <VehicleAvatar vehicle={vehicle} size={{ width: 44, height: 44 }} />
in each chip — clean, correct, and consistent with the rest of the
vehicle rendering pipeline.
roncodes
added a commit
that referenced
this pull request
Mar 23, 2026
* Few bug fixes * Fix: Add POST_NOTIFICATIONS permission for Android 13+ support - Added POST_NOTIFICATIONS permission to AndroidManifest.xml - Implemented runtime permission request in NotificationContext.tsx - Fixes issue where notifications are blocked by default on Android 13+ - Added comprehensive documentation for the fix This resolves the issue where the app shows 'All notifications from the app are blocked' after installation from Google Play Store on Android 13+ devices. * Merge selected changes from app/oli-max branch - Updated CartItemScreen.tsx with spacing fixes for Android - Updated CartScreen.tsx with latest fixes - Updated CreateAccountVerifyScreen.tsx - Updated PhoneLoginVerifyScreen.tsx - Updated ProductScreen.tsx with Android spacing improvements Changes pulled from app/oli-max commits: - 7c83c47: latest w/ fixes - 87d7000: fix: corrected spacing on android platforms Excluded: OrderHistoryScreen.tsx (as requested) * feat: Add Accept-Language header to SDK requests - Added Accept-Language header to use-storefront hook - Added Accept-Language header to use-fleetbase hook - Headers now include user's locale preference from LanguageContext - Headers update dynamically when user changes language - Uses standard HTTP Accept-Language header (RFC 7231/9110) This enables the backend API to return localized content based on user's language preference. * feat: Implement minimum checkout amount validation - Added minimum checkout validation to use-qpay-checkout hook - Added minimum checkout validation to use-stripe-checkout (native & web) hooks - Created MinimumCheckoutNotice component for user feedback - Integrated notice display in QPayCheckoutScreen and StripeCheckoutScreen - Checkout button automatically disabled when cart below minimum - Added English and Mongolian translations for minimum order messages - Feature controlled by storefront options: required_checkout_min and required_checkout_min_amount When enabled, users must reach the configured minimum cart subtotal before checkout is allowed. A clear warning notice displays the current cart amount and required minimum. * fix: Use direct get() for required_checkout_min option check The enabled() method automatically appends '_enabled' to keys, but the required_checkout_min option doesn't follow this naming convention. Changed from: enabled('required_checkout_min') // looks for required_checkout_min_enabled To: get(info, 'options.required_checkout_min') === true // looks for required_checkout_min This matches the existing pattern used for other options like pickup_enabled and ensures the minimum checkout validation works correctly. * Fully implemented min order amount required feature * minor translation fix * fix: Upgrade react-native-maps to 1.26.19 to fix Android addViewAt error Fixes the 'addViewAt: failed to insert view into parent' error that occurs after phone login on Android devices. Issue: react-native-maps/react-native-maps#5781 Fixed in: react-native-maps v1.26.19 The error was caused by an index out of bounds exception when adding feature views in react-native-maps. This has been resolved in version 1.26.19. * fix: Add optional chaining to currentLocation in CustomerLocationSelect Merged from app/oli-max (commit f88549c). Added optional chaining (?.) to prevent potential null/undefined errors when accessing currentLocation.getAttribute('name'). This prevents crashes when currentLocation is not yet loaded or is null. * feat: improve QPay checkout reliability by moving order creation to backend - Refactor use-qpay-checkout hook to retrieve orders from backend instead of creating them - Replace handlePayment with handleOrderCompletion (order already created by backend) - Replace checkPaymentStatus with checkOrderStatus using new /checkouts/status endpoint - Update WebSocket listener to handle order from event payload - Add order recovery on component mount for better reliability - Update LoadingOverlay text to reflect new flow - Use adapter.get directly without SDK changes for faster implementation This change eliminates the reliability gap where users could pay but not receive an order if the app closed before order creation completed. * feat: add translation keys for improved QPay checkout loading messages - Add 'finalizingOrder' and 'checkingOrderStatus' keys to QPayCheckoutScreen - Update both English and Mongolian translations - Supports new loading messages in improved QPay checkout flow * fix: Convert order response to SDK Order instance in checkOrderStatus Ensures onOrderComplete callback receives proper SDK Order instance. ISSUE: - checkOrderStatus gets order from API response (plain object) - onOrderComplete callback expects SDK Order instance - Type mismatch causes issues with Order methods FIX: - Import Order from @fleetbase/sdk - Convert response to SDK instance before passing to callback - Matches pattern in handleOrderCompletion CODE: import { Order } from '@fleetbase/sdk'; const orderInstance = order instanceof Order ? order : new Order(order); handleOrderCompletion(orderInstance); RESULT: - onOrderComplete always receives SDK Order instance - Order methods work correctly - Consistent with other order handling code * fix: Move Order conversion into handleOrderCompletion (proper design) Moved SDK Order instance conversion from caller to handleOrderCompletion itself. PREVIOUS MISTAKE: - Put conversion in checkOrderStatus only - Other callers (WebSocket listener) would still break - Not DRY - conversion would need to be in every caller CORRECT DESIGN: - Conversion INSIDE handleOrderCompletion - ALL callers automatically benefit - Single source of truth - DRY principle CALLERS THAT NOW WORK: 1. checkOrderStatus (status endpoint polling) 2. WebSocket listener (real-time callback events) CODE: const handleOrderCompletion = async (order) => { // Convert at entry point - all callers benefit const orderInstance = order instanceof Order ? order : new Order(order); // Use orderInstance everywhere addOrderToHistoryCache(customer.id, orderInstance); onOrderComplete(orderInstance); }; RESULT: - onOrderComplete always receives SDK Order instance - Works from ANY caller - Maintainable and clean * fix: Add request throttling to prevent simultaneous status checks Prevents multiple simultaneous requests to checkout status endpoint. ISSUE: - Multiple status checks fired simultaneously (focus, app state, polling) - Backend received 2+ requests at same time - Could cause race conditions or duplicate order attempts - Unnecessary load on backend FIX: - Added isCheckingStatus ref to track request in progress - Check ref before making request - Skip request if one already in progress - Reset ref in finally block CODE: const isCheckingStatus = useRef(false); const checkOrderStatus = async () => { if (isCheckingStatus.current) { console.log('Request already in progress, skipping'); return; } isCheckingStatus.current = true; try { // Make request } finally { isCheckingStatus.current = false; } }; BENEFITS: - Only one status check at a time - Prevents backend race conditions - Reduces unnecessary API calls - Better performance RESULT: - Sequential status checks only - No simultaneous requests - Cleaner logs - More reliable checkout flow * move create account button to login screen * feat: add phone number verification prompt on ProfileScreen - Create AddPhoneScreen for entering phone number - Create VerifyPhoneScreen for SMS code verification - Extend AuthContext with requestPhoneVerification() and verifyPhoneNumber() - Add phone check prompt on ProfileScreen using useFocusEffect - Register new screens in StoreNavigator (Authenticated group) - Add English and Mongolian translations for new screens - Prompt appears when user has no phone number on profile - Uses native action sheet for user-friendly prompt * feat: add 'Later' dismiss option to phone number prompt - Replace 'Cancel' with 'Later' button in ProfileScreen phone prompt - Add 'addPhoneLater' translation key in English and Mongolian - Users can now dismiss the prompt and add phone number later - Improves UX by giving users control over when to add phone * fix: replace ScreenWrapper with SafeAreaView to fix back button rendering - Replace ScreenWrapper with SafeAreaView in AddPhoneScreen - Replace ScreenWrapper with SafeAreaView in VerifyPhoneScreen - Fixes issue where back button was rendering behind tab navigation bar - Uses theme.background.val for consistent background color * fix: add bottom padding using useSafeTabBarHeight to prevent tab bar overlap - Import and use useSafeTabBarHeight hook in AddPhoneScreen - Import and use useSafeTabBarHeight hook in VerifyPhoneScreen - Apply tabBarHeight as bottom padding to prevent buttons from being hidden - Ensures back/cancel buttons are visible above the tab bar * feat: integrate phone verification flow with AccountScreen using dynamic returnTo navigation - Add returnTo parameter support in AddPhoneScreen and VerifyPhoneScreen - Update AccountScreen to use AddPhone flow instead of EditAccountProperty - Update ProfileScreen to explicitly pass returnTo: 'Profile' - Enables context-aware navigation: Account->AddPhone->Verify->Account or Profile->AddPhone->Verify->Profile - Improves UX by returning users to the screen where they initiated the flow * fix: Remove yarn cache from Node.js setup to prevent Corepack conflicts - Remove 'cache: yarn' from setup-node step in all jobs - Corepack must be enabled before any yarn caching - Add 'Verify Yarn version' step to confirm correct version is active - Fixes Yarn version mismatch error (1.22.22 vs 3.6.4) This ensures Corepack activates Yarn 3.6.4 before Node.js tries to cache it. * fix: Remove --immutable flag from yarn install in CI - Change 'yarn install --immutable' to 'yarn install' in all jobs - The --immutable flag was causing lockfile modification errors - CI should allow lockfile updates when dependencies change - Fixes: 'The lockfile would have been modified by this install, which is explicitly forbidden' * chore: Update yarn.lock for Yarn 3.6.4 compatibility * fix: Disable postinstall scripts in Yarn 3 to prevent patch-package conflicts - Add 'enableScripts: false' to .yarnrc.yml - Yarn 3 has native patch support and conflicts with patch-package - This prevents the postinstall script from running and failing in CI - TODO: Migrate to Yarn 3 native patches in the future * fix: Enable inline build logs to see actual errors - Add YARN_ENABLE_INLINE_BUILDS=1 to global env - This will display build errors inline instead of hiding them in /tmp files - Will help us identify the actual cause of the workspace build failure * feat(FoodTruckScreen): replace vertical overlay list with horizontal scroll strip (#49) * feat(FoodTruckScreen): replace vertical list with horizontal scroll strip The previous implementation rendered available food trucks as a vertically stacked list inside a top overlay panel. This approach does not scale as more trucks are added — the panel grows downward, obscuring the map and degrading the user experience. This commit replaces the vertical list with a horizontally scrollable FlatList strip anchored to the bottom of the screen, above the tab bar. Each card in the strip displays the truck's vehicle avatar image above its display name (plate number), preserving the existing Pressable handler that animates the map to the truck's location and opens the Catalog screen. Changes: - Add FlatList (horizontal) and View from react-native - Add FastImage for performant, cached vehicle avatar rendering - Add useSafeAreaInsets (react-native-safe-area-context) and useSafeTabBarHeight hook to correctly position the strip above the tab bar on both iOS and Android - Remove the old availableFoodTrucks.map() vertical list rows from the top overlay panel; the panel now only contains the info banner and the current zone indicator - Add renderFoodTruckCard() — renders a fixed-width card (110px) with the vehicle avatar (64x64, FastImage, fallback to local van asset) above the truck display name (name ?? plate_number ?? i18n key) - Add bottom overlay View with pointerEvents='box-none' so touches in the empty space between cards fall through to the MapView - Add nestedScrollEnabled={true} on FlatList to prevent Android from swallowing the horizontal scroll gesture inside the MapView - Add StyleSheet entries: horizontalScrollContainer, horizontalScrollContent, truckCardImage - Remove unused faTruck icon import - Remove unused restoreFleetbasePlace and getCoordinatesObject imports * fix(FoodTruckScreen): move horizontal scroll inside top panel, fix avatar rendering The previous implementation incorrectly placed the horizontal food truck scroll as a separate absolute overlay at the bottom of the screen. This commit corrects the placement and rendering as intended: - The FlatList now lives inside the existing top overlay panel (YStack bg='$background' borderRadius='$5'), directly beneath the zone row — exactly where the old vertical list was. - Each chip is small (72px wide): the vehicle avatar image (44x44, no background fill — image sits flush on the panel surface) above the truck display name in a compact two-line label. - Avatar rendering is fixed: uses React Native's built-in Image component (no FastImage dependency required) sourcing foodTruck.vehicle.avatar_url directly from the serialized vehicle object, with the bundled light_commercial_van.png as fallback. - The zone row gains a conditional borderBottomWidth={1} only when trucks are present, so the divider appears cleanly between the zone row and the scroll strip. - Removed the bottom absolute overlay View and all associated useSafeTabBarHeight / useSafeAreaInsets positioning logic — no longer needed. - Removed FastImage import and dependency. - Restored faTruck import (used in VehicleMarker label). * fix(FoodTruckScreen): use Vehicle+getAttribute for avatar, FastImage for rendering Follow the exact pattern established in VehicleMarker and TrackingMarker: - Instantiate a Vehicle model: new Vehicle(foodTruck.vehicle, fleetbaseAdapter) - Resolve avatar via: vehicle.getAttribute('avatar_url') - Build avatarSource: avatarUrl ? { uri: avatarUrl } : require(fallback) - Render with FastImage + FastImage.resizeMode.contain, consistent with the non-SVG path in TrackingMarker (line 180) - Resolve display name via vehicle.getAttribute('plate_number') as the SDK fallback, matching how the rest of the screen accesses vehicle attributes through the model layer Remove the raw object property access (vehicle.avatar_url) and the plain React Native Image component that were incorrectly used before. * feat(VehicleAvatar): new component with full SVG/FastImage avatar rendering Creates src/components/VehicleAvatar.tsx — a dedicated component for rendering a vehicle's avatar image, extracted from the rendering logic already established in TrackingMarker. Implementation mirrors TrackingMarker exactly: - Accepts a Vehicle model instance and an optional size prop. - Resolves avatarSource via vehicle.getAttribute('avatar_url'), with the bundled light_commercial_van.png as fallback (same as VehicleMarker). - Uses the isRemoteSvg check (isObject + uri.endsWith('.svg')) to branch: SVG path → SvgCssUri + loading Spinner overlay (same as TM line 142-167) Raster path → FastImage + FastImage.resizeMode.contain (same as TM line 168-182) - Both paths wrap the image in a fixed-size View with alignItems/ justifyContent center, consistent with TrackingMarker's View wrappers. Updates FoodTruckScreen.tsx: - Removes the inline FastImage import and direct avatar rendering from renderFoodTruckChip. - Imports VehicleAvatar and uses <VehicleAvatar vehicle={vehicle} size={{ width: 44, height: 44 }} /> in each chip — clean, correct, and consistent with the rest of the vehicle rendering pipeline. * make `mt=$2` --------- Co-authored-by: Ronald A Richardson <ronald@fleetbase.io> Co-authored-by: Manus AI <manus@fleetbase.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the old vertical list of food trucks in
FoodTruckScreen.tsxwith a horizontally scrollable strip that lives inside the existing top overlay panel, directly beneath the "Your Zone" row — exactly where the original list was.Each item in the strip is a small chip: the vehicle avatar image sits directly on the panel surface with no background fill, and the truck's display name appears below it. The strip scrolls horizontally so the panel height stays fixed regardless of how many trucks are in the fleet, solving the core scalability problem.
What changed
src/screens/FoodTruckScreen.tsxPanel structure (the horizontal scroll is inside the panel, not at the bottom of the screen):
Avatar rendering fix:
The serialized vehicle object exposes
avatar_urlas a top-level property (consistent with howVehicleMarkeraccesses it viavehicle.getAttribute('avatar_url')). The previous version usedFastImagewhich introduced an unnecessary dependency. This version uses React Native's built-inImagecomponent with a conditional source —{ uri: vehicle.avatar_url }when present, falling back to the bundledassets/images/vehicles/light_commercial_van.png.Chip design:
resizeMode='contain'fontSize={11},fontWeight='500', max 2 lines, centredmarginRight: 12between chips, last chip has no trailing marginZone row divider:
borderBottomWidthon the zone row is now conditional —1when trucks are present (so the divider appears between the zone row and the scroll strip),0when the list is empty (clean bottom edge on the panel).Removed:
Viewand all associateduseSafeTabBarHeight/useSafeAreaInsetspositioning logicFastImageimportuseSafeTabBarHeightanduseSafeAreaInsetshooksTesting Checklist
avatar_urlis null/empty