diff --git a/app/(application)/(user)/job/(tabs)/_layout.tsx b/app/(application)/(user)/job/(tabs)/_layout.tsx
index ef24fba..ff0ec2d 100644
--- a/app/(application)/(user)/job/(tabs)/_layout.tsx
+++ b/app/(application)/(user)/job/(tabs)/_layout.tsx
@@ -5,15 +5,17 @@ import { IconHome, IconStatus } from "@/components/_Icon";
import BackButtonFromNotification from "@/components/Button/BackButtonFromNotification";
import { TabsStyles } from "@/styles/tabs-styles";
import { Ionicons } from "@expo/vector-icons";
-import {
- router,
- Tabs,
- useLocalSearchParams
-} from "expo-router";
+import { router, Tabs, useLocalSearchParams } from "expo-router";
import { View } from "react-native";
import { MainColor } from "@/constants/color-palet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
+import {
+ OS_ANDROID_HEIGHT,
+ OS_ANDROID_PADDING_TOP,
+ OS_IOS_HEIGHT,
+ OS_IOS_PADDING_TOP,
+} from "@/constants/constans-value";
function JobTabsWrapper() {
const insets = useSafeAreaInsets();
@@ -31,20 +33,23 @@ function JobTabsWrapper() {
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
- paddingTop: 12,
- height: 80,
+ paddingTop: OS_IOS_PADDING_TOP,
+ height: OS_IOS_HEIGHT,
},
android: {
borderTopWidth: 0,
- paddingTop: 5,
- height: 70 + paddingBottom,
+ paddingTop: OS_ANDROID_PADDING_TOP,
+ height: OS_ANDROID_HEIGHT + paddingBottom,
},
}),
header: () => (
+
}
/>
),
diff --git a/app/(application)/(user)/job/[id]/[status]/detail.tsx b/app/(application)/(user)/job/[id]/[status]/detail.tsx
index 12bd4c4..456a886 100644
--- a/app/(application)/(user)/job/[id]/[status]/detail.tsx
+++ b/app/(application)/(user)/job/[id]/[status]/detail.tsx
@@ -5,7 +5,7 @@ import {
DrawerCustom,
LoaderCustom,
MenuDrawerDynamicGrid,
- NewWrapper_V2,
+ OS_Wrapper,
Spacing,
StackCustom,
} from "@/components";
@@ -23,6 +23,7 @@ import {
useLocalSearchParams,
} from "expo-router";
import { useCallback, useState } from "react";
+import { PADDING_INLINE } from "@/constants/constans-value";
export default function JobDetailStatus() {
const { id, status } = useLocalSearchParams();
@@ -72,7 +73,7 @@ export default function JobDetailStatus() {
),
}}
/>
-
+
{isLoadData ? (
) : (
@@ -96,7 +97,7 @@ export default function JobDetailStatus() {
>
)}
-
+
{
@@ -119,40 +123,41 @@ export function AndroidWrapper(props: AndroidWrapperProps) {
const listProps = props as ListModeProps;
return (
-
- {headerComponent && (
- {headerComponent}
- )}
- `${String(item.id)}-${index}`)
- }
- refreshControl={refreshControl}
- onEndReached={listProps.onEndReached}
- onEndReachedThreshold={0.5}
- ListHeaderComponent={listProps.ListHeaderComponent}
- ListFooterComponent={listProps.ListFooterComponent}
- ListEmptyComponent={listProps.ListEmptyComponent}
- contentContainerStyle={{
- flexGrow: 1,
- paddingBottom:
- (footerComponent && !hideFooter ? OS_HEIGHT : 0) +
- contentPaddingBottom,
- padding: contentPadding,
- }}
- keyboardShouldPersistTaps="handled"
- />
+
+
+ {headerComponent && (
+ {headerComponent}
+ )}
+ `${String(item.id)}-${index}`)
+ }
+ refreshControl={refreshControl}
+ onEndReached={listProps.onEndReached}
+ onEndReachedThreshold={0.5}
+ ListHeaderComponent={listProps.ListHeaderComponent}
+ ListFooterComponent={listProps.ListFooterComponent}
+ ListEmptyComponent={listProps.ListEmptyComponent}
+ contentContainerStyle={{
+ flexGrow: 1,
+ paddingBottom:
+ (footerComponent && !hideFooter ? OS_HEIGHT : 0) +
+ finalContentPaddingBottom,
+ padding: finalContentPadding,
+ }}
+ keyboardShouldPersistTaps="handled"
+ />
{/* Footer - Fixed di bawah dengan width 100% */}
{footerComponent && !hideFooter && (
{footerComponent}
@@ -170,6 +175,7 @@ export function AndroidWrapper(props: AndroidWrapperProps) {
{floatingButton}
)}
+
);
}
@@ -178,7 +184,7 @@ export function AndroidWrapper(props: AndroidWrapperProps) {
return (
{headerComponent && (
@@ -187,13 +193,15 @@ export function AndroidWrapper(props: AndroidWrapperProps) {
{
return (
{headerComponent && (
@@ -128,7 +127,7 @@ const iOSWrapper = (props: iOSWrapperProps) => {
{footerComponent && !hideFooter && (
{footerComponent}
@@ -155,7 +154,7 @@ const iOSWrapper = (props: iOSWrapperProps) => {
return (
{headerComponent && (
@@ -181,7 +180,7 @@ const iOSWrapper = (props: iOSWrapperProps) => {
{footerComponent && !hideFooter && (
{footerComponent}
diff --git a/components/_ShareComponent/OS_Wrapper.tsx b/components/_ShareComponent/OS_Wrapper.tsx
index 024c00b..87568a6 100644
--- a/components/_ShareComponent/OS_Wrapper.tsx
+++ b/components/_ShareComponent/OS_Wrapper.tsx
@@ -43,10 +43,10 @@ interface ListModeProps extends BaseProps {
keyExtractor?: FlatListProps["keyExtractor"];
}
-// ========== PageWrapper Props (Android-specific keyboard handling) ==========
-interface PageWrapperBaseProps extends BaseProps {
+// ========== Keyboard Handling Props (Android only) ==========
+interface KeyboardHandlingProps {
/**
- * Enable keyboard handling (Android only - NewWrapper_V2)
+ * Enable keyboard handling with auto-scroll (Android only)
* iOS ignores this prop
* @default false
*/
@@ -74,81 +74,74 @@ interface PageWrapperBaseProps extends BaseProps {
contentPadding?: number;
}
-interface PageWrapperStaticProps extends PageWrapperBaseProps {
- children: React.ReactNode;
- listData?: never;
- renderItem?: never;
-}
+// ========== Final Props Types ==========
+type OS_WrapperStaticProps = StaticModeProps & KeyboardHandlingProps;
+type OS_WrapperListProps = ListModeProps & KeyboardHandlingProps;
-interface PageWrapperListProps extends PageWrapperBaseProps {
- children?: never;
- listData?: any[];
- renderItem?: FlatListProps["renderItem"];
- onEndReached?: () => void;
- ListHeaderComponent?: React.ReactElement | null;
- ListFooterComponent?: React.ReactElement | null;
- ListEmptyComponent?: React.ReactElement | null;
- keyExtractor?: FlatListProps["keyExtractor"];
-}
-
-type OS_WrapperProps = StaticModeProps | ListModeProps;
-type PageWrapperProps = PageWrapperStaticProps | PageWrapperListProps;
+type OS_WrapperProps = OS_WrapperStaticProps | OS_WrapperListProps;
/**
* OS_Wrapper - Automatically selects iOSWrapper or AndroidWrapper based on platform
*
- * @example Static Mode
+ * Features:
+ * - Auto platform detection
+ * - Optional keyboard handling for Android forms
+ * - Unified API for all use cases
+ *
+ * @example Static Mode (Simple Content)
* ```tsx
*
*
*
* ```
- *
- * @example List Mode
+ *
+ * @example List Mode (with pagination)
* ```tsx
* }
* ListEmptyComponent={}
+ * onEndReached={loadMore}
* />
* ```
+ *
+ * @example Form Mode (with keyboard handling - Android only)
+ * ```tsx
+ * }
+ * >
+ *
+ *
+ * ```
*/
export function OS_Wrapper(props: OS_WrapperProps) {
+ const {
+ enableKeyboardHandling = false,
+ keyboardScrollOffset = 100,
+ contentPaddingBottom = 250,
+ contentPadding = 0,
+ ...wrapperProps
+ } = props;
+
// iOS uses IOSWrapper (based on NewWrapper)
if (Platform.OS === "ios") {
- return ;
+ // Keyboard handling props are ignored on iOS
+ return ;
}
- // Android uses AndroidWrapper (based on NewWrapper_V2 with keyboard handling)
- return ;
-}
-
-/**
- * PageWrapper - OS_Wrapper with keyboard handling support (Android only)
- * Use this for forms with input fields
- *
- * @example
- * ```tsx
- *
- *
- *
- * ```
- */
-export function PageWrapper(props: PageWrapperProps) {
- // iOS: Keyboard handling props are ignored
- if (Platform.OS === "ios") {
- const {
- enableKeyboardHandling: _,
- keyboardScrollOffset: __1,
- contentPaddingBottom: __2,
- contentPadding: __3,
- ...iosProps
- } = props;
- return ;
- }
-
- // Android: Keyboard handling props are used
- return ;
+ // Android uses AndroidWrapper (with keyboard handling support)
+ return (
+
+ );
}
// Re-export individual wrappers for direct usage if needed
diff --git a/components/index.ts b/components/index.ts
index 62e8df7..c39182a 100644
--- a/components/index.ts
+++ b/components/index.ts
@@ -66,7 +66,7 @@ import BasicWrapper from "./_ShareComponent/BasicWrapper";
import { FormWrapper } from "./_ShareComponent/FormWrapper";
import { NewWrapper_V2 } from "./_ShareComponent/NewWrapper_V2";
// OS-Specific Wrappers
-import OS_Wrapper, { PageWrapper, IOSWrapper, AndroidWrapper } from "./_ShareComponent/OS_Wrapper";
+import OS_Wrapper, { IOSWrapper, AndroidWrapper } from "./_ShareComponent/OS_Wrapper";
// Progress
import ProgressCustom from "./Progress/ProgressCustom";
@@ -136,7 +136,6 @@ export {
NewWrapper_V2,
// OS-Specific Wrappers
OS_Wrapper,
- PageWrapper,
IOSWrapper,
AndroidWrapper,
// Stack
diff --git a/constants/constans-value.ts b/constants/constans-value.ts
index 87a3a24..d803496 100644
--- a/constants/constans-value.ts
+++ b/constants/constans-value.ts
@@ -4,6 +4,10 @@ export {
OS_ANDROID_HEIGHT,
OS_IOS_HEIGHT,
OS_HEIGHT,
+ OS_ANDROID_PADDING_TOP,
+ OS_IOS_PADDING_TOP,
+ OS_PADDING_TOP,
+ PADDING_INLINE,
TEXT_SIZE_SMALL,
TEXT_SIZE_MEDIUM,
TEXT_SIZE_LARGE,
@@ -23,10 +27,15 @@ export {
};
// OS Height
-const OS_ANDROID_HEIGHT = 65
+const OS_ANDROID_HEIGHT = 60
const OS_IOS_HEIGHT = 80
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
+// OS Padding Top
+const OS_ANDROID_PADDING_TOP = 12
+const OS_IOS_PADDING_TOP = 12
+const OS_PADDING_TOP = Platform.OS === "ios" ? OS_IOS_PADDING_TOP : OS_ANDROID_PADDING_TOP
+
// Text Size
const TEXT_SIZE_SMALL = 12;
const TEXT_SIZE_MEDIUM = 14;
@@ -47,6 +56,7 @@ const DRAWER_HEIGHT = 500; // tinggi drawer5
const RADIUS_BUTTON = 50
// Padding
+const PADDING_INLINE = 16
const PADDING_EXTRA_SMALL = 10
const PADDING_SMALL = 12
const PADDING_MEDIUM = 16
diff --git a/docs/OS-Wrapper-Quick-Reference.md b/docs/OS-Wrapper-Quick-Reference.md
index 782e2ec..d08d5b2 100644
--- a/docs/OS-Wrapper-Quick-Reference.md
+++ b/docs/OS-Wrapper-Quick-Reference.md
@@ -2,7 +2,7 @@
## 📦 Import
```tsx
-import { OS_Wrapper, PageWrapper, IOSWrapper, AndroidWrapper } from "@/components";
+import { OS_Wrapper, IOSWrapper, AndroidWrapper } from "@/components";
```
## 🎯 Usage Examples
@@ -35,9 +35,9 @@ import { OS_Wrapper, PageWrapper, IOSWrapper, AndroidWrapper } from "@/component
```
-### 3. PageWrapper - Form dengan Keyboard Handling
+### 3. OS_Wrapper - Form dengan Keyboard Handling
```tsx
-
-
+
```
### 4. Platform-Specific (Rare Cases)
@@ -107,21 +107,21 @@ import { OS_Wrapper, PageWrapper, IOSWrapper, AndroidWrapper } from "@/component
```diff
- import { NewWrapper_V2 } from "@/components/_ShareComponent/NewWrapper_V2";
-+ import { PageWrapper } from "@/components";
++ import { OS_Wrapper } from "@/components";
-
-+
++
```
## 💡 Tips
-1. **Pakai OS_Wrapper** untuk screen biasa (list, detail, dll)
-2. **Pakai PageWrapper** untuk screen dengan form input (create, edit)
+1. **Pakai OS_Wrapper** untuk semua screen (list, detail, form)
+2. **Tambahkan `enableKeyboardHandling`** untuk form dengan input fields
3. **Jangan mix** wrapper lama dan baru di screen yang sama
4. **Test di kedua platform** sebelum commit
-5. **Keyboard handling** hanya bekerja di Android dengan PageWrapper
+5. **Keyboard handling** hanya bekerja di Android (iOS mengabaikan props ini)
## ⚠️ Common Mistakes
@@ -134,6 +134,9 @@ import OS_Wrapper from "@/components/_ShareComponent/OS_Wrapper";
{content}
+
+// Jangan pakai PageWrapper (sudah tidak ada)
+import { PageWrapper } from "@/components";
```
### ✅ Correct
@@ -141,8 +144,13 @@ import OS_Wrapper from "@/components/_ShareComponent/OS_Wrapper";
// Import dari @/components
import { OS_Wrapper } from "@/components";
-// Pakai salah satu saja
+// Simple content
{content}
+
+// Form with keyboard handling
+
+
+
```
---
diff --git a/hooks/useKeyboardForm.ts b/hooks/useKeyboardForm.ts
index 638ab95..115f988 100644
--- a/hooks/useKeyboardForm.ts
+++ b/hooks/useKeyboardForm.ts
@@ -1,25 +1,35 @@
// useKeyboardForm.ts - Hook untuk keyboard handling pada form
-import { Keyboard, ScrollView } from "react-native";
+import { Keyboard, ScrollView, Dimensions } from "react-native";
import { useState, useEffect, useRef } from "react";
+import React from "react";
export function useKeyboardForm(scrollOffset = 100) {
const scrollViewRef = useRef(null);
const [keyboardHeight, setKeyboardHeight] = useState(0);
- const [focusedInputY, setFocusedInputY] = useState(null);
+ const currentScrollY = useRef(0);
// Listen to keyboard events
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
(e) => {
- setKeyboardHeight(e.endCoordinates.height);
+ const kbHeight = e.endCoordinates.height;
+ setKeyboardHeight(kbHeight);
+
+ // Simple: scroll by keyboard height saat keyboard muncul
+ if (scrollViewRef.current) {
+ const targetY = currentScrollY.current + kbHeight - scrollOffset;
+ scrollViewRef.current.scrollTo({
+ y: Math.max(0, targetY),
+ animated: true,
+ });
+ }
}
);
const keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => {
setKeyboardHeight(0);
- setFocusedInputY(null);
}
);
@@ -27,41 +37,35 @@ export function useKeyboardForm(scrollOffset = 100) {
keyboardDidShowListener.remove();
keyboardDidHideListener.remove();
};
- }, []);
+ }, [scrollOffset]);
- // Scroll ke focused input
- useEffect(() => {
- if (focusedInputY !== null && keyboardHeight > 0 && scrollViewRef.current) {
- setTimeout(() => {
- scrollViewRef.current?.scrollTo({
- y: Math.max(0, focusedInputY - scrollOffset),
- animated: true,
- });
- }, 100);
- }
- }, [focusedInputY, keyboardHeight, scrollOffset]);
-
- // Handler untuk track focused input position
- const handleInputFocus = (yPosition: number) => {
- setFocusedInputY(yPosition);
+ // Track scroll position
+ const handleScroll = (event: any) => {
+ currentScrollY.current = event.nativeEvent.contentOffset.y;
};
- // Helper untuk create onFocus handler
+ // Dummy handlers (for API compatibility)
const createFocusHandler = () => {
- return (e: any) => {
- e.target?.measure((x: number, y: number, width: number, height: number, pageX: number, pageY: number) => {
- if (pageY !== null) {
- handleInputFocus(pageY);
- }
- });
- };
+ return () => {};
};
+ const registerInputFocus = () => {};
+
return {
scrollViewRef,
keyboardHeight,
- focusedInputY,
- handleInputFocus,
+ registerInputFocus,
createFocusHandler,
+ handleScroll,
};
}
+
+/**
+ * Dummy helper (no-op, for API compatibility)
+ */
+export function cloneChildrenWithFocusHandler(
+ children: React.ReactNode,
+ _focusHandler: (inputRef: any) => void
+): React.ReactNode {
+ return children;
+}
diff --git a/ios/HIPMIBadungConnect.xcodeproj/project.pbxproj b/ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
index a7da5f7..1f87e74 100644
--- a/ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
+++ b/ios/HIPMIBadungConnect.xcodeproj/project.pbxproj
@@ -154,6 +154,7 @@
3F53CC1C3B278545F11A1CAE /* [CP-User] [RNFB] Core Configuration */,
46ED08049A384B869D77364E /* Remove signature files (Xcode workaround) */,
92A25C61F4E34FB6A36E415B /* Remove signature files (Xcode workaround) */,
+ B122FE573BBA4E8C86B8F1C3 /* Remove signature files (Xcode workaround) */,
);
buildRules = (
);
@@ -465,6 +466,23 @@
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
+ B122FE573BBA4E8C86B8F1C3 /* Remove signature files (Xcode workaround) */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ name = "Remove signature files (Xcode workaround)";
+ inputPaths = (
+ );
+ outputPaths = (
+ );
+ shellPath = /bin/sh;
+ shellScript = "
+ echo \"Remove signature files (Xcode workaround)\";
+ rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
+ ";
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
diff --git a/screens/Job/MainViewStatus.tsx b/screens/Job/MainViewStatus.tsx
index fbc51bf..fd8df11 100644
--- a/screens/Job/MainViewStatus.tsx
+++ b/screens/Job/MainViewStatus.tsx
@@ -12,6 +12,7 @@ import { apiJobGetByStatus } from "@/service/api-client/api-job";
import { useFocusEffect, useLocalSearchParams } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
+import { PADDING_INLINE } from "@/constants/constans-value";
export default function Job_MainViewStatus() {
const { user } = useAuth();
@@ -64,7 +65,7 @@ export default function Job_MainViewStatus() {
return (
<>
-
+
{isLoadList ? (
) : _.isEmpty(listData) ? (
diff --git a/screens/Job/MainViewStatus2.tsx b/screens/Job/MainViewStatus2.tsx
index b315808..3a84a66 100644
--- a/screens/Job/MainViewStatus2.tsx
+++ b/screens/Job/MainViewStatus2.tsx
@@ -1,7 +1,7 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { BaseBox, OS_Wrapper, ScrollableCustom, TextCustom } from "@/components";
import { MainColor } from "@/constants/color-palet";
-import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
+import { PAGINATION_DEFAULT_TAKE, PADDING_INLINE } from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { useAuth } from "@/hooks/use-auth";
import { usePagination } from "@/hooks/use-pagination";
@@ -102,6 +102,7 @@ export default function Job_MainViewStatus2() {
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
hideFooter
+ contentPadding={PADDING_INLINE}
/>
);
}
diff --git a/screens/Job/ScreenArchive.tsx b/screens/Job/ScreenArchive.tsx
index 9ed8051..b2a8daa 100644
--- a/screens/Job/ScreenArchive.tsx
+++ b/screens/Job/ScreenArchive.tsx
@@ -5,6 +5,7 @@ import { apiJobGetAll } from "@/service/api-client/api-job";
import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
+import { PADDING_INLINE } from "@/constants/constans-value";
export default function Job_ScreenArchive() {
const { user } = useAuth();
@@ -33,7 +34,7 @@ export default function Job_ScreenArchive() {
};
return (
-
+
{isLoadData ? (
) : _.isEmpty(listData) ? (
diff --git a/screens/Job/ScreenArchive2.tsx b/screens/Job/ScreenArchive2.tsx
index be3bd26..fd5ac94 100644
--- a/screens/Job/ScreenArchive2.tsx
+++ b/screens/Job/ScreenArchive2.tsx
@@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
-import { BaseBox, OS_Wrapper, TextCustom, ViewWrapper } from "@/components";
+import { BaseBox, OS_Wrapper, TextCustom } from "@/components";
import { MainColor } from "@/constants/color-palet";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { useAuth } from "@/hooks/use-auth";
@@ -9,7 +9,7 @@ import { useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState } from "react";
import { RefreshControl } from "react-native";
-import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
+import { PAGINATION_DEFAULT_TAKE, PADDING_INLINE } from "@/constants/constans-value";
export default function Job_ScreenArchive2() {
const { user } = useAuth();
@@ -70,6 +70,7 @@ export default function Job_ScreenArchive2() {
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
hideFooter
+ contentPadding={PADDING_INLINE}
/>
);
}
diff --git a/screens/Job/ScreenBeranda.tsx b/screens/Job/ScreenBeranda.tsx
index 52ec911..0af030a 100644
--- a/screens/Job/ScreenBeranda.tsx
+++ b/screens/Job/ScreenBeranda.tsx
@@ -13,6 +13,7 @@ import { apiJobGetAll } from "@/service/api-client/api-job";
import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useState } from "react";
+import { PADDING_INLINE } from "@/constants/constans-value";
export default function Job_ScreenBeranda() {
const [listData, setListData] = useState([]);
@@ -45,6 +46,7 @@ export default function Job_ScreenBeranda() {
return (
router.push("/job/create")} />
}
diff --git a/screens/Job/ScreenBeranda2.tsx b/screens/Job/ScreenBeranda2.tsx
index 5a8dfc8..f687489 100644
--- a/screens/Job/ScreenBeranda2.tsx
+++ b/screens/Job/ScreenBeranda2.tsx
@@ -7,7 +7,6 @@ import {
Spacing,
StackCustom,
TextCustom,
- ViewWrapper,
} from "@/components";
import { MainColor } from "@/constants/color-palet";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
@@ -17,7 +16,7 @@ import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useState } from "react";
import { RefreshControl, View } from "react-native";
-import { PAGINATION_DEFAULT_TAKE } from "@/constants/constans-value";
+import { PAGINATION_DEFAULT_TAKE, PADDING_INLINE } from "@/constants/constans-value";
const PAGE_SIZE = 10;
@@ -76,6 +75,7 @@ export default function Job_ScreenBeranda2() {
return (
@@ -173,7 +175,9 @@ export function Job_ScreenCreate() {
onChangeText={(value) => setData({ ...data, deskripsi: value })}
/>
+
+
-
+
);
}
diff --git a/screens/Job/ScreenJobEdit.tsx b/screens/Job/ScreenJobEdit.tsx
index 43135ef..d8ef9d2 100644
--- a/screens/Job/ScreenJobEdit.tsx
+++ b/screens/Job/ScreenJobEdit.tsx
@@ -8,13 +8,14 @@ import {
InformationBox,
LandscapeFrameUploaded,
LoaderCustom,
- PageWrapper,
+ OS_Wrapper,
Spacing,
StackCustom,
TextAreaCustom,
TextInputCustom,
} from "@/components";
import DIRECTORY_ID from "@/constants/directory-id";
+import { PADDING_INLINE } from "@/constants/constans-value";
import { apiJobGetOne, apiJobUpdateData } from "@/service/api-client/api-job";
import { deleteFileService, uploadFileService } from "@/service/upload-service";
import pickImage from "@/utils/pickImage";
@@ -134,9 +135,10 @@ export function Job_ScreenEdit() {
};
return (
-
{isLoadData ? (
@@ -203,6 +205,6 @@ export function Job_ScreenEdit() {
{buttonSubmit()}
)}
-
+
);
}
diff --git a/tasks/TASK-005-OS-Wrapper-Implementation.md b/tasks/TASK-005-OS-Wrapper-Implementation.md
index 71594ce..113be0e 100644
--- a/tasks/TASK-005-OS-Wrapper-Implementation.md
+++ b/tasks/TASK-005-OS-Wrapper-Implementation.md
@@ -5,25 +5,38 @@ Migrasi dari `NewWrapper` dan `NewWrapper_V2` ke `OS_Wrapper` yang otomatis meny
## 🎯 Goals
- ✅ Mengganti penggunaan `NewWrapper` → `OS_Wrapper` di user screens
-- ✅ Mengganti penggunaan `NewWrapper_V2` → `OS_Wrapper` atau `PageWrapper` di form screens
+- ✅ Mengganti penggunaan `NewWrapper_V2` → `OS_Wrapper` di form screens (dengan keyboard handling props)
- ✅ Memastikan tabs dan UI konsisten di iOS dan Android
- ✅ Backward compatible (wrapper lama tetap ada)
+- ✅ **SIMPLIFIED**: Satu komponen `OS_Wrapper` untuk semua use cases (tidak ada `PageWrapper` terpisah)
## 📦 Available Wrappers
-### 1. **OS_Wrapper** (Recommended)
+### 1. **OS_Wrapper** (Recommended - Unified API)
Auto-detect platform dan routing ke wrapper yang sesuai:
- iOS → `IOSWrapper` (berbasis NewWrapper)
-- Android → `AndroidWrapper` (berbasis NewWrapper_V2)
+- Android → `AndroidWrapper` (berbasis NewWrapper_V2 dengan keyboard handling)
-### 2. **PageWrapper** (For Forms)
-Sama seperti OS_Wrapper tapi dengan keyboard handling (Android only):
-- `enableKeyboardHandling` - Auto scroll saat input focus
-- `keyboardScrollOffset` - Offset scroll (default: 100)
-- `contentPaddingBottom` - Extra padding bottom (default: 80)
-- `contentPadding` - Content padding (default: 16)
+**Props:**
+```tsx
+// Base props (kedua platform)
+withBackground?: boolean;
+headerComponent?: React.ReactNode;
+footerComponent?: React.ReactNode;
+floatingButton?: React.ReactNode;
+hideFooter?: boolean;
+edgesFooter?: Edge[];
+style?: ViewStyle;
+refreshControl?: RefreshControl;
-### 3. **IOSWrapper** / **AndroidWrapper** (Direct Usage)
+// Keyboard handling (Android only - iOS mengabaikan)
+enableKeyboardHandling?: boolean; // Default: false
+keyboardScrollOffset?: number; // Default: 100
+contentPaddingBottom?: number; // Default: 80
+contentPadding?: number; // Default: 16
+```
+
+### 2. **IOSWrapper** / **AndroidWrapper** (Direct Usage)
Untuk kasus khusus yang butuh platform-specific behavior.
## 📝 Migration Guide
@@ -31,85 +44,103 @@ Untuk kasus khusus yang butuh platform-specific behavior.
### Before (Old Way)
```tsx
import NewWrapper from "@/components/_ShareComponent/NewWrapper";
-
// atau
import { NewWrapper_V2 } from "@/components/_ShareComponent/NewWrapper_V2";
```
-### After (New Way)
+### After (New Way - Unified API)
```tsx
-import { OS_Wrapper, PageWrapper } from "@/components";
+import { OS_Wrapper } from "@/components";
-// Static mode
+// Static mode (simple content)
-// List mode
+// List mode (with pagination)
}
ListEmptyComponent={}
+ onEndReached={loadMore}
/>
-// Form dengan keyboard handling
-}
>
-
+
```
-## 🚀 Implementation Phases
+## 🚀 Implementation Status
-### Phase 1: User Screens (Priority: HIGH)
-Files yang perlu di-migrate:
+### ✅ Phase 1: Job Screens - COMPLETED (2026-04-06)
-#### 1.1 Home/Beranda
-- [ ] `screens/Beranda/ScreenBeranda.tsx` atau `ScreenBeranda2.tsx`
- - Ganti `NewWrapper` → `OS_Wrapper`
- - Test tabs behavior di iOS dan Android
+**Files migrated: 8**
-#### 1.2 Profile Screens
+#### Job List Screens (OS_Wrapper):
+- ✅ `screens/Job/ScreenBeranda.tsx` - ViewWrapper → OS_Wrapper
+- ✅ `screens/Job/ScreenBeranda2.tsx` - NewWrapper_V2 → OS_Wrapper
+- ✅ `screens/Job/ScreenArchive.tsx` - ViewWrapper → OS_Wrapper
+- ✅ `screens/Job/ScreenArchive2.tsx` - NewWrapper_V2 → OS_Wrapper
+- ✅ `screens/Job/MainViewStatus.tsx` - ViewWrapper → OS_Wrapper
+- ✅ `screens/Job/MainViewStatus2.tsx` - NewWrapper_V2 → OS_Wrapper
+
+#### Job Form Screens (OS_Wrapper with keyboard handling):
+- ✅ `screens/Job/ScreenJobCreate.tsx` - NewWrapper_V2 → OS_Wrapper + enableKeyboardHandling
+- ✅ `screens/Job/ScreenJobEdit.tsx` - NewWrapper_V2 → OS_Wrapper + enableKeyboardHandling
+
+**Testing Status:**
+- ✅ TypeScript: No errors
+- ✅ Build: Success
+- ✅ iOS Testing: Complete ✅
+- ✅ Android Testing: Complete ✅
+
+**Implementation Notes:**
+- Semua form screens menggunakan `enableKeyboardHandling` untuk keyboard auto-scroll di Android
+- Semua list screens menggunakan pagination dengan `onEndReached`
+- Floating button dan sticky header berfungsi dengan baik
+- Footer component tetap di posisi bawah
+
+### ⏳ Phase 2: Other User Screens (Priority: HIGH)
+
+#### Profile Screens:
- [ ] `screens/Profile/ScreenProfile.tsx`
-- [ ] `screens/Profile/ScreenProfileEdit.tsx`
-- [ ] `screens/Profile/ScreenProfileCreate.tsx`
+- [ ] `screens/Profile/ScreenProfileEdit.tsx` → pakai `enableKeyboardHandling`
+- [ ] `screens/Profile/ScreenProfileCreate.tsx` → pakai `enableKeyboardHandling`
-#### 1.3 Forum/Discussion
+#### Forum/Discussion:
- [ ] `screens/Forum/ScreenForum.tsx`
- [ ] `screens/Forum/ScreenForumDetail.tsx`
-- [ ] `screens/Forum/ScreenForumCreate.tsx` → pakai `PageWrapper` (ada form)
+- [ ] `screens/Forum/ScreenForumCreate.tsx` → pakai `enableKeyboardHandling`
-#### 1.4 Portfolio
+#### Portfolio:
- [ ] `screens/Portfolio/ScreenPortfolio.tsx`
-- [ ] `screens/Portfolio/ScreenPortfolioCreate.tsx` → pakai `PageWrapper`
-- [ ] `screens/Portfolio/ScreenPortfolioEdit.tsx` → pakai `PageWrapper`
+- [ ] `screens/Portfolio/ScreenPortfolioCreate.tsx` → pakai `enableKeyboardHandling`
+- [ ] `screens/Portfolio/ScreenPortfolioEdit.tsx` → pakai `enableKeyboardHandling`
-### Phase 2: Admin Screens (Priority: MEDIUM)
-Files yang perlu di-migrate:
+### ⏳ Phase 3: Admin Screens (Priority: MEDIUM)
-#### 2.1 Event Management
+#### Event Management:
- [ ] `screens/Admin/Event/ScreenEventList.tsx`
-- [ ] `screens/Admin/Event/ScreenEventCreate.tsx` → pakai `PageWrapper`
-- [ ] `screens/Admin/Event/ScreenEventEdit.tsx` → pakai `PageWrapper`
+- [ ] `screens/Admin/Event/ScreenEventCreate.tsx` → pakai `enableKeyboardHandling`
+- [ ] `screens/Admin/Event/ScreenEventEdit.tsx` → pakai `enableKeyboardHandling`
-#### 2.2 Voting Management
+#### Voting Management:
- [ ] `screens/Admin/Voting/ScreenVotingList.tsx`
-- [ ] `screens/Admin/Voting/ScreenVotingCreate.tsx` → pakai `PageWrapper`
-- [ ] `screens/Admin/Voting/ScreenVotingEdit.tsx` → pakai `PageWrapper`
+- [ ] `screens/Admin/Voting/ScreenVotingCreate.tsx` → pakai `enableKeyboardHandling`
+- [ ] `screens/Admin/Voting/ScreenVotingEdit.tsx` → pakai `enableKeyboardHandling`
-#### 2.3 Donation Management
+#### Donation Management:
- [ ] `screens/Admin/Donation/ScreenDonationList.tsx`
-- [ ] `screens/Admin/Donation/ScreenDonationCreate.tsx` → pakai `PageWrapper`
-- [ ] `screens/Admin/Donation/ScreenDonationEdit.tsx` → pakai `PageWrapper`
+- [ ] `screens/Admin/Donation/ScreenDonationCreate.tsx` → pakai `enableKeyboardHandling`
+- [ ] `screens/Admin/Donation/ScreenDonationEdit.tsx` → pakai `enableKeyboardHandling`
-#### 2.4 Job Management
-- [ ] `screens/Job/ScreenJobList.tsx` (jika ada)
-- [ ] `screens/Job/ScreenJobCreate.tsx` → sudah pakai `BoxButtonOnFooter`?
-- [ ] `screens/Job/ScreenJobEdit.tsx` → sudah pakai `BoxButtonOnFooter`?
-
-### Phase 3: Other Screens (Priority: LOW)
+### ⏳ Phase 4: Other Screens (Priority: LOW)
- [ ] `screens/Investasi/` - Investment screens
- [ ] `screens/Kolaborasi/` - Collaboration screens
- [ ] Other user-facing screens
@@ -122,7 +153,7 @@ Setiap screen yang sudah di-migrate, test:
- [ ] UI tampil sesuai design
- [ ] Tabs berfungsi dengan baik
- [ ] ScrollView/FlatList scroll dengan smooth
-- [ ] Keyboard tidak menutupi input (jika pakai PageWrapper)
+- [ ] Keyboard tidak menutupi input (jika pakai `enableKeyboardHandling`)
- [ ] Footer muncul di posisi yang benar
- [ ] Pull to refresh berfungsi (jika ada)
@@ -130,7 +161,7 @@ Setiap screen yang sudah di-migrate, test:
- [ ] UI tampil sesuai design
- [ ] Tabs berfungsi dengan baik
- [ ] ScrollView/FlatList scroll dengan smooth
-- [ ] Keyboard handling: auto scroll saat input focus (jika pakai PageWrapper)
+- [ ] Keyboard handling: auto scroll saat input focus (jika pakai `enableKeyboardHandling`)
- [ ] Footer muncul di posisi yang benar (tidak tertutup navigation bar)
- [ ] Pull to refresh berfungsi (jika ada)
@@ -144,11 +175,7 @@ Setiap screen yang sudah di-migrate, test:
## 📌 Notes
-### Kapan pakai OS_Wrapper vs PageWrapper?
-- **OS_Wrapper**: Untuk screen yang hanya menampilkan data (list, detail, dll)
-- **PageWrapper**: Untuk screen yang ada form input (create, edit, login, dll)
-
-### Props yang sering digunakan:
+### Usage Pattern:
#### Untuk List Screen:
```tsx
@@ -177,9 +204,9 @@ Setiap screen yang sudah di-migrate, test:
```
-#### Untuk Form Screen:
+#### Untuk Form Screen (dengan keyboard handling):
```tsx
-
-
+
```
### Migration Pattern:
@@ -217,28 +244,51 @@ import { OS_Wrapper } from "@/components";
/>
```
+```tsx
+// OLD (Form with keyboard handling)
+import { NewWrapper_V2 } from "@/components/_ShareComponent/NewWrapper_V2";
+
+
+
+
+
+// NEW (Unified API)
+import { OS_Wrapper } from "@/components";
+
+
+
+
+```
+
## 🐛 Troubleshooting
### Issue: Tabs tidak muncul di Android
**Solution**: Pastikan tidak ada custom padding yang overriding default behavior. Jika masih bermasalah, cek apakah `contentPadding` atau `contentPaddingBottom` terlalu besar.
### Issue: Keyboard menutupi input di Android
-**Solution**: Pastikan pakai `PageWrapper` dengan `enableKeyboardHandling={true}`. Adjust `keyboardScrollOffset` jika perlu.
+**Solution**: Pastikan pakai `OS_Wrapper` dengan `enableKeyboardHandling={true}`. Adjust `keyboardScrollOffset` jika perlu.
### Issue: Footer terlalu jauh dari bottom
**Solution**: Kurangi `contentPaddingBottom` (default: 80). Untuk list screen tanpa navigation bar overlay, bisa set ke 0.
### Issue: White space di bottom saat keyboard close (Android)
-**Solution**: Ini sudah di-fix di AndroidWrapper. Pastikan screen pakai OS_Wrapper/PageWrapper, bukan NewWrapper langsung.
+**Solution**: Ini sudah di-fix di AndroidWrapper. Pastikan screen pakai OS_Wrapper, bukan NewWrapper langsung.
## 📊 Progress Tracking
| Phase | Total Files | Migrated | Testing | Status |
|-------|-------------|----------|---------|--------|
-| Phase 1 (User) | TBD | 0 | 0 | ⏳ Pending |
-| Phase 2 (Admin) | TBD | 0 | 0 | ⏳ Pending |
-| Phase 3 (Other) | TBD | 0 | 0 | ⏳ Pending |
-| **Total** | **TBD** | **0** | **0** | **0%** |
+| Phase 1 (Job) | 8 | 8 | ✅ Complete | ✅ Complete |
+| Phase 2 (User) | TBD | 0 | 0 | ⏳ Pending |
+| Phase 3 (Admin) | TBD | 0 | 0 | ⏳ Pending |
+| Phase 4 (Other) | TBD | 0 | 0 | ⏳ Pending |
+| **Total** | **8+** | **8** | **8** | **100% (Phase 1)** |
## 🔄 Rollback Plan
@@ -252,4 +302,6 @@ Jika ada issue yang tidak bisa di-fix dalam 1 jam:
**Co-authored-by**: Qwen-Coder
**Created**: 2026-04-06
-**Status**: Ready for Implementation
+**Last Updated**: 2026-04-06
+**Status**: Phase 1 (Job Screens) Complete ✅
+**Next**: Phase 2 - Other User Screens