Profile Screens (8 files):
- [id]/index.tsx: NewWrapper → OS_Wrapper (list with refresh)
- [id]/edit.tsx: ViewWrapper → OS_Wrapper (form + keyboard handling)
- create.tsx: ViewWrapper → OS_Wrapper (form + keyboard handling)
- [id]/blocked-list.tsx: NewWrapper → OS_Wrapper (pagination list)
- [id]/detail-blocked.tsx: NewWrapper → OS_Wrapper (static with footer)
- [id]/update-background.tsx: ViewWrapper → OS_Wrapper (static with footer)
- [id]/update-photo.tsx: ViewWrapper → OS_Wrapper (static with footer)
- All Profile forms use enableKeyboardHandling + contentPaddingBottom={250}
Other Screens (2 files):
- waiting-room.tsx: NewWrapper → OS_Wrapper (static with refresh + footer)
- delete-account.tsx: ViewWrapper → OS_Wrapper (form + keyboard handling)
Bug Fixes:
- AndroidWrapper: Add refreshControl to ScrollView (fix pull-to-refresh on static mode)
Pattern Applied:
- List screens: contentPaddingBottom=100 (default)
- Form screens: contentPaddingBottom=250 (with TextInput)
- No PADDING_INLINE (user preference - prevents box narrowing)
Documentation:
- Update TASK-005 with Phase 1 completion details
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
14 KiB
HIPMI Mobile Application - Development Context
Project Overview
HIPMI Mobile is a cross-platform mobile application built with Expo and React Native. The application is named "HIPMI Badung Connect" and serves as a platform for the HIPMI (Himpunan Pengusaha dan Pengusaha Indonesia) Badung chapter. It's designed to run on iOS, Android, and web platforms using a single codebase.
Key Technologies
- Framework: Expo (v54.0.0) with React Native (v0.81.5)
- Language: TypeScript
- Architecture: File-based routing with Expo Router
- State Management: Context API (AuthContext)
- UI Components: React Native Paper, custom components
- Maps Integration: Maplibre Maps for React Native (
@maplibre/maplibre-react-nativev10.4.2) - Push Notifications: React Native Firebase Messaging
- Build System: Metro bundler
- Package Manager: Bun
Project Structure
hipmi-mobile/
├── app/ # Main application screens and routing (Expo Router)
│ ├── _layout.tsx # Root layout component
│ ├── index.tsx # Entry point (Login screen)
│ └── (application)/ # Main app screens
│ ├── admin/ # Admin panel screens
│ ├── (user)/ # User screens
│ └── ...
├── components/ # Reusable UI components
│ ├── _ShareComponent/ # Shared components (NewWrapper, Admin components)
│ ├── _Icon/ # Icon components
│ └── ...
├── context/ # State management (AuthContext)
├── screens/ # Screen components organized by feature
│ ├── Admin/ # Admin panel screens
│ │ ├── Donation/ # Donation management screens
│ │ ├── Voting/ # Voting management screens
│ │ ├── Event/ # Event management screens
│ │ └── ...
│ ├── Authentication/ # Login, registration flows
│ ├── RootLayout/ # Root layout components
│ └── ...
├── service/ # API services and business logic
│ ├── api-admin/ # Admin API endpoints
│ ├── api-client/ # Client API endpoints
│ └── api-config.ts # Axios configuration
├── hooks/ # Custom React hooks
│ ├── use-pagination.tsx # Pagination hook
│ └── ...
├── helpers/ # Helper functions
│ ├── paginationHelpers.tsx # Pagination UI helpers
│ └── ...
├── types/ # TypeScript type definitions
├── utils/ # Utility functions
├── constants/ # Constants and configuration values
├── styles/ # Global styles
├── assets/ # Images, icons, and static assets
└── docs/ # Documentation files
Building and Running
Prerequisites
- Node.js: v18+ with Bun package manager
- Expo CLI: Installed globally or via npx
- iOS: Xcode (macOS only) for iOS simulator/builds
- Android: Android Studio for Android emulator/builds
Setup and Development
-
Install Dependencies
bun install -
Run Development Server
bun run start # or bunx expo start -
Platform-Specific Commands
# iOS Simulator bun run ios # or bunx expo start --ios # Android Emulator bun run android # or bunx expo start --android # Web Browser bun run web # or bunx expo start --web -
Linting
bun run lint
Build Commands
EAS Build (Production)
# Production build (App Store / Play Store)
eas build --profile production
# Preview build (Internal distribution)
eas build --profile preview
# Development build (Development client)
eas build --profile development
Local Native Builds
# Generate native folders (iOS & Android)
npx expo prebuild
# iOS specific
bunx expo prebuild --platform ios
open ios/HIPMIBadungConnect.xcworkspace
# Android specific
bunx expo prebuild --platform android
Version Management
# Patch version update
npm version patch
# Update iOS build number
bunx expo prebuild --platform ios
# Update Android version code
bunx expo prebuild --platform android
Android Debugging
# List connected devices
adb devices
# Install APK to device/emulator
adb install android/app/build/outputs/apk/debug/app-debug.apk
# Install to specific device
adb -s <device_id> install android/app/build/outputs/apk/debug/app-debug.apk
Environment Variables
Create a .env file in the project root with:
API_BASE_URL=https://your-api-base-url.com
BASE_URL=https://your-app-url.com
DEEP_LINK_URL=hipmimobile://
These are loaded in app.config.js and accessible via Constants.expoConfig.extra.
Architecture Patterns
1. Separation of Concerns
Route Files (app/) should be minimal (max 5 lines):
import { Admin_ScreenXXX } from "@/screens/Admin/XXX/ScreenXXX";
export default function AdminXXX() {
return <Admin_ScreenXXX />;
}
Screen Components (screens/) contain all business logic:
export function Admin_ScreenXXX() {
// Logic, hooks, state management
return <NewWrapper ... />;
}
2. Pagination Pattern
Using usePagination hook with infinite scroll:
const pagination = usePagination({
fetchFunction: async (page, searchQuery) => {
const response = await apiXXX({ page: String(page) });
if (response.success) {
return { data: response.data };
}
return { data: [] };
},
pageSize: PAGINATION_DEFAULT_TAKE, // 10
searchQuery: search,
dependencies: [dependency],
});
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
emptyMessage: "Belum ada data",
skeletonCount: PAGINATION_DEFAULT_TAKE,
});
3. Wrapper Components
NewWrapper (preferred for lists):
<NewWrapper
listData={pagination.listData}
renderItem={renderItem}
headerComponent={headerComponent}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onEndReached={pagination.loadMore}
refreshControl={<RefreshControl ... />}
/>
AdminBasicBox (for card layouts):
<AdminBasicBox
onPress={() => router.push(`/path/${item.id}`)}
style={{ marginHorizontal: 10, marginVertical: 5 }}
>
<StackCustom gap={0}>
<GridSpan_4_8 label="Label" value={<TextCustom>Value</TextCustom>} />
</StackCustom>
</AdminBasicBox>
4. API Service Structure
// service/api-admin/api-xxx.ts
export async function apiXXX({ page = "1" }: { page?: string }) {
try {
const response = await apiConfig.get(`/mobile/admin/xxx?page=${page}`);
return response.data;
} catch (error) {
throw error;
}
}
Important: All list APIs should support pagination with page parameter (default: "1").
5. Authentication Flow
Managed by AuthContext:
loginWithNomor()- Send phone number, receive OTPvalidateOtp()- Validate OTP, get tokenregisterUser()- Register new userlogout()- Clear session and logoutuserData()- Fetch user data by token
Development Conventions
Coding Standards
- TypeScript: Strict mode enabled
- Naming:
- Components: PascalCase (
Admin_ScreenDonationStatus) - Files: PascalCase for components (
ScreenDonationStatus.tsx) - Variables: camelCase
- Constants: UPPER_SNAKE_CASE
- Components: PascalCase (
- Path Aliases:
@/*maps to project root - Imports: Group imports by type (components, hooks, services, etc.)
Component Structure
// 1. Imports (grouped)
import { ... } from "@/components";
import { ... } from "@/hooks";
import { ... } from "@/service";
// 2. Types/Interfaces
interface Props { ... }
// 3. Main Component
export function ComponentName() {
// State
// Hooks
// Functions
// Render
}
Testing
- Linting:
bun run lint - No formal test suite configured yet
Git Workflow
- Feature branches:
feature/xxxorfixed-admin/xxx - Commit messages: Clear and descriptive
- Use CHANGE_LOG.md for tracking changes
Key Features
Authentication
- Phone number login with OTP
- User registration
- Terms & Conditions acceptance
- Session persistence with AsyncStorage
Admin Module
- Dashboard: Overview and statistics
- User Access: User management
- Event: Event CRUD with status management
- Voting: Voting management (publish/review/reject)
- Donation: Donation management with categories and transaction tracking
- Collaboration: Collaboration requests
- Investment: Investment management
- Maps: Location-based features
- App Information: Bank and business field management
User Module
- Home: Main dashboard
- Forum: Discussion forums
- Profile: User profile management
- Portfolio: Member portfolio
- Notifications: Push notifications via Firebase
API Configuration
Base URLs
// From app.config.js extra
API_BASE_URL: process.env.API_BASE_URL
BASE_URL: process.env.BASE_URL
DEEP_LINK_URL: process.env.DEEP_LINK_URL
Axios Interceptor
All API calls use apiConfig with automatic token injection:
apiConfig.interceptors.request.use(async (config) => {
const token = await AsyncStorage.getItem("authToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
Platform Configuration
iOS
- Bundle ID:
com.anonymous.hipmi-mobile - Build Number: 21
- Google Services: Configured
- Associated Domains:
applinks:cld-dkr-staging-hipmi.wibudev.com - Tablet Support: Enabled
Android
- Package:
com.bip.hipmimobileapp - Version Code: 4
- Google Services: Configured (
google-services.json) - Deep Links: HTTPS intent filters configured
- Edge-to-Edge: Enabled
Web
- Output: Static
- Bundler: Metro
Special Integrations
Firebase
- Authentication
- Push Notifications (FCM)
- Configured for both iOS and Android
Maplibre
- Map integration via
@maplibre/maplibre-react-native - Location permissions configured
Deep Linking
- Scheme:
hipmimobile:// - HTTPS:
cld-dkr-hipmi-stg.wibudev.com - Configured for both platforms
Camera
- Camera and microphone permissions
- QR code generation support
Common Development Tasks
Adding a New Admin Screen
- Create Screen Component (
screens/Admin/Feature/ScreenXXX.tsx):
export function Admin_ScreenXXX() {
const pagination = usePagination({...});
const renderItem = useCallback(...);
const headerComponent = useMemo(...);
return <NewWrapper ... />;
}
- Create Box Component (optional, for custom item rendering):
export default function Admin_BoxXXX({ item }: { item: any }) {
return (
<AdminBasicBox onPress={() => router.push(...)}>
...
</AdminBasicBox>
);
}
- Update API (add pagination if needed):
export async function apiXXX({ page = "1" }: { page?: string }) {
// ...
}
- Create Route File (
app/(application)/admin/feature/xxx.tsx):
import { Admin_ScreenXXX } from "@/screens/Admin/Feature/ScreenXXX";
export default function AdminXXX() {
return <Admin_ScreenXXX />;
}
Updating API Endpoints
- Add function in appropriate service file
- Include
pageparameter for list endpoints - Use
apiConfigaxios instance - Handle errors properly
Troubleshooting
Build Issues
# Clean and rebuild
rm -rf node_modules
bun install
bunx expo prebuild --clean
# iOS specific
cd ios && pod install && cd ..
# Android specific
cd android && ./gradlew clean && cd ..
Cache Issues
# Clear Expo cache
bunx expo start -c
# Clear Metro cache
bunx expo start --clear
Dependency Issues
# Reinstall dependencies
rm -rf node_modules bun.lock
bun install
iOS Maplibre Crash Fix
When using Maplibre MapView on iOS, prevent "Attempt to recycle a mounted view" crash:
- Always render PointAnnotation (not conditional)
- Use opacity for visibility instead of conditional rendering
- Avoid key prop changes that force remounting
// ✅ GOOD: Stable PointAnnotation
<PointAnnotation
coordinate={annotationCoordinate} // Always rendered
...
>
<View style={{ opacity: selectedLocation ? 1 : 0 }}>
<SelectedLocationMarker />
</View>
</PointAnnotation>
// ❌ BAD: Conditional rendering causes crash
{selectedLocation && (
<PointAnnotation coordinate={selectedLocation} ... />
)}
Documentation Files
docs/CHANGE_LOG.md- Change log for recent updatesdocs/hipmi-note.md- Build and deployment notesdocs/prompt-for-qwen-code.md- Development prompts and patterns
Resources
- Expo Documentation
- React Native Documentation
- Expo Router Documentation
- TypeScript Documentation
- Maplibre React Native
Qwen Added Memories
- OS_Wrapper contentPaddingBottom pattern:
- Default: contentPaddingBottom=100 (untuk list screens)
- Forms: contentPaddingBottom=250 (HANYA untuk screens yang punya TextInput/TextArea)
- contentPadding=0 (default, per-screen control)
- OS_ANDROID_PADDING_TOP=6 (compact tabs)
- OS_IOS_PADDING_TOP=12
- PADDING_INLINE=16 (constant)
Contoh:
// List screen (default 100px)
<OS_Wrapper listData={data} renderItem={renderItem} />
// Form screen (explicit 250px)
<OS_Wrapper enableKeyboardHandling contentPaddingBottom={250}>
<FormWithTextInput />
</OS_Wrapper>
- PADDING_INLINE usage pattern - User preference:
- PADDING_INLINE (16px) TIDAK selalu diperlukan
- User remove PADDING_INLINE dari Profile screens karena mempersempit box tampilan
- Decision: Tambahkan PADDING_INLINE HANYA jika diperlukan per-screen, jangan default
- User akan review dan tambahkan sendiri jika perlu
Profile screens: PADDING_INLINE dihapus dari edit.tsx dan create.tsx