Skip to content

feat(FoodTruckScreen): replace vertical overlay list with horizontal scroll strip#49

Merged
roncodes merged 5 commits intodev-v0.0.19from
feature/food-truck-horizontal-scroll
Mar 23, 2026
Merged

feat(FoodTruckScreen): replace vertical overlay list with horizontal scroll strip#49
roncodes merged 5 commits intodev-v0.0.19from
feature/food-truck-horizontal-scroll

Conversation

@roncodes
Copy link
Member

@roncodes roncodes commented Mar 13, 2026

Summary

Replaces the old vertical list of food trucks in FoodTruckScreen.tsx with 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.tsx

Panel structure (the horizontal scroll is inside the panel, not at the bottom of the screen):

YStack bg='$background' borderRadius='$5'   ← existing panel
  ├── XStack  (info banner row)
  ├── Pressable > XStack  (zone row)
  └── FlatList horizontal  ← NEW: replaces the old vertical list
        └── renderFoodTruckChip()
              ├── Image (avatar, 44×44, no background)
              └── Text  (plate number / name)

Avatar rendering fix:
The serialized vehicle object exposes avatar_url as a top-level property (consistent with how VehicleMarker accesses it via vehicle.getAttribute('avatar_url')). The previous version used FastImage which introduced an unnecessary dependency. This version uses React Native's built-in Image component with a conditional source — { uri: vehicle.avatar_url } when present, falling back to the bundled assets/images/vehicles/light_commercial_van.png.

Chip design:

  • Width: 72px, no background fill on the image
  • Image: 44×44, resizeMode='contain'
  • Label: fontSize={11}, fontWeight='500', max 2 lines, centred
  • Pressed opacity: 0.65 for tactile feedback
  • marginRight: 12 between chips, last chip has no trailing margin

Zone row divider:
borderBottomWidth on the zone row is now conditional — 1 when trucks are present (so the divider appears between the zone row and the scroll strip), 0 when the list is empty (clean bottom edge on the panel).

Removed:

  • Bottom absolute overlay View and all associated useSafeTabBarHeight / useSafeAreaInsets positioning logic
  • FastImage import
  • useSafeTabBarHeight and useSafeAreaInsets hooks

Testing Checklist

  • Horizontal strip renders inside the top panel, below the zone row
  • Strip is not visible at the bottom of the screen
  • Vehicle avatar image renders correctly (no background fill on image)
  • Fallback van image renders when avatar_url is null/empty
  • Truck name appears below the icon
  • Tapping a chip animates map to truck location and opens Catalog
  • Panel height stays fixed as more trucks are added (horizontal scroll)
  • Zone row shows divider only when trucks are present
  • Dark mode and light mode both render correctly

roncodes and others added 5 commits March 13, 2026 06:14
…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 roncodes changed the base branch from main to dev-v0.0.19 March 23, 2026 09:31
@roncodes roncodes merged commit ac040ec into dev-v0.0.19 Mar 23, 2026
9 of 10 checks passed
@roncodes roncodes deleted the feature/food-truck-horizontal-scroll branch March 23, 2026 09:32
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant