Compare commits

...

2 Commits

Author SHA1 Message Date
2e58f8c7b4 Docs: add task breakdown untuk fix phone input iOS 16+
- Add TASKS/fix-phone-input-ios-16.md dengan analisis lengkap
- 3 opsi solusi: KeyboardAvoidingView, Keyboard Controller, Custom Input
- Detailed implementation guidelines
- Testing checklist untuk iOS 16, 17, 18

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-25 16:39:48 +08:00
b6cd308b0b Refactor pagination implementation dan perbaikan UI
- Add default page parameter di apiAllUser
- Refactor MainView_V2.tsx dengan separate render functions
- Update pagination pageSize menjadi 10 di Forum
- Fix iOS height constant dan tab styling
- Rename Admin_ScreenPortofolioCreate ke ScreenPortofolioCreate
- Add TASKS documentation folder

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-25 16:22:54 +08:00
16 changed files with 548 additions and 151 deletions

View File

@@ -0,0 +1,369 @@
# Fix Phone Number Input - iOS 16+ Compatibility
## 📋 Ringkasan Task
Memperbaiki masalah phone number input pada `screens/Authentication/LoginView.tsx` yang tidak berfungsi dengan baik pada iOS versi 16 ke atas.
---
## 🎯 Tujuan
1. Fix keyboard overlay issues pada iOS 16+
2. Perbaiki layout measurement dan safe area
3. Pastikan input phone number responsive di semua device
4. Maintain UX yang konsisten dengan Android
---
## 📁 File yang Terlibat
### File Utama
- **Target**: `screens/Authentication/LoginView.tsx`
### File Pendukung
- `components/TextInput/TextInputCustom.tsx` - Alternatif custom input
- `styles/global-styles.ts` - Styling adjustments
- `package.json` - Update dependencies (jika perlu)
### Dependencies Terkait
- `react-native-international-phone-number`: ^0.9.3
- `react-native-keyboard-controller`: ^1.18.6
---
## 🔍 Analisis Masalah
### Issues pada iOS 16+
#### 1. **Keyboard Overlay Problem**
**Symptom**: Input field tertutup keyboard saat aktif
**Cause**:
- Safe area insets tidak terhitung dengan benar
- Keyboard animation tidak sinkron dengan layout
- `react-native-keyboard-controller` tidak terintegrasi dengan baik
#### 2. **Layout Measurement Issues**
**Symptom**: Input field berubah ukuran secara tidak terduga
**Cause**:
- Dynamic Type settings mempengaruhi layout
- Font scaling pada iOS 16+ berbeda
- Container tidak memiliki fixed height
#### 3. **Focus Behavior**
**Symptom**: Input tidak auto-scroll saat di-focus
**Cause**:
- ScrollView/KeyboardAvoidingView configuration salah
- Keyboard dismissing behavior tidak konsisten
#### 4. **Visual Glitches**
**Symptom**: Country flag dropdown tidak muncul atau terpotong
**Cause**:
- Z-index issues pada iOS
- Modal/Popover rendering problems
---
## 🎨 Solusi yang Direkomendasikan
### Option 1: **KeyboardAvoidingView Enhancement** (RECOMMENDED)
**Effort**: Medium
**Impact**: High
```typescript
import { KeyboardAvoidingView, Platform } from "react-native";
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? 90 : 50}
style={{ flex: 1 }}
>
<ViewWrapper>
{/* Content */}
</ViewWrapper>
</KeyboardAvoidingView>
```
**Keuntungan**:
- Native solution dari React Native
- Tidak perlu tambahan library
- Stabil untuk iOS 16+
**Kekurangan**:
- Perlu tuning offset untuk setiap device
- Tidak se-smooth keyboard controller
### Option 2: **React Native Keyboard Controller** (BETTER UX)
**Effort**: Medium-High
**Impact**: High
```typescript
import { KeyboardProvider } from "react-native-keyboard-controller";
// Wrap di root app (sudah ada di _layout.tsx)
<KeyboardProvider>
<App />
</KeyboardProvider>
// Di LoginView
import { KeyboardAwareScrollView } from "react-native-keyboard-controller";
<KeyboardAwareScrollView
bottomOffset={20}
keyboardOffset={10}
>
{/* Content */}
</KeyboardAwareScrollView>
```
**Keuntungan**:
- Smooth keyboard animations
- Better control over keyboard behavior
- Cross-platform consistency
**Kekurangan**:
- Perlu verify konfigurasi yang sudah ada
- Mungkin perlu update library
### Option 3: **Custom Phone Input** (FALLBACK)
**Effort**: High
**Impact**: Medium
Membuat custom phone input dengan:
- TextInputCustom component
- Country code picker modal
- Validation logic
**Keuntungan**:
- Full control atas behavior
- Tidak depend on third-party issues
**Kekurangan**:
- Development time lebih lama
- Perlu testing ekstensif
- Maintain code sendiri
---
## 📝 Breakdown Task
### Task 1: Research & Setup ✅
- [x] Identifikasi masalah pada iOS 16+
- [x] Cek dokumentasi library
- [x] Review existing implementation
- [ ] Test di iOS simulator (iOS 16+)
- [ ] Test di device fisik (jika ada)
### Task 2: Implement KeyboardAvoidingView Fix
- [ ] Wrap ViewWrapper dengan KeyboardAvoidingView
- [ ] Set behavior berdasarkan Platform
- [ ] Adjust keyboardVerticalOffset
- [ ] Test di berbagai ukuran layar
- [ ] Test landscape mode (jika applicable)
### Task 3: Adjust Layout & Styling
- [ ] Fix container height/width
- [ ] Adjust safe area insets
- [ ] Test dengan Dynamic Type settings
- [ ] Ensure consistent padding/margin
### Task 4: Test Focus Behavior
- [ ] Auto-scroll saat focus
- [ ] Keyboard dismiss saat tap outside
- [ ] Next/previous field navigation (jika ada)
- [ ] Input validation on blur
### Task 5: Country Picker Fix
- [ ] Verify dropdown z-index
- [ ] Test modal presentation
- [ ] Ensure flag icons visible
- [ ] Test search functionality
### Task 6: Testing & QA
- [ ] Test iOS 16, 17, 18
- [ ] Test Android (regression)
- [ ] Test dengan berbagai device sizes
- [ ] Test accessibility (VoiceOver)
- [ ] Performance test (no lag)
### Task 7: Documentation
- [ ] Update code comments
- [ ] Document iOS-specific workarounds
- [ ] Add troubleshooting notes
---
## 💻 Implementation Guidelines
### Recommended Implementation (Option 1 + 2 Hybrid)
```typescript
import { KeyboardAvoidingView, Platform } from "react-native";
import { KeyboardProvider } from "react-native-keyboard-controller";
// Di LoginView.tsx
export default function LoginView() {
return (
<ViewWrapper
withBackground
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
}
>
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? 100 : 50}
style={{ flex: 1 }}
>
<View style={[GStyles.authContainer, { paddingBottom: 40 }]}>
{/* Title Section */}
<View style={GStyles.authContainerTitle}>
<Text style={GStyles.authSubTitle}>WELCOME TO</Text>
<Spacing height={5} />
<Text style={GStyles.authTitle}>HIPMI BADUNG APPS</Text>
<Spacing height={5} />
</View>
<Spacing height={50} />
{/* Phone Input - Wrap dengan View untuk stability */}
<View style={{ marginBottom: 20 }}>
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
defaultCountry="ID"
placeholder="Masukkan nomor"
// Add iOS-specific props
textInputProps={{
keyboardType: "phone-pad",
autoComplete: "tel",
importantForAutofill: "yes",
}}
/>
</View>
<Spacing />
{/* Login Button */}
<ButtonCustom
onPress={handleLogin}
disabled={loadingTerm}
isLoading={loading || loadingTerm}
>
Login
</ButtonCustom>
<Spacing height={50} />
{/* Terms Text */}
<Text style={{ ...GStyles.textLabel, textAlign: "center", fontSize: 12 }}>
Dengan menggunakan aplikasi ini, Anda telah menyetujui{" "}
<Text
style={{
color: MainColor.yellow,
textDecorationLine: "underline",
}}
onPress={() => {
const toUrl = `${url}/terms-of-service.html`;
openBrowser(toUrl);
}}
>
Syarat & Ketentuan
</Text>{" "}
dan seluruh kebijakan privasi yang berlaku.
</Text>
{/* Version Info */}
<Text
style={{
position: "absolute",
bottom: 35,
right: 50,
fontSize: 10,
fontWeight: "thin",
fontStyle: "italic",
color: MainColor.white_gray,
}}
>
{version} | powered by muku.id
</Text>
</View>
</KeyboardAvoidingView>
</ViewWrapper>
);
}
```
### Styling Adjustments
```typescript
// styles/global-styles.ts
export const GStyles = StyleSheet.create({
authContainer: {
flex: 1,
justifyContent: "center",
paddingHorizontal: 24,
// Add padding bottom untuk keyboard space
paddingBottom: Platform.OS === "ios" ? 40 : 20,
},
// ... existing styles
});
```
---
## ✅ Acceptance Criteria
### Functional
- [ ] Input field tidak tertutup keyboard saat focus
- [ ] Country picker dropdown berfungsi dengan baik
- [ ] Auto-scroll bekerja smooth saat focus
- [ ] Keyboard dismiss saat tap outside
- [ ] Input validation berjalan normal
### Visual
- [ ] Layout tidak berubah saat keyboard muncul
- [ ] No visual glitches atau flickering
- [ ] Country flag icons visible
- [ ] Consistent spacing dan padding
### Compatibility
- [ ] iOS 16, 17, 18 - Tested ✅
- [ ] Android - No regression ✅
- [ ] iPad - Responsive ✅
- [ ] Landscape mode - Usable ✅
### Performance
- [ ] No lag saat typing
- [ ] Smooth keyboard animations
- [ ] No memory leaks
- [ ] Fast input response
---
## 🔗 References
- [React Native KeyboardAvoidingView Docs](https://reactnative.dev/docs/keyboardavoidingview)
- [react-native-keyboard-controller](https://github.com/kirillzyusko/react-native-keyboard-controller)
- [react-native-international-phone-number Issues](https://github.com/bluesky01/react-native-international-phone-number/issues)
- [iOS 16+ Keyboard Changes](https://developer.apple.com/documentation/uikit/uikit)
---
## 📊 Estimated Effort
- **Complexity**: Medium
- **Time Estimate**: 2-4 jam
- **Risk Level**: Medium (perlu testing ekstensif di device)
---
## 📝 Notes
- **Priority**: High (login adalah critical path)
- **Testing**: Wajib test di device fisik jika memungkinkan
- **Fallback**: Jika Option 1 & 2 gagal, siap untuk implement Option 3 (custom input)
- **Monitoring**: Add analytics untuk track input completion rate
---
**Created**: 2026-03-25
**Status**: Pending
**Priority**: High
**Related Issue**: iOS 16+ keyboard compatibility

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable react-hooks/exhaustive-deps */
import { BasicWrapper, StackCustom, ViewWrapper } from "@/components";
import { BasicWrapper, NewWrapper, StackCustom, ViewWrapper } from "@/components";
import AppHeader from "@/components/_ShareComponent/AppHeader";
import CustomSkeleton from "@/components/_ShareComponent/SkeletonCustom";
import { MainColor } from "@/constants/color-palet";
@@ -148,7 +148,7 @@ export default function Application() {
}}
/>
<ViewWrapper
<NewWrapper
refreshControl={
<RefreshControl
refreshing={refreshing}
@@ -166,18 +166,19 @@ export default function Application() {
})}
/>
) : (
<View style={GStyles.tabBar}>
<View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
{Array.from({ length: 4 }).map((e, index) => (
<CustomSkeleton
key={index}
height={40}
width={40}
radius={100}
/>
))}
</View>
</View>
null
// <View style={GStyles.tabBar}>
// <View style={[GStyles.tabContainer, { paddingTop: 10 }]}>
// {Array.from({ length: 4 }).map((e, index) => (
// <CustomSkeleton
// key={index}
// height={40}
// width={40}
// radius={100}
// />
// ))}
// </View>
// </View>
)
}
>
@@ -201,10 +202,10 @@ export default function Application() {
{data ? (
<Home_BottomFeatureSection listData={listData} />
) : (
<CustomSkeleton height={200} />
<CustomSkeleton height={150} />
)}
</StackCustom>
</ViewWrapper>
</NewWrapper>
</>
);
}

