diff --git a/FIX_ANDROID.md b/FIX_ANDROID.md deleted file mode 100644 index 39e8997..0000000 --- a/FIX_ANDROID.md +++ /dev/null @@ -1,69 +0,0 @@ -# To AI Agent: Implement Smart Runtime Environment Detection for Backend URL (Eliminate Dynamic .env Conflicts) - -## 1. Context & Objective -We want to modify the frontend backend-URL resolution engine **exactly once** so that pulling code to the Linux Debian Server (Docker build) or a Windows PC (Android build) requires zero manual `.env` updates. - -- **The Strategy:** Instead of hardcoding `VITE_BACKEND_URL` in a static `.env` file, we will write a smart runtime detector inside the code. -- **The Logic:** - - If the app runs on a standard web browser (Docker Server), `window.location.origin` automatically resolves to `https://yotrip.labz.io.vn`. - - If the app runs inside an Android APK wrapper, the origin defaults to `http://localhost`, `capacitor://`, or `file://`. The code will detect this and automatically inject the absolute server production URL. - ---- - -## 2. Refactoring Blueprint - -### Step 1: Overhaul `socketService.ts` (or your central API configuration file) -Replace the current environment variable lookup with this smart dynamic runtime resolver: - -```typescript -import { io, Socket } from 'socket.io-client'; - -const resolveBackendEndpoint = (): string => { - // 1. Keep a fallback for explicit env overrides if deliberately set - if (import.meta.env.VITE_BACKEND_URL) { - return import.meta.env.VITE_BACKEND_URL; - } - - const origin = window.location.origin; - const userAgent = navigator.userAgent.toLowerCase(); - - /* 2. DETECT ANDROID APK WRAPPER ENVIRONMENT - Android webviews/hybrid shells run on custom local hosts or file protocols - */ - const isAndroidApp = - origin.includes('localhost') || - origin.includes('capacitor://') || - origin.startsWith('file://') || - (userAgent.includes('android') && !origin.includes('yotrip.labz.io.vn')); - - if (isAndroidApp) { - console.log("📱 Android App environment detected. Forcing absolute production domain mapping."); - return '[https://yotrip.labz.io.vn](https://yotrip.labz.io.vn)'; - } - - // 3. Default fallback for Production Web (Docker server automatically yields its own domain) - return origin; -}; - -export const SOCKET_URL = resolveBackendEndpoint(); -console.log(`🌐 Active Network Base Endpoint: ${SOCKET_URL}`); - -### Step 2: Sync API Axios Client Configuration (api.ts / axiosClient.ts) -Ensure your API client hooks directly into the newly created SOCKET_URL variable to keep them unified: - -import axios from 'axios'; -import { SOCKET_URL } from './socketService'; - -export const api = axios.create({ - baseURL: SOCKET_URL, // Dynamically synchronizes across both Docker and Android build pipelines - headers: { - 'Content-Type': 'application/json', - }, -}); - -## 3. Verification Checklist for AI Agent - [ ] Eradicate .env Dependency: Ensure that clearing out VITE_BACKEND_URL from local .env files does not cause code compilation failures. - - [ ] Web Browser Verification: When deployed via Docker on the server, verify the network tab calls requests relatively to the running host origin. - - [ ] Android Simulation Success: When compiled into an APK on Windows, confirm all API/Socket requests direct straight to https://yotrip.labz.io.vn, avoiding any localhost 404 blockages. \ No newline at end of file diff --git a/GET_ANDROID.md b/GET_ANDROID.md new file mode 100644 index 0000000..fe2e412 --- /dev/null +++ b/GET_ANDROID.md @@ -0,0 +1,110 @@ +# To AI Agent: Restore Smart Android App Download Banner Below Top-Bar Header + +## 1. Context & Feature Objective +Previously, the codebase had a promotional banner encouraging mobile users to download the native Android `.apk` file. However, during the recent overhaul of the Top-Bar header and Member Dropdown Menu configurations, this banner component was accidentally unmounted or hidden. + +**Objective:** Restore and refactor this promotional frame (`AppDownloadBanner.tsx`). It must **ONLY** appear when a user accesses the web application via a **Mobile Android Browser** (Chrome, Samsung Internet, Opera Mobile, etc.). It must be positioned dynamically as a small, clean horizontal frame pinned directly underneath the main Top-Bar header layout, without conflicting with the new absolute Profile Dropdown Menu. + +--- + +## 2. Visual & Behavioral Layout Specifications +- **Placement Context:** Directly underneath the Top-Bar layer, shifting the core page content (Map Canvas or Landing Hero) down proportionally (`relative` or `sticky` stack). It must not overlay or block map navigation tools. +- **Conditional Trigger Logic:** The banner must evaluate `navigator.userAgent`. If the user agent includes `'android'` **AND** the user is running inside a standard web browser (not inside the compiled wrapper app itself), display the banner. +- **Dismissible Interaction:** Include a small close button (`X`). Clicking it should temporarily store a flag in `sessionStorage` or `localStorage` to prevent the banner from bugging the user repeatedly during their session. + +--- + +## 3. Technical Implementation Blueprint + +### Step 1: Create the Responsive Banner Component (`AppDownloadBanner.tsx`) +Create or re-engineer the banner layout within `frontend/src/components/layout/AppDownloadBanner.tsx`: + +```typescript +import React, { useState, useEffect } from 'react'; +import { X, Download } from 'lucide-react'; + +export const AppDownloadBanner: React.FC = () => { + const [isVisible, setIsVisible] = useState(false); + const APK_DOWNLOAD_URL = `${import.meta.env.VITE_BACKEND_URL || window.location.origin}/downloads/yotrip-latest.apk`; + + useEffect(() => { + const userAgent = navigator.userAgent.toLowerCase(); + const isAndroidBrowser = userAgent.includes('android') && !window.location.origin.includes('capacitor://') && !window.location.origin.includes('localhost:80'); + const isBannerDismissed = localStorage.getItem('yotrip_apk_banner_dismissed') === 'true'; + + // ✅ Target condition match: User is on Android mobile browser and hasn't closed it yet + if (isAndroidBrowser && !isBannerDismissed) { + setIsVisible(true); + } + }, []); + + const handleDismiss = () => { + localStorage.setItem('yotrip_apk_banner_dismissed', 'true'); + setIsVisible(false); + }; + + if (!isVisible) return null; + + return ( +
+ Trải nghiệm mượt mà hơn với ứng dụng YoTrip cho Android! +
++ Trải nghiệm mượt mà hơn với ứng dụng YoTrip cho Android! +
+