6 Commits

150 changed files with 7784 additions and 708 deletions
+14
View File
@@ -0,0 +1,14 @@
DATABASE_URL="postgresql://postgres:password@localhost:5432/travel_db?schema=public"
PORT=3001
REDIS_URL=redis://localhost:6379
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=yotripadmin@gmail.com
SMTP_PASS="ahhh suif kfcp waie"
GOOGLE_CLIENT_ID=639044397050-7hair384u6lle941h033trut1s3q47l4.apps.googleusercontent.com
FRONTEND_URL=https://yotrip.labz.io.vn
ADMIN_SECRET_KEY=Yotrip@Bitabub1
# Development: Backend URL for vite dev server proxy (only used with npm run dev)
# For production: Nginx handles proxying, this is not used
VITE_BACKEND_URL=http://localhost:3001
-1
View File
@@ -1,4 +1,3 @@
.env
node_modules
server/dist
dist
+201
View File
@@ -0,0 +1,201 @@
# 🗺️ Hướng Dẫn Đóng Gói & Kiểm Thử Ứng Dụng Android (YoTrip)
Tài liệu này hướng dẫn chi tiết quy trình thiết lập môi trường máy tính Windows Local để biên dịch, kiểm thử dự án Frontend (React + Vite) qua Capacitor, kết nối tới hệ thống Docker Production (`https://yotrip.labz.io.vn`) và chuẩn bị phát hành lên Google Play Store.
---
## 📋 1. Điều Kiện Tiên Quyết (Môi Trường Windows)
Trước khi chạy lệnh, đảm bảo máy tính local đã cài đặt và cấu hình đầy đủ các công cụ sau:
* **Node.js:** Phiên bản v18 hoặc v20+.
* **Java JDK:** Phiên bản 17 hoặc 21 (Temurin hoặc Microsoft OpenJDK).
* Biến môi trường hệ thống: `JAVA_HOME` trỏ tới thư mục cài đặt JDK.
* Biến `Path` hệ thống: Bổ sung `%JAVA_HOME%\bin`.
* **Android Studio:** * Đã cài đặt **Android SDK**, **Android SDK Command-line Tools**.
* Biến môi trường hệ thống: `ANDROID_HOME` trỏ tới `AppData\Local\Android\Sdk`.
* Đã khởi tạo 1 thiết bị ảo (Android Simulator) qua *Virtual Device Manager*.
---
## ⚙️ 2. Cấu Hình Mã Nguồn Frontend (Local)
### 2.1 Cấu hình file `.env.production`
Tạo hoặc cập nhật file `.env.production` nằm tại thư mục gốc của `frontend/`:
```text
VITE_BACKEND_URL=[https://yotrip.labz.io.vn](https://yotrip.labz.io.vn)
### 2.2 Cấu hình Axios / API Instance (src/api/axios.ts)
Cập nhật logic baseURL để tự động phân tách môi trường chạy Web Dev (sử dụng Proxy của Vite) và môi trường chạy App Native (gọi trực tiếp URL tuyệt đối):
import axios from 'axios';
import { Capacitor } from '@capacitor/core';
const API = axios.create({
baseURL: Capacitor.isNativePlatform()
? import.meta.env.VITE_BACKEND_URL
: '',
timeout: 15000,
headers: {
'Content-Type': 'application/json',
},
});
export default API;
## 3. Cấu Hình Nền Tảng Android Native
### 3.1 Thiết lập Biểu tượng Ứng dụng (App Icon)
Để sử dụng frontend/public/favicon.ico làm icon của app trên Android, chúng ta cần chuyển đổi nó sang định dạng .png độ phân giải cao và sử dụng công cụ của Capacitor để tự động tạo các kích thước cần thiết cho Android.
Chuẩn bị ảnh: Chuyển đổi file favicon.ico của bạn thành file .png (khuyên dùng độ phân giải ít nhất 1024x1024 pixel để có chất lượng tốt nhất trên các thiết bị đời mới) và lưu tên là icon-only.png.
Cài đặt công cụ: Chạy lệnh sau tại thư mục frontend/ để cài đặt công cụ quản lý tài nguyên của Capacitor:
Bash
npm install @capacitor/assets --save-dev
Khởi tạo thư mục: Tạo thư mục assets ở thư mục gốc của frontend/ (cùng cấp với src) và đặt file icon-only.png vào đó.
Bash
mkdir assets
# Sau đó di chuyển file icon-only.png của bạn vào thư mục assets/
Tạo Icon: Chạy lệnh sau để tự động tạo và đặt các icon vào đúng vị trí trong dự án Android:
Bash
npx capacitor-assets generate --android
### 3.2 File capacitor.config.json
Định danh chính xác gói ứng dụng (App ID) dùng để đăng ký trên Google Play Console:
{
"appId": "com.yotrip.app",
"appName": "YoTrip",
"webDir": "dist",
"plugins": {
"SplashScreen": {
"launchShowDuration": 2000
}
}
}
### 3.3 File capacitor.config.json
Định danh chính xác gói ứng dụng (App ID) dùng để đăng ký trên Google Play Console:
JSON
{
"appId": "com.yotrip.app",
"appName": "YoTrip",
"webDir": "dist",
"plugins": {
"SplashScreen": {
"launchShowDuration": 2000
}
}
}
### 3.4 Cấu hình quyền trong AndroidManifest.xml
Mở đường dẫn android/app/src/main/AndroidManifest.xml, thêm các quyền truy cập Internet, định vị GPS, và các quyền cần thiết cho tính năng chụp ảnh và lưu ảnh vào bộ nhớ máy:
XML
<manifest xmlns:android="http://schemas.microsoft.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>
<application
...
android:usesCleartextTraffic="true"> ...
</application>
</manifest>
## 4. Tích Hợp Tính Năng Chụp Ảnh và Lưu Ảnh (React Code)
Sử dụng Plugin của Capacitor để tích hợp trực tiếp vào code React của bạn.
Cài đặt Plugin Camera: Chạy lệnh sau tại thư mục frontend/:
Bash
npm install @capacitor/camera
npx cap update
Ví dụ code: Dưới đây là cách implement tính năng chụp ảnh và tự động lưu ảnh gốc vào thư viện ảnh của điện thoại trong một React component (ví dụ src/components/PhotoTaker.tsx):
JavaScript
import React, { useState } from 'react';
import { IonButton, IonIcon, IonContent, IonPage } from '@ionic/react';
import { camera } from 'ionicons/icons';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
const PhotoTaker: React.FC = () => {
const [photoUri, setPhotoUri] = useState<string | undefined>();
const takeAndSavePhoto = async () => {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: false, // Giữ nguyên ảnh gốc, không qua chỉnh sửa
resultType: CameraResultType.Uri,
source: CameraSource.Camera, // Mở camera trực tiếp
saveToGallery: true, // YÊU CẦU MỚI: Tự động lưu ảnh gốc vào thư viện điện thoại
});
// Bạn có thể sử dụng image.webPath để hiển thị xem trước
setPhotoUri(image.webPath);
console.log('Ảnh đã được chụp và lưu tại:', image.path);
} catch (error) {
console.error('Lỗi khi chụp hoặc lưu ảnh:', error);
}
};
return (
<IonPage>
<IonContent className="ion-padding">
<div className="flex flex-col items-center gap-4">
<h1 className="text-xl font-bold">Tính năng Chụp ảnh</h1>
<IonButton onClick={takeAndSavePhoto} color="primary">
<IonIcon slot="start" icon={camera}></IonIcon>
Chụp và Lưu Ảnh Gốc
</IonButton>
{photoUri && (
<div className="mt-4 border p-2">
<p>Xem trước ảnh vừa chụp:</p>
<img src={photoUri} alt="Xem trước ảnh chụp" className="max-w-xs mt-2" />
</div>
)}
</div>
</IonContent>
</IonPage>
);
};
export default PhotoTaker;
## 5. Quy Trình Biên Dịch & Kiểm Thử (Simulator)
Mỗi lần cập nhật code giao diện ở máy local, chạy chuỗi lệnh sau tại Terminal của VS Code để đẩy app lên máy ảo:
# Bước 1: Cài đặt các thư viện phụ thuộc tại local
npm install
# Bước 2: Build code React + Vite thành file tĩnh
npm run build
# Bước 3: Đồng bộ mã nguồn tĩnh vào thư mục Android mã nguồn mở
npx cap sync
# Bước 4: Khởi chạy máy ảo và nạp ứng dụng tự động
npx cap run android
-104
View File
@@ -1,104 +0,0 @@
# To AI Agent: Fix Infinite Re-render Loop and Flickering "ĐANG TÌM ĐƯỜNG TỐI ƯU..." Label
## 1. Context & Layout Bug Analysis
We are resolving a critical performance and UI bug inside the Map routing engine interface (`image.png`):
- **The Issue:** The floating loading indicator badge reading `"ĐANG TÌM ĐƯỜNG TỐI ƯU..."` keeps flashing, flickering, or appearing and disappearing in an infinite execution loop.
- **Root Cause:** This is caused by a broken React lifecycle loop. Every time the map triggers a directions route query, it toggles a loading state variable (`isSearching: true`). Once the route loads, the state updates (`isSearching: false`), forcing a component re-render. If the coordinates object (`origin`, `destination`) or the map instance ref inside the `useEffect` dependency array changes its reference pointer on every render, the hook fires *again*, creating an endless loop of API fetching and component flashing.
---
## 2. Technical Execution Strategy
To terminate this flickering cycle, we must enforce a strict guard rail on the network/calculation trigger pipeline:
1. **Coordinate Reference Stability:** Deconstruct the input latitude/longitude objects into raw primitive string values (e.g., `origin.lat`, `origin.lng`) inside the hook dependency array to avoid object reference mutations triggering re-renders.
2. **Locking Ref Mechanism:** Implement a mutable React tracking reference (`const queryInProgress = useRef(false)`) to lock the execution window. If a fetch operation is active, block subsequent duplicate queries from firing.
3. **Clean Loading State Termination:** Turn off the search state flag explicitly only *after* the route geometry polyline has completely finished rendering onto the map viewport layout.
---
## 3. Code Refactoring Blueprint
Locate your map navigation layer or modal component (e.g., `LocationNavigationModal.tsx`) and refactor the execution lifecycle loop as defined below:
```typescript
import React, { useEffect, useState, useRef } from 'react';
// Inside your Map Navigation Modal / Layer component wrapper:
export const LocationNavigationModal = ({ isOpen, routeData }) => {
const [isSearchingRoute, setIsSearchingRoute] = useState(false);
const mapInstanceRef = useRef<any>(null);
// CRITICAL FIX 2: Guard mechanism to block concurrent duplicated queries
const fetchLockRef = useRef(false);
// Deconstruct coordinate primitives to secure a stable dependency array
const originLat = routeData?.origin?.lat;
const originLng = routeData?.origin?.lng;
const destLat = routeData?.destination?.lat;
const destLng = routeData?.destination?.lng;
useEffect(() => {
if (!isOpen || !originLat || !originLng || !destLat || !destLng) return;
// If an operation is already locked and active, bail out immediately to prevent loops
if (fetchLockRef.current) return;
const calculateOptimalRoute = async () => {
try {
// 1. Activate loading feedback banner
setIsSearchingRoute(true);
fetchLockRef.current = true; // Engage execution lock
console.log("Fetching route coordinates exactly once...");
// --- YOUR MAP COMPONENT ROUTING LOGIC START ---
// Example: const response = await directionsService.route({...});
// await mapInstanceRef.current.drawPolyline(response);
// --- YOUR MAP COMPONENT ROUTING LOGIC END ---
} catch (error) {
console.error("Failed to compile route optimization maps:", error);
} finally {
// 2. Safe, definitive termination of the tracking states
setIsSearchingRoute(false);
fetchLockRef.current = false; // Disengage execution lock
}
};
calculateOptimalRoute();
// Cleanup phase: Reset execution parameters when inputs dismantle or modal closes
return () => {
fetchLockRef.current = false;
setIsSearchingRoute(false);
};
/* CRITICAL FIX 1: Explicitly tracking primitives only.
Do NOT pass full 'routeData', 'mapInstanceRef' or object literals here!
*/
}, [isOpen, originLat, originLng, destLat, destLng]);
return (
<div className="relative w-full h-full">
{/* Map Content Target Canvas */}
<div id="navigation-viewport-map-canvas" className="w-full h-full" />
{/* RENDER CONTROLLER: Only mount the label if routing calculations are actively processing */}
{isSearchingRoute && (
<div className="absolute top-16 left-4 z-30 bg-white/95 dark:bg-slate-900/95 border border-slate-200 dark:border-slate-800 px-3 py-1.5 rounded-full shadow-lg flex items-center gap-2 animate-pulse">
{/* Circular Loading Spinner Element */}
<div className="w-3.5 h-3.5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
<span className="text-[11px] font-bold text-slate-700 dark:text-slate-200 uppercase tracking-wider">
ĐANG TÌM ĐƯỜNG TỐI ƯU...
</span>
</div>
)}
</div>
);
};
## 4. Verification & Quality Acceptance Criteria
[ ] Single Instance Trigger: Check console telemetry outputs. When the map modal mounts, the route compilation routine must print its trace log exactly once.
[ ] Flicker Nullification: The "ĐANG TÌM ĐƯỜNG TỐI ƯU..." badge must display smoothly with an animation pulse. It must not shake, blink, flash, or rapid-cycle on and off.
[ ] Deterministic Hiding: As soon as the blue route line draws completely across the map terrain grid layout, the loading badge must cleanly unmount and disappear from view without reappearing unless a new destination node button is clicked.
+83
View File
@@ -0,0 +1,83 @@
# To AI Agent: Global Mobile Viewport Optimization Plan for `frontend/src/pages`
## 1. Context & Architectural Objective
We are launching a comprehensive mobile responsive refactoring across all core application pages inside `frontend/src/pages/`. Currently, several layouts suffer from desktop-first design assumptions, leading to sideways horizontal scrolling, squished sidebars, text wrapping collisions, and clipped viewports on mobile browsers.
**Objective:** Inspect and refactor all page-level layout components to guarantee an impeccable, fluid mobile UX (screen widths under 640px) while preserving the current widescreen layout using Tailwind CSS responsive breakpoints (`sm:`, `md:`, `lg:`).
---
## 2. Core Mobile-Responsive Design Rules for Pages
When auditing and refactoring page containers, strictly enforce these implementation guardrails:
1. **Fluid Heights over Sticky Viewports:** Avoid hardcoding page heights to `h-screen`. On mobile browsers, the address bar dynamically expands and collapses, causing layout jumps. Use **`h-auto`** or the new dynamic viewport utilities **`h-dvh`** / **`min-h-dvh`** instead.
2. **Horizontal Overflow Elimination:** Ensure the root wrapper of every page enforces `w-full overflow-x-hidden`. Any element causing a horizontal scrollbar must be converted to a flex-wrap, horizontal scroll grid, or dynamic stack.
3. **Flex/Grid Stacking:** Multi-column dashboard layouts must stack vertically on mobile and separate into side-by-side structures on desktop:
- Use `flex flex-col md:flex-row`
- Use `grid grid-cols-1 md:grid-cols-3`
4. **Touch Target & Spacing Downscaling:** Mobile views require higher breathing margins but smaller typography. Reduce text sizes (`text-base``text-xs/sm`) and scale down paddings (`p-6``p-3/4`) on mobile screens.
---
## 3. Targeted Page-by-Page Refactoring Guide
### 3.1. `MemberDashboard.tsx` (Trang chính / Danh sách Tour)
- **The Issue:** The grid grid-cols-2 or grid-cols-3 arrangement squishes Tour Card components on small viewports.
- **Refactor Spec:** - Change main wrapper grid to `grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4`.
- Force individual Tour Cards to occupy 100% width on mobile, stacking all actions ("Trò chuyện", "Chi tiết hành trình") into standard full-width rows or equal flex pairs (`flex-1`).
### 3.2. `ItineraryTimeline.tsx` (Trang quản lý Lộ trình Chi tiết)
- **The Issue:** The sub-navigation tabs ribbon ("Lộ trình, Chi phí, Ảnh...") gets compressed, causing word overlapping. Timeline lines and node circles clip when left padding is too wide.
- **Refactor Spec:**
- Convert the sub-navigation menu container into a smooth horizontally scrollable ribbon on mobile:
```jsx
className="flex items-center gap-2 overflow-x-auto whitespace-nowrap scrollbar-none pb-2 md:overflow-x-visible md:whitespace-normal"
```
- Reduce the absolute left offset tracking of the timeline vertical axis indicator from `left-[32px]` down to a safe margin fitting tight spaces.
### 3.3. `PhotoGallery.tsx` / `GalleryPage.tsx` (Thư viện ảnh Tour)
- **The Issue:** Widescreen image matrices cause layout bleeding or weird masonry columns.
- **Refactor Spec:**
- Force image grid wrappers to adopt `grid-cols-2` or `grid-cols-3` on mobile browsers instead of desktop 4-5 structures.
- Ensure the modal lightboxes or floating overlays use full-width settings (`w-screen h-screen`) with zero perimeter radius limits.
---
## 4. Code Refactoring Reference Standard
### Layout Component Conversion Pattern:
Apply this fluid adaptation standard on your root page return templates:
```jsx
{/* ❌ BEFORE: RIGID DESKTOP-FIRST PAGE WRAPPER */}
<div className="w-screen h-screen bg-slate-950 flex p-6 gap-6">
<aside className="w-64 bg-slate-900">Sidebar</aside>
<main className="flex-1 overflow-y-auto">Main Dashboard Content</main>
</div>
{/* ✅ AFTER: MOBILE-FIRST FULLY RESPONSIVE LAYOUT SHEET */}
<div className="w-full min-h-dvh bg-slate-950 flex flex-col md:flex-row p-3 sm:p-6 gap-4 sm:gap-6 overflow-x-hidden">
{/* Sticky Navigation or Drawer Menu on Mobile, Fixed Sidebar on Desktop */}
<aside className="w-full md:w-64 shrink-0 bg-slate-900 rounded-xl p-4 md:sticky md:top-6 md:h-[calc(100vh-3rem)]">
Sidebar/Menu Content
</aside>
{/* Main Scrollable Core Content Space */}
<main className="w-full flex-1 overflow-y-visible md:overflow-y-auto">
<div className="space-y-4 max-w-7xl mx-auto">
{/* Grid elements stack on mobile (1 col) and expand on desktop */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{/* Card components populate here */}
</div>
</div>
</main>
</div>
## 5. Automated Verification Checklist for AI Agent
[ ] Zero Pixel Width Hardcodes: Scan all code blocks in frontend/src/pages/. Ensure no outer boundaries utilize structural fixed layouts like w-[1200px] or w-[800px] without a breakpoint utility prefix (e.g., lg:w-[1200px]).
[ ] Viewport Axis Lock: Simulate viewport checks at 360px, 390px, and 412px widths. Verify that horizontal browser layout shifting is fully neutralized (window.scrollX === 0).
[ ] Dynamic Viewport Heights Verification: Confirm that full-page dashboards replace static h-screen classes with h-auto or dynamic min-h-dvh settings to avoid layout bugs when the mobile address bar shifts.
+518
View File
@@ -0,0 +1,518 @@
# Plan: Build ứng dụng Android 11+ từ Codebase hiện tại
## Tổng quan
Codebase hiện tại là một **web app React + Vite** (frontend) và **NestJS** (backend) theo mô hình monorepo. Chiến lược được đề xuất là dùng **Capacitor.js** để đóng gói web app thành native Android APK/AAB mà **không cần viết lại code**, đồng thời bổ sung các tính năng native (camera, GPS, notifications...) qua Capacitor plugins.
---
## So sánh các lựa chọn
| Phương pháp | Ưu điểm | Nhược điểm | Phù hợp? |
|---|---|---|---|
| **Capacitor.js** | Tái sử dụng 100% React code, hỗ trợ Vite, ít học thêm | Cần build native mỗi lần release | ✅ **Phù hợp nhất** |
| React Native | Performance tốt hơn, native feel | Phải viết lại toàn bộ UI/components | ❌ Tốn quá nhiều công |
| PWA (Add to Home Screen) | Không cần build APK | Bị giới hạn API trình duyệt, không lên Google Play | ❌ Không phù hợp |
| Cordova/PhoneGap | Tương tự Capacitor | Cũ hơn, ít được duy trì | ❌ Không nên dùng |
**→ Quyết định: Sử dụng [Capacitor.js](https://capacitorjs.com/)** (do Ionic team phát triển), hỗ trợ Android API 30+ (Android 11).
---
## Kiến trúc triển khai
```
┌─────────────────────────────────────────────────────┐
│ Android APK / AAB │
│ ┌─────────────────────────────────────────────┐ │
│ │ Capacitor WebView │ │
│ │ (Chứa toàn bộ frontend React/Vite) │ │
│ └──────────────────┬──────────────────────────┘ │
│ │ HTTPS API calls │
└─────────────────────┼───────────────────────────────┘
┌────────────────────────┐
│ NestJS Backend │
│ (Deployed server / │
│ localhost dev) │
└────────────────────────┘
```
---
## Thông tin đã xác nhận
| Hạng mục | Quyết định |
|---|---|
| **Tên app** | YoTrip |
| **App ID** | `com.yotrip.app` |
| **Phát hành** | Google Play Store |
| **Camera** | Native (chụp ảnh trực tiếp từ app) |
| **Backend** | Docker trên Debian Homelab, proxy qua Nginx |
| **Domain** | `yotrip.labz.io.vn` |
| **DNS động** | Cloudflare DDNS |
---
## Điều kiện tiên quyết
> [!IMPORTANT]
> Trước khi thực hiện, cần chuẩn bị:
> 1. **Java JDK 17+** — Android build tool yêu cầu
> 2. **Android Studio** — để build APK, quản lý Android SDK và tạo/chạy Emulator
> 3. **Android SDK** với API Level 30+ (Android 11 / API 30)
> 4. **Android Virtual Device (AVD)** — tạo trong Android Studio AVD Manager
> 5. **Domain + HTTPS cho Homelab** — bắt buộc cho Google Play (xem Phần Bên dưới)
> [!WARNING]
> **Google Play bắt buộc HTTPS** — Android 9+ chặn HTTP rõ ràng mặc định. Homelab phải có SSL certificate hợp lệ (Let's Encrypt qua Nginx) và domain name trỏ vào homelab server.
> [!NOTE]
> Trên emulator, `localhost` của **emulator** khác với `localhost` của máy tính. Phải dùng địa chỉ đặc biệt `10.0.2.2` thay thế khi test emulator (xem Phase 5b).
---
## Proposed Changes
### Phase 1a: Cấu hình Backend Homelab (Docker + Nginx)
---
> [!IMPORTANT]
> Đây là điều kiện tiên quyết của toàn bộ kế hoạch. App không thể gửi lên Google Play nếu API chưa có HTTPS.
Homelab của bạn chạy Docker + Nginx là **đủ điều kiện kết nối**, nhưng cần đảm bảo:
#### Checklist Homelab/Nginx
| Yêu cầu | Mô tả |
|---|---|
| **Domain name** | `yotrip.labz.io.vn` — đã xác nhận |
| **Port forwarding** | Router cài đặt forward port 80 và 443 vào Nginx server |
| **SSL Certificate** | Let's Encrypt (miễn phí) qua Certbot: `certbot --nginx -d yotrip.labz.io.vn` |
| **Nginx reverse proxy** | Forward HTTPS → NestJS container port 3001 |
| **Docker Compose** | Backend container luôn restart khi Debian reboot |
| **CORS** | Backend phải cho phép origin từ app Capacitor (có thể mở rộng `*` ban đầu) |
| **IP động** | Dùng **Cloudflare DDNS** để giữ domain `yotrip.labz.io.vn` luôn trỏ đúng IP |
#### Mẫu cấu hình Nginx reverse proxy
```nginx
# /etc/nginx/sites-available/yotrip-api
server {
listen 443 ssl;
server_name yotrip.labz.io.vn;
ssl_certificate /etc/letsencrypt/live/yotrip.labz.io.vn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yotrip.labz.io.vn/privkey.pem;
location / {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
# Cần thiết cho WebSocket (Socket.IO)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
# Redirect HTTP sang HTTPS
server {
listen 80;
server_name yotrip.labz.io.vn;
return 301 https://$host$request_uri;
}
```
#### File cấu hình môi trường sẽ dùng
```env
# frontend/.env.production
VITE_API_BASE_URL=https://yotrip.labz.io.vn
# frontend/.env.emulator (test local)
VITE_API_BASE_URL=http://10.0.2.2:3001
```
---
### Phase 1b: Tập trung API URL trong Frontend
#### [MODIFY] [vite.config.ts](file:///home/locpham/travelplanning/frontend/vite.config.ts)
- Dev proxy hiện tại trỏ về `http://localhost:3001` — chỉ hoạt động khi chạy trên browser máy tính.
- Tạo biến môi trường `VITE_API_BASE_URL` để frontend biết trỏ đến đâu.
#### [NEW] `.env.production` trong `frontend/`
```env
VITE_API_BASE_URL=https://your-backend-server.com
```
#### [NEW] `.env.development` trong `frontend/`
```env
# Dùng ngrok hoặc IP máy tính cho mobile dev
VITE_API_BASE_URL=http://192.168.x.x:3001
```
#### [MODIFY] Toàn bộ các file gọi `/api/v1/...`
- Thay `fetch('/api/v1/...')` bằng `fetch(\`${import.meta.env.VITE_API_BASE_URL}/api/v1/...\`)`
- **Ưu tiên**: Tạo một file `src/lib/api.ts` (helper) để tập trung URL, tránh sửa từng file.
```typescript
// src/lib/api.ts
export const API_BASE = import.meta.env.VITE_API_BASE_URL || '';
export const apiFetch = (path: string, options?: RequestInit) =>
fetch(`${API_BASE}${path}`, options);
```
---
### Phase 2: Tích hợp Capacitor
---
#### Cài đặt Capacitor vào frontend
```bash
# Trong thư mục frontend/
npm install @capacitor/core @capacitor/cli
npx cap init "YoTrip" "com.yotrip.app" --web-dir dist
npm install @capacitor/android
npx cap add android
```
#### [NEW] `frontend/capacitor.config.ts`
```typescript
import { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.yotrip.app', // ✅ App ID chính thức
appName: 'YoTrip', // ✅ Tên app
webDir: 'dist',
server: {
// Production: không cần cấu hình, dùng VITE_API_BASE_URL trong build
// Dev/Emulator: uncomment dòng dưới
// url: 'http://10.0.2.2:3002',
// cleartext: true,
},
android: {
minSdkVersion: 30, // Android 11 (API 30)
targetSdkVersion: 34, // Android 14
buildOptions: {
keystorePath: 'release-key.keystore',
keystoreAlias: 'yotrip',
}
},
plugins: {
SplashScreen: {
launchAutoHide: false,
backgroundColor: '#0f172a',
androidSplashResourceName: 'splash',
},
// Camera plugin config (bắt buộc vì app cần chụp ảnh)
Camera: {
presentationStyle: 'fullscreen',
}
}
};
export default config;
```
---
### Phase 3: Android Project Setup
---
#### Build flow
```bash
# Bước 1: Build React app thành static files
npm run build -w frontend
# Bước 2: Copy static files vào Android project
npx cap copy android
# Bước 3: Sync plugins và dependencies
npx cap sync android
# Bước 4: Mở Android Studio để build APK/AAB
npx cap open android
```
#### [MODIFY] `android/app/src/main/AndroidManifest.xml` (tự sinh bởi Capacitor)
Thêm các permissions cần thiết:
```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Android 11+ scoped storage -->
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
```
---
### Phase 4: Native Plugins (**Bắt buộc**)
| Plugin | Mức độ | Mục đích | Package |
|---|---|---|---|
| `@capacitor/camera` | ✅ **Bắt buộc** | Chụp ảnh trực tiếp từ camera + gallery | `@capacitor/camera` |
| `@capacitor/geolocation` | ✅ **Bắt buộc** | GPS thiết bị cho bản đồ | `@capacitor/geolocation` |
| `@capacitor/splash-screen` | ✅ **Bắt buộc** | Màn hình khởi động | `@capacitor/splash-screen` |
| `@capacitor/status-bar` | ✅ **Bắt buộc** | Màu status bar đồng bộ UI | `@capacitor/status-bar` |
| `@capacitor/push-notifications` | 🔲 Tùy chọn | Thông báo bình luận, tour | `@capacitor/push-notifications` |
| `@capacitor/network` | 🔲 Tùy chọn | Kiểm tra kết nối mạng | `@capacitor/network` |
#### Cách dùng Camera plugin trong code (thay thế input file)
```typescript
// Thay thế <input type="file"> bằng Capacitor Camera API
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
const takePhoto = async () => {
const photo = await Camera.getPhoto({
resultType: CameraResultType.DataUrl, // Hoặc Uri cho hiệu năng tốt hơn
source: CameraSource.Prompt, // Hỏi: Camera hay Gallery?
quality: 85,
});
// photo.dataUrl → upload lên backend
};
```
---
### Phase 5: Điều chỉnh UI cho Mobile
Một số thành phần hiện tại đã có responsive CSS, nhưng cần kiểm tra thêm:
- **`PublicPhotoModal.tsx`**: Đã có kế hoạch fix mobile layout (từ plan cũ), cần implement trước khi build.
- **`ExploreMap.tsx`**: Leaflet map cần đảm bảo touch events hoạt động (thường OK trên mobile).
- **`TourDetailPage.tsx`**: Kiểm tra scroll behavior, fixed headers.
- **Safe Area Insets**: Dùng `env(safe-area-inset-*)` cho các thiết bị có notch/dynamic island.
```css
/* Thêm vào index.css */
:root {
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
}
```
---
### Phase 5b: Test trên Android Emulator (AVD)
> [!IMPORTANT]
> Đây là bước bắt buộc trước khi test trên thiết bị thật. Emulator giúp phát hiện lỗi layout, API call, và native permissions mà không cần thiết bị vật lý.
#### Tạo Android Virtual Device (AVD)
1. Mở **Android Studio → Tools → Device Manager → Create Device**
2. Chọn **Pixel 6** (hoặc tương đương) → chọn hệ thống **Android 11.0 (API 30)**
3. Cấp RAM 2GB+, Storage 4GB+ cho emulator
4. Khởi động emulator, đảm bảo hiện **"Emulator is running"**
#### Địa chỉ đặc biệt trong Emulator
```
10.0.2.2 → Trỏ đến localhost (127.0.0.1) của máy tính host
```
Khi chạy trong emulator, backend ở `localhost:3001` của máy tính phải được gọi bằng `10.0.2.2:3001`.
#### Cấu hình `capacitor.config.ts` cho Emulator Dev
```typescript
// Tạm thời uncomment khi test trên emulator
server: {
url: 'http://10.0.2.2:3002', // Trỏ đến Vite dev server trên máy host
cleartext: true, // Cho phép HTTP (không dùng trong production)
},
```
Hoặc dùng biến môi trường `.env.emulator`:
```env
VITE_API_BASE_URL=http://10.0.2.2:3001
```
#### Chạy app trên Emulator
```bash
# Bước 1: Đảm bảo emulator đang chạy
adb devices
# Phải thấy: emulator-5554 device
# Bước 2: Build & sync
npm run build -w frontend
npx cap copy android && npx cap sync android
# Bước 3: Chạy trực tiếp trên emulator
npx cap run android
# hoặc trong Android Studio: Run ▶ chọn emulator
```
#### Checklist kiểm tra trên Emulator
| Chức năng | Test case | Kết quả mong đợi |
|---|---|---|
| Khởi động | Mở app | Splash screen → Landing page |
| Đăng nhập | Nhập email/password | Vào ExploreMap |
| Bản đồ | Zoom/pan trên emulator | Leaflet map hoạt động |
| Upload ảnh | Chọn ảnh từ gallery giả lập | Ảnh được upload thành công |
| Bình luận | Nhập & gửi bình luận | Hiện real-time qua WebSocket |
| Safe area | Xoay màn hình | Layout không bị che |
| Responsive | Portrait/Landscape | UI không bị vỡ |
---
### Phase 6: Test trên thiết bị Android thật
> [!NOTE]
> Chỉ tiến hành Phase này sau khi toàn bộ Phase 5b đã pass. Thiết bị thật giúp phát hiện lỗi về hiệu năng, cảm biến thực tế, và hành vi network.
#### Kết nối thiết bị
```bash
# Bật Developer Mode + USB Debugging trên điện thoại
# Cắm cáp USB → xác nhận "Allow USB Debugging"
adb devices
# Phải thấy: <serial_number> device
```
#### Cấu hình backend cho thiết bị thật
Thiết bị thật phải dùng IP LAN hoặc ngrok (không thể dùng `10.0.2.2`):
```bash
# Tùy chọn A: Dùng IP LAN (điện thoại và máy tính cùng WiFi)
VITE_API_BASE_URL=http://192.168.x.x:3001
# Tùy chọn B: Dùng ngrok (tiện hơn, không cần cùng mạng)
ngrok http 3001
# → VITE_API_BASE_URL=https://xxxx.ngrok-free.app
```
#### Cài debug APK lên thiết bị thật
```bash
# Build debug APK
cd frontend/android && ./gradlew assembleDebug
# Cài APK lên thiết bị
adb install app/build/outputs/apk/debug/app-debug.apk
```
---
### Phase 7: Ký APK và phát hành
#### Debug build (dev testing)
```bash
cd android && ./gradlew assembleDebug
# Output: android/app/build/outputs/apk/debug/app-debug.apk
```
#### Release build (production)
```bash
# Tạo keystore lần đầu
keytool -genkey -v -keystore release-key.keystore -alias yotrip \
-keyalg RSA -keysize 2048 -validity 10000
# Build release
cd android && ./gradlew bundleRelease
# Output: .aab file để upload Google Play
```
---
## Checklist thực hiện
```
Phase 1 - Backend URL
[ ] Tạo file src/lib/api.ts (centralised fetch helper)
[ ] Refactor tất cả fetch('/api/v1/...) sang apiFetch()
[ ] Tạo .env.production với VITE_API_BASE_URL
[ ] Tạo .env.emulator với VITE_API_BASE_URL=http://10.0.2.2:3001
Phase 2 - Capacitor Setup
[ ] npm install @capacitor/core @capacitor/cli @capacitor/android
[ ] npx cap init với App ID và tên app
[ ] Tạo capacitor.config.ts
[ ] npx cap add android
Phase 3 - Build & Sync
[ ] npm run build -w frontend (kiểm tra không có lỗi TypeScript)
[ ] npx cap copy android && npx cap sync android
[ ] Cấu hình AndroidManifest.xml với permissions
Phase 4 - Native Plugins
[ ] Cài @capacitor/geolocation (thay navigator.geolocation)
[ ] Cài @capacitor/camera (upload ảnh từ điện thoại)
[ ] Cài @capacitor/splash-screen, @capacitor/status-bar
Phase 5 - Mobile UI Polish
[ ] Implement mobile layout fixes cho PublicPhotoModal.tsx
[ ] Kiểm tra safe-area-inset cho Android
Phase 5b - Test trên Android Emulator (AVD)
[ ] Tạo AVD Android 11 (API 30) trong Android Studio
[ ] Cấu hình capacitor.config.ts server.url = http://10.0.2.2:3002
[ ] Khởi động emulator → adb devices xác nhận kết nối
[ ] npx cap run android → kiểm tra app khởi động
[ ] Kiểm tra toàn bộ checklist: Login, Map, Upload, Comment, SafeArea
[ ] Sửa tất cả lỗi phát sinh trên emulator
Phase 6 - Test trên thiết bị Android 11 thật
[ ] Bật Developer Mode + USB Debugging trên điện thoại
[ ] adb devices xác nhận thiết bị kết nối
[ ] Cấu hình VITE_API_BASE_URL = IP LAN hoặc ngrok
[ ] Build debug APK → adb install
[ ] Kiểm tra lại toàn bộ checklist trên thiết bị thật
[ ] Kiểm tra performance, pin, cảm biến GPS thực tế
Phase 7 - Build Release
[ ] Build debug APK để test
[ ] Tạo keystore và build release AAB
[ ] Chuẩn bị lên Google Play (nếu cần)
```
---
## Verification Plan
### Automated
- `npm run build -w frontend` — không có lỗi TypeScript/build
- `adb devices` — xác nhận emulator/thiết bị thật đang kết nối
### Stage 1: Test trên Android Emulator
1. App khởi động không crash, hiện Landing page đúng.
2. Đăng nhập / Đăng ký hoạt động (kết nối `10.0.2.2:3001`).
3. Bản đồ Leaflet zoom/pan bằng cảm ứng mô phỏng.
4. Upload ảnh từ gallery giả lập của emulator.
5. Bình luận real-time qua WebSocket hoạt động.
6. Layout không bị vỡ khi xoay màn hình (portrait/landscape).
7. Safe-area-inset không bị che bởi status bar.
### Stage 2: Test trên thiết bị Android 11 thật
1. Lặp lại toàn bộ Stage 1 trên thiết bị thật.
2. GPS thực tế hoạt động và hiện đúng vị trí trên bản đồ.
3. Camera native chụp và upload ảnh thành công.
4. Performance mượt mà (scroll, animation không giật).
5. WebSocket giữ kết nối ổn định trên mobile network (4G/WiFi).
6. Kiểm tra pin consumption không bất thường.
---
## Các quyết định đã xác nhận
| Câu hỏi | Trả lời |
|---|---|
| Backend deploy ở đâu? | Docker trên Debian homelab, proxy qua Nginx |
| Tên app và App ID? | `YoTrip``com.yotrip.app` |
| Domain? | `yotrip.labz.io.vn` (Cloudflare DDNS) |
| Phát hành? | Đưa lên **Google Play Store** |
| Camera? | **Native camera** — chụp ảnh trực tiếp từ app |
> [!NOTE]
> **Lưu ý quan trọng về Homelab + Google Play:**
> - Homelab + Docker + Nginx là **đủ điều kiện kết nối** cho app Android.
> - Domain `yotrip.labz.io.vn` dùng **Cloudflare DDNS** — IP homelab thay đổi sẽ được cập nhật tự động.
> - **WebSocket (Socket.IO)** cần Nginx được cấu hình `proxy_set_header Upgrade` (xem Phase 1a).
> - Certbot cấp SSL cho `yotrip.labz.io.vn`: `certbot --nginx -d yotrip.labz.io.vn`

Before

Width:  |  Height:  |  Size: 868 KiB

After

Width:  |  Height:  |  Size: 868 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 KiB

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Before

Width:  |  Height:  |  Size: 844 KiB

After

Width:  |  Height:  |  Size: 844 KiB

Before

Width:  |  Height:  |  Size: 408 KiB

After

Width:  |  Height:  |  Size: 408 KiB

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

@@ -0,0 +1 @@
{"web":{"client_id":"639044397050-7hair384u6lle941h033trut1s3q47l4.apps.googleusercontent.com","project_id":"yotrip-web","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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"}}
+1 -1
View File
@@ -31,7 +31,7 @@ services:
ports:
- "3001:3001"
volumes:
- ./backend/uploads:/usr/src/app/uploads
- /mnt/storage/yotrip/uploads:/usr/src/app/uploads
environment:
DATABASE_URL: "postgresql://postgres:password@postgres:5432/travel_db?schema=public"
REDIS_URL: "redis://redis:6379"
+87
View File
@@ -0,0 +1,87 @@
# Kế hoạch triển khai: Cấu hình Docker để Chạy Server & Phát triển (Hot-reload)
Kế hoạch này phác thảo cách cấu hình Docker và Docker Compose cho dự án YoTrip. Cấu hình này sẽ đáp ứng đồng thời hai nhu cầu:
1. **Môi trường Phát triển (Development)**: Đồng bộ mã nguồn trực tiếp (bind mounts) từ máy local vào container, hỗ trợ hot-reload cho cả backend (NestJS watch) và frontend (Vite HMR).
2. **Môi trường Triển khai (Production)**: Đóng gói tối ưu thành các image độc lập, sử dụng Nginx để phục vụ frontend tĩnh và tối ưu hóa hiệu năng NestJS backend.
---
## User Review Required
> [!IMPORTANT]
> - **Biến môi trường trong Docker**: Khi chạy trong Docker Compose, địa chỉ kết nối cơ sở dữ liệu (`DATABASE_URL`) và Redis (`REDIS_URL`) phải trỏ đến tên các service của container (ví dụ: `postgres` thay vì `localhost`). Chúng tôi sẽ cấu hình Docker Compose ghi đè (override) các biến này một cách tự động để tránh làm hỏng cấu hình chạy trực tiếp bằng `npm run start:dev` trên máy local của bạn.
> - **Cổng mạng (Ports)**:
> - Backend: Cổng `3001` được mở ra ngoài.
> - Frontend: Cổng `5173` (cho Dev) và cổng `3002` (cho Production thông qua Nginx).
> - Database: Cổng `5432` (mở để truy cập quản trị nếu cần).
> - Redis: Cổng `6379`.
---
## Proposed Changes
### 1. Dockerfile cho Backend
#### [NEW] [Dockerfile](file:///home/locpham/travelplanning/backend/Dockerfile)
- Thiết lập môi trường chạy Node.js (phiên bản `20-alpine`).
- Cài đặt các gói phụ thuộc hệ thống cần thiết (như openssl cho Prisma).
- Cấu hình chạy chế độ phát triển (sử dụng volume mounts để hot-reload) và chế độ production (build code JS).
- Chạy Prisma client generation lúc build.
---
### 2. Dockerfile cho Frontend
#### [NEW] [Dockerfile](file:///home/locpham/travelplanning/frontend/Dockerfile)
- Sử dụng chiến lược Multi-stage build để tối ưu hóa dung lượng:
- **Stage 1 (Build)**: Cài đặt dependencies và build mã nguồn React/Vite thành thư mục tĩnh `dist`.
- **Stage 2 (Nginx)**: Copy thư mục `dist` vào container chạy Nginx để phục vụ các file tĩnh ở cổng `80` (phục vụ môi trường production).
- Hỗ trợ chạy Node.js trực tiếp cho môi trường phát triển để dùng Vite dev server và HMR.
---
### 3. Docker Compose cho Phát triển & Sửa Code (Hot-reload)
#### [NEW] [docker-compose.yml](file:///home/locpham/travelplanning/docker-compose.yml)
- Khởi tạo 4 services chính:
1. `postgres`: Cơ sở dữ liệu PostgreSQL 15, lưu trữ dữ liệu bền vững qua volume `pg_data`.
2. `redis`: Caching và WebSockets.
3. `backend`: Mount thư mục `backend/` vào container, chạy lệnh `npm run start:dev` để tự động reload khi sửa code trên máy host.
4. `frontend`: Mount thư mục `frontend/` vào container, chạy lệnh `npm run dev -- --host` để phục vụ Vite dev server hỗ trợ HMR (Hot Module Replacement).
- Đồng bộ hóa các volume ẩn `node_modules` để tránh xung đột hệ điều hành giữa máy host và container.
---
### 4. Docker Compose cho Triển khai lên Server (Production)
#### [NEW] [docker-compose.prod.yml](file:///home/locpham/travelplanning/docker-compose.prod.yml)
- Cấu hình tối ưu để triển khai lên server cloud:
- Builds production image cho `backend` và chạy trực tiếp file JS đã build (`dist/src/main.js`).
- Builds production image cho `frontend` sử dụng Nginx để phục vụ client, tối ưu hóa tốc độ tải trang và bảo mật.
- Tự động restart dịch vụ nếu gặp sự cố (`restart: always`).
---
## Verification Plan
### Automated Tests
- Kiểm tra tính hợp lệ của cấu hình docker-compose:
```bash
docker compose config
```
### Manual Verification
1. **Kiểm tra Môi trường Phát triển (Sửa code trực tiếp)**:
- Chạy lệnh khởi động môi trường dev:
```bash
docker compose up --build
```
- Truy cập giao diện tại `http://localhost:5173`.
- Sửa đổi một dòng văn bản trong frontend (ví dụ: nhãn nút ở `LandingPage.tsx`) hoặc backend và kiểm tra xem container có tự động tải lại (hot-reload) tức thì hay không.
2. **Kiểm tra Môi trường Production (Triển khai server)**:
- Chạy lệnh khởi động môi trường prod:
```bash
docker compose -f docker-compose.prod.yml up --build -d
```
- Xác nhận mọi service khởi chạy ngầm thành công.
- Truy cập ứng dụng qua cổng `80` (http://localhost) và xác nhận hoạt động bình thường.
+2
View File
@@ -0,0 +1,2 @@
VITE_BACKEND_URL=https://yotrip.labz.io.vn
VITE_GOOGLE_CLIENT_ID=639044397050-7hair384u6lle941h033trut1s3q47l4.apps.googleusercontent.com
+2
View File
@@ -0,0 +1,2 @@
VITE_BACKEND_URL=https://yotrip.labz.io.vn
VITE_GOOGLE_CLIENT_ID=639044397050-7hair384u6lle941h033trut1s3q47l4.apps.googleusercontent.com
+101
View File
@@ -0,0 +1,101 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
+2
View File
@@ -0,0 +1,2 @@
/build/*
!/build/.npmkeep
+54
View File
@@ -0,0 +1,54 @@
apply plugin: 'com.android.application'
android {
namespace "com.yotrip.app"
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.yotrip.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
@@ -0,0 +1,22 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-camera')
implementation project(':capacitor-geolocation')
implementation project(':capacitor-local-notifications')
implementation project(':codetrix-studio-capacitor-google-auth')
}
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,26 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
</queries>
</manifest>
@@ -0,0 +1,5 @@
package com.yotrip.app;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {}
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
@@ -0,0 +1,8 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">YoTrip</string>
<string name="title_activity_main">YoTrip</string>
<string name="package_name">com.yotrip.app</string>
<string name="custom_url_scheme">com.yotrip.app</string>
<string name="server_client_id">639044397050-7hair384u6lle941h033trut1s3q47l4.apps.googleusercontent.com</string>
</resources>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
</style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
</style>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>
@@ -0,0 +1,18 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
+38
View File
@@ -0,0 +1,38 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.2'
classpath 'com.google.gms:google-services:4.4.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
mavenCentral()
}
}
subprojects {
configurations.all {
resolutionStrategy {
force "androidx.core:core-ktx:${rootProject.ext.androidxCoreVersion}"
force "androidx.core:core:${rootProject.ext.androidxCoreVersion}"
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
@@ -0,0 +1,15 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../node_modules/@capacitor/android/capacitor')
include ':capacitor-camera'
project(':capacitor-camera').projectDir = new File('../../node_modules/@capacitor/camera/android')
include ':capacitor-geolocation'
project(':capacitor-geolocation').projectDir = new File('../../node_modules/@capacitor/geolocation/android')
include ':capacitor-local-notifications'
project(':capacitor-local-notifications').projectDir = new File('../../node_modules/@capacitor/local-notifications/android')
include ':codetrix-studio-capacitor-google-auth'
project(':codetrix-studio-capacitor-google-auth').projectDir = new File('../../node_modules/@codetrix-studio/capacitor-google-auth/android')
+22
View File
@@ -0,0 +1,22 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
Binary file not shown.
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+252
View File
@@ -0,0 +1,252 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

Some files were not shown because too many files have changed in this diff Show More