View File

@@ -1,5 +1,5 @@
import { Admin_ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate";
import { ScreenPortofolioCreate } from "@/screens/Portofolio/ScreenPortofolioCreate";
export default function PortofolioCreate() {
return <Admin_ScreenPortofolioCreate />;
return <ScreenPortofolioCreate />;
}

View File

@@ -19,6 +19,7 @@ import {
SafeAreaView,
} from "react-native-safe-area-context";
import type { ScrollViewProps, FlatListProps } from "react-native";
import Spacing from "./Spacing";
// --- ✅ Tambahkan refreshControl ke BaseProps ---
interface BaseProps {
@@ -111,7 +112,6 @@ const NewWrapper = (props: NewWrapperProps) => {
return `${String(item.id)}-${index}`;
})
}
refreshControl={refreshControl} // ✅ dari BaseProps
onEndReached={listProps.onEndReached}
onEndReachedThreshold={0.5}
@@ -156,15 +156,27 @@ const NewWrapper = (props: NewWrapperProps) => {
<View style={GStyles.stickyHeader}>{headerComponent}</View>
)}
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
<View style={{ flex: 0 }} collapsable={false}>
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
refreshControl={refreshControl} // ✅ sekarang valid
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
{renderContainer(staticProps.children)}
</TouchableWithoutFeedback>
</ScrollView>
</View>
{/* <ScrollView
contentContainerStyle={{ flexGrow: 0 }}
keyboardShouldPersistTaps="handled"
refreshControl={refreshControl} // ✅ sekarang valid
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
{renderContainer(staticProps.children)}
</TouchableWithoutFeedback>
</ScrollView>
</ScrollView> */}
{footerComponent ? (
<SafeAreaView

View File

@@ -24,7 +24,7 @@ export {
// OS Height
const OS_ANDROID_HEIGHT = 115
const OS_IOS_HEIGHT = 90
const OS_IOS_HEIGHT = 80
const OS_HEIGHT = Platform.OS === "ios" ? OS_IOS_HEIGHT : OS_ANDROID_HEIGHT
// Text Size

View File

@@ -120,4 +120,9 @@ Buatkan file baru pada "Folder tujuan" dengan nama "Nama file utama" dan ubah na
<!-- END Create Box -->
<!-- Random Prompt -->
Diskusi pada file screens/Authentication/LoginView.tsx , tentang penggunaan phone number input. Karena tidak berfungsi dengan baik pada versi ios 26 keatas
<!-- END Random Prompt -->
<!-- END Use Prompt Now -->

View File

@@ -197,6 +197,7 @@
D5CA1D54CFF74AB4B8B5B583 /* Remove signature files (Xcode workaround) */,
97C01196E2194AF5A13C7773 /* Remove signature files (Xcode workaround) */,
EB19F4C53C8B434CBAD50897 /* Remove signature files (Xcode workaround) */,
95ABFC1FE48F4F2ABAF407D8 /* Remove signature files (Xcode workaround) */,
);
buildRules = (
);
@@ -1247,6 +1248,23 @@
rm -rf \"$CONFIGURATION_BUILD_DIR/MapLibre.xcframework-ios.signature\";
";
};
95ABFC1FE48F4F2ABAF407D8 /* 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 */

View File

@@ -1,4 +1,4 @@
import { NewWrapper } from "@/components";
import { NewWrapper, ViewWrapper } from "@/components";
import ButtonCustom from "@/components/Button/ButtonCustom";
import ModalReactNative from "@/components/Modal/ModalReactNative";
import Spacing from "@/components/_ShareComponent/Spacing";
@@ -128,7 +128,7 @@ export default function LoginView() {
}
return (
<NewWrapper
<ViewWrapper
withBackground
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />
@@ -205,6 +205,6 @@ export default function LoginView() {
setLoadingTerm={setLoadingTerm}
/>
</ModalReactNative>
</NewWrapper>
</ViewWrapper>
);
}

View File

@@ -18,7 +18,7 @@ import _ from "lodash";
import { useEffect, useState } from "react";
import { RefreshControl, TouchableOpacity, View } from "react-native";
const PAGE_SIZE = 5;
const PAGE_SIZE = 10;
export default function Forum_ViewBeranda3() {
const { user } = useAuth();

View File

@@ -94,7 +94,7 @@ export const stylesHome = StyleSheet.create({
jobVacancyHeader: {
flexDirection: "row",
alignItems: "center",
marginBottom: 16,
marginBottom: 20,
},
jobVacancyTitle: {
fontSize: 18,

View File

@@ -5,7 +5,6 @@ import { router } from "expo-router";
import React from "react";
import { Text, TouchableOpacity, View } from "react-native";
const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
<TouchableOpacity
style={[GStyles.tabItem, isActive && GStyles.activeTab]}
@@ -17,7 +16,7 @@ const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
>
<Ionicons
name={icon as any}
size={20}
size={18}
color={isActive ? "#fff" : "#666"}
/>
</View>
@@ -30,8 +29,8 @@ const CustomTab = ({ icon, label, isActive, onPress }: ICustomTab) => (
export default function TabSection({ tabs }: { tabs: ITabs[] }) {
return (
<>
<View style={GStyles.tabBar}>
<View style={GStyles.tabContainer}>
<View style={GStyles.tabBar} pointerEvents="box-none">
<View style={GStyles.tabContainer} pointerEvents="box-none">
{tabs.map((e) => (
<CustomTab
key={e.id}

View File

@@ -33,7 +33,7 @@ import { Text, View } from "react-native";
import PhoneInput, { ICountry } from "react-native-international-phone-number";
import { Avatar } from "react-native-paper";
export function Admin_ScreenPortofolioCreate() {
export function ScreenPortofolioCreate() {
const { id } = useLocalSearchParams();
const [selectedCountry, setSelectedCountry] = useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>("");
@@ -72,7 +72,7 @@ export function Admin_ScreenPortofolioCreate() {
useCallback(() => {
onLoadMaster();
onLoadMasterSubBidangBisnis();
}, [])
}, []),
);
const onLoadMaster = async () => {
@@ -97,7 +97,7 @@ export function Admin_ScreenPortofolioCreate() {
const handlerSelectedSubBidang = ({ id }: { id: string }) => {
const selectedList = subBidangBisnis?.filter(
(item) => (item?.masterBidangBisnisId as any) === id
(item) => (item?.masterBidangBisnisId as any) === id,
);
setSelectedSubBidang(selectedList as any[]);
};
@@ -168,8 +168,7 @@ export function Admin_ScreenPortofolioCreate() {
.filter((option: any) => {
const selectedValues = listSubBidangSelected.map((s) => s.id);
return (
option.id === item.id ||
!selectedValues.includes(option.id)
option.id === item.id || !selectedValues.includes(option.id)
);
})
.map((e: any) => ({
@@ -188,7 +187,9 @@ export function Admin_ScreenPortofolioCreate() {
<CenterCustom>
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
<ActionIcon
disabled={selectedSubBidang.length === listSubBidangSelected.length}
disabled={
selectedSubBidang.length === listSubBidangSelected.length
}
onPress={() => {
setListSubBidangSelected([
...listSubBidangSelected,

View File

@@ -12,6 +12,7 @@ import {
ICON_SIZE_SMALL,
PAGINATION_DEFAULT_TAKE,
} from "@/constants/constans-value";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
import { usePagination } from "@/hooks/use-pagination";
import { apiAllUser } from "@/service/api-client/api-user";
import { Ionicons } from "@expo/vector-icons";
@@ -19,21 +20,89 @@ import { router, useFocusEffect } from "expo-router";
import _ from "lodash";
import { useCallback, useRef, useState } from "react";
import { RefreshControl, View } from "react-native";
import { createPaginationComponents } from "@/helpers/paginationHelpers";
const PAGE_SIZE = PAGINATION_DEFAULT_TAKE;
/**
* Render header dengan search input
*/
const renderHeader = (search: string, setSearch: (text: string) => void) => (
<TextInputCustom
value={search}
onChangeText={setSearch}
iconLeft={
<Ionicons
name="search"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari Pengguna"
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
);
/**
* Render item user
*/
const renderItem = ({ item }: { item: any }) => (
<View
style={{
backgroundColor: MainColor.soft_darkblue,
borderRadius: 8,
padding: 12,
marginBottom: 10,
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
}}
>
<ClickableCustom
onPress={() => {
router.push(`/profile/${item?.Profile?.id}`);
}}
>
<Grid>
<Grid.Col span={2}>
<AvatarComp fileId={item?.Profile?.imageId} size="base" />
</Grid.Col>
<Grid.Col span={9}>
<StackCustom gap={"sm"}>
<TextCustom size="large">{item?.username}</TextCustom>
<TextCustom size="small">+{item?.nomor}</TextCustom>
{item?.Profile?.businessField && (
<TextCustom size="small">
{item?.Profile?.businessField}
</TextCustom>
)}
</StackCustom>
</Grid.Col>
<Grid.Col
span={1}
style={{
justifyContent: "center",
alignItems: "flex-end",
}}
>
<Ionicons
name="chevron-forward"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
</Grid.Col>
</Grid>
</ClickableCustom>
</View>
);
export default function UserSearchMainView_V2() {
const isInitialMount = useRef(true);
const [search, setSearch] = useState("");
const {
listData,
loading,
refreshing,
hasMore,
onRefresh,
loadMore,
isInitialLoad,
} = usePagination({
const pagination = usePagination({
fetchFunction: async (page, searchQuery) => {
const response = await apiAllUser({
page: String(page),
@@ -41,7 +110,7 @@ export default function UserSearchMainView_V2() {
});
return response;
},
pageSize: PAGINATION_DEFAULT_TAKE,
pageSize: PAGE_SIZE,
searchQuery: search,
});
@@ -49,119 +118,42 @@ export default function UserSearchMainView_V2() {
useFocusEffect(
useCallback(() => {
if (isInitialMount.current) {
// Skip saat pertama kali mount
isInitialMount.current = false;
return;
}
// Hanya refresh saat kembali dari screen lain
onRefresh();
}, [onRefresh]),
);
const renderHeader = () => (
<>
<TextInputCustom
value={search}
onChangeText={setSearch}
iconLeft={
<Ionicons
name="search"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
}
placeholder="Cari Pengguna"
borderRadius={50}
containerStyle={{ marginBottom: 0 }}
/>
</>
);
const renderItem = ({ item }: { item: any }) => (
<View
style={{
backgroundColor: MainColor.soft_darkblue,
borderRadius: 8,
padding: 12,
marginBottom: 10,
elevation: 2,
shadowColor: "#000",
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
// height: 100
}}
>
<ClickableCustom
onPress={() => {
console.log("Ke Profile");
router.push(`/profile/${item?.Profile?.id}`);
}}
>
<Grid>
<Grid.Col span={2}>
<AvatarComp fileId={item?.Profile?.imageId} size="base" />
</Grid.Col>
<Grid.Col span={9}>
<StackCustom gap={"sm"}>
<TextCustom size="large">{item?.username}</TextCustom>
<TextCustom size="small">+{item?.nomor}</TextCustom>
{item?.Profile?.businessField && (
<TextCustom size="small">
{item?.Profile?.businessField}
</TextCustom>
)}
</StackCustom>
</Grid.Col>
<Grid.Col
span={1}
style={{
justifyContent: "center",
alignItems: "flex-end",
}}
>
<Ionicons
name="chevron-forward"
size={ICON_SIZE_SMALL}
color={MainColor.placeholder}
/>
</Grid.Col>
</Grid>
</ClickableCustom>
</View>
pagination.onRefresh();
}, [pagination.onRefresh]),
);
const { ListEmptyComponent, ListFooterComponent } =
createPaginationComponents({
loading,
refreshing,
listData,
loading: pagination.loading,
refreshing: pagination.refreshing,
listData: pagination.listData,
searchQuery: search,
emptyMessage: "Tidak ada pengguna ditemukan",
emptySearchMessage: "Tidak ada hasil pencarian",
skeletonCount: PAGINATION_DEFAULT_TAKE,
skeletonHeight: 100,
loadingFooterText: "Memuat lebih banyak pengguna...",
isInitialLoad,
isInitialLoad: pagination.isInitialLoad,
});
return (
<>
<NewWrapper
headerComponent={renderHeader()}
listData={listData}
renderItem={renderItem}
onEndReached={loadMore}
refreshControl={
<RefreshControl
progressBackgroundColor={MainColor.yellow}
refreshing={refreshing}
onRefresh={onRefresh}
/>
}
ListFooterComponent={ListFooterComponent}
ListEmptyComponent={ListEmptyComponent}
/>
</>
<NewWrapper
headerComponent={renderHeader(search, setSearch)}
listData={pagination.listData}
renderItem={renderItem}
onEndReached={pagination.loadMore}
refreshControl={
<RefreshControl
progressBackgroundColor={MainColor.yellow}
refreshing={pagination.refreshing}
onRefresh={pagination.onRefresh}
/>
}
ListFooterComponent={ListFooterComponent}
ListEmptyComponent={ListEmptyComponent}
/>
);
}

View File

@@ -6,13 +6,13 @@ export async function apiUser(id: string) {
}
export async function apiAllUser({
page,
page = "1",
search,
}: {
page?: string;
search?: string;
}) {
const pageQuery = page ? `?page=${page}` : "";
const pageQuery = `?page=${page}`;
const searchQuery = search ? `&search=${search}` : "";
try {

View File

@@ -159,7 +159,7 @@ export const GStyles = StyleSheet.create({
transform: [{ scale: 1.05 }],
},
iconContainer: {
padding: 8,
padding: 5,
borderRadius: 20,
// marginBottom: 4,
},

View File

@@ -11,7 +11,7 @@ export const TabsStyles: BottomTabNavigationOptions = {
tabBarStyle: Platform.select({
ios: {
borderTopWidth: 0,
paddingTop: 5,
paddingTop: 12,
height: OS_IOS_HEIGHT,
},
android: {