Files
hipmi-mobile/QWEN.md
bagasbanuna 6ec839fd67 feat: Migrate Profile, Waiting Room, and Delete Account to OS_Wrapper
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>
2026-04-08 14:32:11 +08:00

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-native v10.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

  1. Install Dependencies

    bun install
    
  2. Run Development Server

    bun run start
    # or
    bunx expo start
    
  3. 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
    
  4. 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 OTP
  • validateOtp() - Validate OTP, get token
  • registerUser() - Register new user
  • logout() - Clear session and logout
  • userData() - 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
  • 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/xxx or fixed-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

  1. Create Screen Component (screens/Admin/Feature/ScreenXXX.tsx):
export function Admin_ScreenXXX() {
  const pagination = usePagination({...});
  const renderItem = useCallback(...);
  const headerComponent = useMemo(...);
  
  return <NewWrapper ... />;
}
  1. Create Box Component (optional, for custom item rendering):
export default function Admin_BoxXXX({ item }: { item: any }) {
  return (
    <AdminBasicBox onPress={() => router.push(...)}>
      ...
    </AdminBasicBox>
  );
}
  1. Update API (add pagination if needed):
export async function apiXXX({ page = "1" }: { page?: string }) {
  // ...
}
  1. 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

  1. Add function in appropriate service file
  2. Include page parameter for list endpoints
  3. Use apiConfig axios instance
  4. 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:

  1. Always render PointAnnotation (not conditional)
  2. Use opacity for visibility instead of conditional rendering
  3. 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 updates
  • docs/hipmi-note.md - Build and deployment notes
  • docs/prompt-for-qwen-code.md - Development prompts and patterns

Resources

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