diff --git a/api/URLs.ts b/api/URLs.ts index ba4716b..88f8736 100644 --- a/api/URLs.ts +++ b/api/URLs.ts @@ -32,6 +32,7 @@ export const API_URLS = { My_Bonuses: 'api/cashback/', My_Refferals: 'api/referral/', Goverment_Service: '/api/goverment-service/', + Goverment_Category: '/api/goverment-category/', Notification_List: '/api/notifications/', Notification_Ready: (id: number) => `/api/notifications/${id}/read/`, }; diff --git a/app.json b/app.json index a9a8e5b..764390d 100644 --- a/app.json +++ b/app.json @@ -11,7 +11,9 @@ "ios": { "supportsTablet": true, "infoPlist": { - "UIBackgroundModes": ["remote-notification"] + "UIBackgroundModes": [ + "remote-notification" + ] }, "bundleIdentifier": "com.felix.infotarget" }, @@ -63,7 +65,8 @@ "backgroundColor": "#000000" } } - ] + ], + "expo-video" ], "experiments": { "typedRoutes": true, diff --git a/app/(dashboard)/_layout.tsx b/app/(dashboard)/_layout.tsx index cfb3f81..da35fc4 100644 --- a/app/(dashboard)/_layout.tsx +++ b/app/(dashboard)/_layout.tsx @@ -2,17 +2,33 @@ import Logo from '@/assets/images/logo.png'; import { useTheme } from '@/components/ThemeContext'; import { RefreshProvider } from '@/components/ui/RefreshContext'; import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; -import { Image } from 'expo-image'; -import { LinearGradient } from 'expo-linear-gradient'; -import { Tabs } from 'expo-router'; +import { router, Tabs } from 'expo-router'; import { Home, Megaphone, PlusCircle, User } from 'lucide-react-native'; +import { useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { Platform, StyleSheet, Text, View } from 'react-native'; +import { Animated, Easing, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; export default function TabsLayout() { const { isDark } = useTheme(); const { t } = useTranslation(); + const rotateAnim = useRef(new Animated.Value(0)).current; + + useEffect(() => { + Animated.loop( + Animated.timing(rotateAnim, { + toValue: 1, + duration: 4000, + easing: Easing.linear, + useNativeDriver: true, + }) + ).start(); + }, []); + + const rotate = rotateAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }); return ( @@ -77,7 +93,7 @@ export default function TabsLayout() { style={[ styles.tabLabel, { - marginLeft: 10, + marginLeft: 0, color, fontWeight: focused ? '700' : '600', }, @@ -92,7 +108,7 @@ export default function TabsLayout() { styles.iconContainer, focused && styles.iconContainerActive, { - marginLeft: 10, + marginLeft: 0, }, ]} > @@ -112,7 +128,8 @@ export default function TabsLayout() { style={[ styles.tabLabel, { - marginRight: 10, + // marginRight: 10, + marginLeft: 0, color, fontWeight: focused ? '700' : '600', }, @@ -127,7 +144,8 @@ export default function TabsLayout() { styles.iconContainer, focused && styles.iconContainerActive, { - marginRight: 10, + // marginRight: 10, + marginLeft: 0, }, ]} > @@ -138,28 +156,41 @@ export default function TabsLayout() { /> null, tabBarItemStyle: { flex: 1.2, - top: -10, + top: 8, }, - tabBarIcon: ({ focused }) => ( - - ( + router.push('/(dashboard)/e-service/e-services')} + > + - - - - - + + {/* {t('Davlat xizmatlari')} - - + */} + ), }} /> @@ -190,7 +221,8 @@ export default function TabsLayout() { style={[ styles.tabLabel, { - marginLeft: 20, + // marginLeft: 10, + marginRight: 0, color, fontWeight: focused ? '700' : '600', }, @@ -205,7 +237,8 @@ export default function TabsLayout() { styles.iconContainer, focused && styles.iconContainerActive, { - marginLeft: 20, + // marginLeft: 10, + marginRight: 0, }, ]} > @@ -225,7 +258,7 @@ export default function TabsLayout() { style={[ styles.tabLabel, { - marginRight: 20, + marginRight: 0, color, fontWeight: focused ? '700' : '600', }, @@ -239,7 +272,7 @@ export default function TabsLayout() { style={[ styles.iconContainer, focused && styles.iconContainerActive, - { marginRight: 20 }, + { marginRight: 0 }, ]} > @@ -270,29 +303,10 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - centerTabGradient: { - width: 72, - height: 72, - borderRadius: 36, - justifyContent: 'center', - alignItems: 'center', - ...Platform.select({ - ios: { - shadowColor: '#3b82f6', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.4, - shadowRadius: 12, - }, - android: { - elevation: 12, - }, - }), - }, centerTabInner: { - width: 68, - height: 68, + width: 65, + height: 65, borderRadius: 34, - backgroundColor: 'rgba(255, 255, 255, 0.1)', justifyContent: 'center', alignItems: 'center', }, diff --git a/app/(dashboard)/e-service/_layout.tsx b/app/(dashboard)/e-service/_layout.tsx new file mode 100644 index 0000000..bf0c2d2 --- /dev/null +++ b/app/(dashboard)/e-service/_layout.tsx @@ -0,0 +1,10 @@ +import { Stack } from 'expo-router'; + +export default function EServiceLayout() { + return ( + + + + + ); +} diff --git a/app/(dashboard)/e-service/e-services-category.tsx b/app/(dashboard)/e-service/e-services-category.tsx new file mode 100644 index 0000000..dfdbdcb --- /dev/null +++ b/app/(dashboard)/e-service/e-services-category.tsx @@ -0,0 +1,17 @@ +import { useTheme } from '@/components/ThemeContext'; +import { CustomHeader } from '@/components/ui/Header'; +import EServicesScreen from '@/screens/e-services/ui/EServicesScreen'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +export default function EServicesCategory() { + const { isDark } = useTheme(); + return ( + + + + + ); +} diff --git a/app/(dashboard)/e-services.tsx b/app/(dashboard)/e-service/e-services.tsx similarity index 73% rename from app/(dashboard)/e-services.tsx rename to app/(dashboard)/e-service/e-services.tsx index 5890488..67950a8 100644 --- a/app/(dashboard)/e-services.tsx +++ b/app/(dashboard)/e-service/e-services.tsx @@ -1,17 +1,17 @@ import { useTheme } from '@/components/ThemeContext'; import { CustomHeader } from '@/components/ui/Header'; -import EServicesScreen from '@/screens/e-services/ui/EServices'; +import EServicesCategoryScreen from '@/screens/e-services/ui/EServicesCategoryScreen'; import { SafeAreaView } from 'react-native-safe-area-context'; export default function EServices() { const { isDark } = useTheme(); return ( - + ); } diff --git a/app/(dashboard)/index.tsx b/app/(dashboard)/index.tsx index 1070a0f..a228710 100644 --- a/app/(dashboard)/index.tsx +++ b/app/(dashboard)/index.tsx @@ -25,7 +25,7 @@ export default function Index() { return ( diff --git a/app/(dashboard)/profile.tsx b/app/(dashboard)/profile.tsx index c9be477..18012ed 100644 --- a/app/(dashboard)/profile.tsx +++ b/app/(dashboard)/profile.tsx @@ -7,7 +7,7 @@ export default function ProfileScreen() { const { isDark } = useTheme(); return ( diff --git a/app/_layout.tsx b/app/_layout.tsx index 454b48b..aed6b6d 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,25 +1,19 @@ import { AuthProvider } from '@/components/AuthProvider'; import QueryProvider from '@/components/QueryProvider'; -import { ThemeProvider, useTheme } from '@/components/ThemeContext'; +import { ThemeProvider } from '@/components/ThemeContext'; import { useNotifications } from '@/hooks/useNotifications'; import i18n from '@/i18n/i18n'; import { ProfileDataProvider } from '@/screens/profile/lib/ProfileDataContext'; import { Stack } from 'expo-router'; import { I18nextProvider } from 'react-i18next'; -import { StatusBar } from 'react-native'; import 'react-native-reanimated'; function AppContent() { - const { isDark } = useTheme(); useNotifications(); + return ( <> - ); } diff --git a/assets/images/Express_diagnistika.png b/assets/images/Express_diagnistika.png new file mode 100644 index 0000000..fe7e6a1 Binary files /dev/null and b/assets/images/Express_diagnistika.png differ diff --git a/assets/images/android-icon-background.png b/assets/images/android-icon-background.png deleted file mode 100644 index 5ffefc5..0000000 Binary files a/assets/images/android-icon-background.png and /dev/null differ diff --git a/assets/images/android-icon-foreground.png b/assets/images/android-icon-foreground.png deleted file mode 100644 index 3a9e501..0000000 Binary files a/assets/images/android-icon-foreground.png and /dev/null differ diff --git a/assets/images/android-icon-monochrome.png b/assets/images/android-icon-monochrome.png deleted file mode 100644 index 77484eb..0000000 Binary files a/assets/images/android-icon-monochrome.png and /dev/null differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png deleted file mode 100644 index 408bd74..0000000 Binary files a/assets/images/favicon.png and /dev/null differ diff --git a/assets/images/icon.png b/assets/images/icon.png deleted file mode 100644 index 7165a53..0000000 Binary files a/assets/images/icon.png and /dev/null differ diff --git a/assets/images/logo.png b/assets/images/logo.png index 4ac508d..a60e427 100644 Binary files a/assets/images/logo.png and b/assets/images/logo.png differ diff --git a/assets/images/navbar.png b/assets/images/navbar.png index 2228c87..197389e 100644 Binary files a/assets/images/navbar.png and b/assets/images/navbar.png differ diff --git a/assets/images/partial-react-logo.png b/assets/images/partial-react-logo.png deleted file mode 100644 index 66fd957..0000000 Binary files a/assets/images/partial-react-logo.png and /dev/null differ diff --git a/assets/images/react-logo.png b/assets/images/react-logo.png deleted file mode 100644 index 9d72a9f..0000000 Binary files a/assets/images/react-logo.png and /dev/null differ diff --git a/assets/images/react-logo@2x.png b/assets/images/react-logo@2x.png deleted file mode 100644 index 2229b13..0000000 Binary files a/assets/images/react-logo@2x.png and /dev/null differ diff --git a/assets/images/react-logo@3x.png b/assets/images/react-logo@3x.png deleted file mode 100644 index a99b203..0000000 Binary files a/assets/images/react-logo@3x.png and /dev/null differ diff --git a/assets/images/splash-icon.png b/assets/images/splash-icon.png deleted file mode 100644 index 03d6f6b..0000000 Binary files a/assets/images/splash-icon.png and /dev/null differ diff --git a/assets/manual/image_en/step1.jpg b/assets/manual/image_en/step1.jpg new file mode 100644 index 0000000..fe27ea4 Binary files /dev/null and b/assets/manual/image_en/step1.jpg differ diff --git a/assets/manual/image_en/step2.jpg b/assets/manual/image_en/step2.jpg new file mode 100644 index 0000000..5b1b0ef Binary files /dev/null and b/assets/manual/image_en/step2.jpg differ diff --git a/assets/manual/image_en/step3.jpg b/assets/manual/image_en/step3.jpg new file mode 100644 index 0000000..eb083df Binary files /dev/null and b/assets/manual/image_en/step3.jpg differ diff --git a/assets/manual/image_en/step4.jpg b/assets/manual/image_en/step4.jpg new file mode 100644 index 0000000..befa774 Binary files /dev/null and b/assets/manual/image_en/step4.jpg differ diff --git a/assets/manual/image_en/step5.jpg b/assets/manual/image_en/step5.jpg new file mode 100644 index 0000000..d08dc0e Binary files /dev/null and b/assets/manual/image_en/step5.jpg differ diff --git a/assets/manual/image_en/step6.jpg b/assets/manual/image_en/step6.jpg new file mode 100644 index 0000000..9aa743a Binary files /dev/null and b/assets/manual/image_en/step6.jpg differ diff --git a/assets/manual/image_en/step7.jpg b/assets/manual/image_en/step7.jpg new file mode 100644 index 0000000..e31b9e7 Binary files /dev/null and b/assets/manual/image_en/step7.jpg differ diff --git a/assets/manual/image_en/step8.jpg b/assets/manual/image_en/step8.jpg new file mode 100644 index 0000000..afdd2ee Binary files /dev/null and b/assets/manual/image_en/step8.jpg differ diff --git a/assets/manual/image_ru/step1.jpg b/assets/manual/image_ru/step1.jpg new file mode 100644 index 0000000..2e60f2a Binary files /dev/null and b/assets/manual/image_ru/step1.jpg differ diff --git a/assets/manual/image_ru/step2.jpg b/assets/manual/image_ru/step2.jpg new file mode 100644 index 0000000..701dc4f Binary files /dev/null and b/assets/manual/image_ru/step2.jpg differ diff --git a/assets/manual/image_ru/step3.jpg b/assets/manual/image_ru/step3.jpg new file mode 100644 index 0000000..38e3b40 Binary files /dev/null and b/assets/manual/image_ru/step3.jpg differ diff --git a/assets/manual/image_ru/step4.jpg b/assets/manual/image_ru/step4.jpg new file mode 100644 index 0000000..0a14f11 Binary files /dev/null and b/assets/manual/image_ru/step4.jpg differ diff --git a/assets/manual/image_ru/step5.jpg b/assets/manual/image_ru/step5.jpg new file mode 100644 index 0000000..686610e Binary files /dev/null and b/assets/manual/image_ru/step5.jpg differ diff --git a/assets/manual/image_ru/step6.jpg b/assets/manual/image_ru/step6.jpg new file mode 100644 index 0000000..72986a6 Binary files /dev/null and b/assets/manual/image_ru/step6.jpg differ diff --git a/assets/manual/image_ru/step7.jpg b/assets/manual/image_ru/step7.jpg new file mode 100644 index 0000000..31f5c26 Binary files /dev/null and b/assets/manual/image_ru/step7.jpg differ diff --git a/assets/manual/image_ru/step8.jpg b/assets/manual/image_ru/step8.jpg new file mode 100644 index 0000000..682eb9a Binary files /dev/null and b/assets/manual/image_ru/step8.jpg differ diff --git a/assets/manual/image_uz/step1.jpg b/assets/manual/image_uz/step1.jpg new file mode 100644 index 0000000..26707c3 Binary files /dev/null and b/assets/manual/image_uz/step1.jpg differ diff --git a/assets/manual/image_uz/step2.jpg b/assets/manual/image_uz/step2.jpg new file mode 100644 index 0000000..1e57021 Binary files /dev/null and b/assets/manual/image_uz/step2.jpg differ diff --git a/assets/manual/image_uz/step3.jpg b/assets/manual/image_uz/step3.jpg new file mode 100644 index 0000000..e1e8250 Binary files /dev/null and b/assets/manual/image_uz/step3.jpg differ diff --git a/assets/manual/image_uz/step4.jpg b/assets/manual/image_uz/step4.jpg new file mode 100644 index 0000000..cae1b3c Binary files /dev/null and b/assets/manual/image_uz/step4.jpg differ diff --git a/assets/manual/image_uz/step5.jpg b/assets/manual/image_uz/step5.jpg new file mode 100644 index 0000000..8c6cf3f Binary files /dev/null and b/assets/manual/image_uz/step5.jpg differ diff --git a/assets/manual/image_uz/step6.jpg b/assets/manual/image_uz/step6.jpg new file mode 100644 index 0000000..2412c0d Binary files /dev/null and b/assets/manual/image_uz/step6.jpg differ diff --git a/assets/manual/image_uz/step7.jpg b/assets/manual/image_uz/step7.jpg new file mode 100644 index 0000000..cbee2d7 Binary files /dev/null and b/assets/manual/image_uz/step7.jpg differ diff --git a/assets/manual/image_uz/step8.jpg b/assets/manual/image_uz/step8.jpg new file mode 100644 index 0000000..09a340e Binary files /dev/null and b/assets/manual/image_uz/step8.jpg differ diff --git a/assets/manual/manual_video_en.mp4 b/assets/manual/manual_video_en.mp4 index e68e8c9..ec03703 100644 Binary files a/assets/manual/manual_video_en.mp4 and b/assets/manual/manual_video_en.mp4 differ diff --git a/assets/manual/manual_video_ru.mp4 b/assets/manual/manual_video_ru.mp4 index 4a9b8b6..4e1fce5 100644 Binary files a/assets/manual/manual_video_ru.mp4 and b/assets/manual/manual_video_ru.mp4 differ diff --git a/assets/manual/manual_video_uz.mp4 b/assets/manual/manual_video_uz.mp4 index fe50c5c..83e3b56 100644 Binary files a/assets/manual/manual_video_uz.mp4 and b/assets/manual/manual_video_uz.mp4 differ diff --git a/assets/manual/photo_8_2026-01-30_17-15-31.jpg b/assets/manual/photo_8_2026-01-30_17-15-31.jpg deleted file mode 100644 index 6614744..0000000 Binary files a/assets/manual/photo_8_2026-01-30_17-15-31.jpg and /dev/null differ diff --git a/assets/manual/photo_9_2026-01-30_17-15-31.jpg b/assets/manual/photo_9_2026-01-30_17-15-31.jpg deleted file mode 100644 index b92136d..0000000 Binary files a/assets/manual/photo_9_2026-01-30_17-15-31.jpg and /dev/null differ diff --git a/assets/manual/step1.jpg b/assets/manual/step1.jpg deleted file mode 100644 index b2dc3dc..0000000 Binary files a/assets/manual/step1.jpg and /dev/null differ diff --git a/assets/manual/step2.jpg b/assets/manual/step2.jpg deleted file mode 100644 index 9752559..0000000 Binary files a/assets/manual/step2.jpg and /dev/null differ diff --git a/assets/manual/step3.jpg b/assets/manual/step3.jpg deleted file mode 100644 index 421f1a4..0000000 Binary files a/assets/manual/step3.jpg and /dev/null differ diff --git a/assets/manual/step4.jpg b/assets/manual/step4.jpg deleted file mode 100644 index 001f414..0000000 Binary files a/assets/manual/step4.jpg and /dev/null differ diff --git a/assets/manual/step5.jpg b/assets/manual/step5.jpg deleted file mode 100644 index 746fb3a..0000000 Binary files a/assets/manual/step5.jpg and /dev/null differ diff --git a/assets/manual/step6.jpg b/assets/manual/step6.jpg deleted file mode 100644 index 304cea1..0000000 Binary files a/assets/manual/step6.jpg and /dev/null differ diff --git a/assets/manual/step7.jpg b/assets/manual/step7.jpg deleted file mode 100644 index b3c8870..0000000 Binary files a/assets/manual/step7.jpg and /dev/null differ diff --git a/components/ui/Header.tsx b/components/ui/Header.tsx index 1b0f21f..4b0c3b7 100644 --- a/components/ui/Header.tsx +++ b/components/ui/Header.tsx @@ -27,10 +27,8 @@ export const CustomHeader = ({ return ( - - - - + + {logoutbtn && ( @@ -46,7 +44,7 @@ export const CustomHeader = ({ {unreadCount > 0 && ( - {unreadCount > 99 ? '99+' : unreadCount} + {unreadCount > 9 ? '9+' : unreadCount} )} @@ -87,7 +85,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', alignContent: 'center', - gap: 5, + gap: 8, }, logoContainer: { @@ -101,8 +99,8 @@ const styles = StyleSheet.create({ }, logo: { - width: 32, - height: 32, + width: 40, + height: 40, resizeMode: 'contain', }, @@ -130,11 +128,10 @@ const styles = StyleSheet.create({ minWidth: 18, height: 18, borderRadius: 9, - backgroundColor: '#ef4444', // qizil badge + backgroundColor: '#ef4444', justifyContent: 'center', alignItems: 'center', paddingHorizontal: 4, - borderWidth: 1.5, }, badgeText: { diff --git a/i18n/locales/en.json b/i18n/locales/en.json index da24488..925f2aa 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -164,7 +164,7 @@ "Jinsi": "Gender", "male": "Male", "female": "Female", - "Foydalanish qo'lanmasi": "User Manual", + "Foydalanish qo'llanmasi": "User Manual", "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida": "Registration – in 1 minute", "Platformaga kirish uchun avval ro'yxatdan o'ting.": "First, register to access the platform.", "Profilni to'ldirish va tasdiqlash": "Complete and verify your profile", @@ -183,5 +183,21 @@ "Hozir": "Just now", "daqiqa oldin": "minute ago", "soat oldin": "hours ago", - "kun oldin": "days ago" + "kun oldin": "days ago", + "Rasm yoki video yuklang": "Upload image or video", + "Media turi": "Media type", + "Rasm": "Picture", + "Video": "Video", + "Rasm yuklash": "Upload image", + "Rasm tanlang": "Select image", + "Video yuklash": "Upload video", + "Video tanlang": "Select a video", + "Quyidagi qisqa video yoki rasmlarni ko'rib chiqing": "Check out the short videos or images below", + "Foydalanish video qo'llanma": "Use video tutorial", + "Foydalanish rasm qo‘llanma": "Use Image Guide", + "Video tilini tanlang": "Select a video language", + "Davlat xizmatlari kategoriyalari": "Categories of government services", + "Kerakli xizmat turini tanlang": "Select the desired service type", + "Bu kategoriya bo'yicha xizmat topilmadi": "No service in this category", + "Tez orada xizmat qo'shiladi": "Service will be added soon" } diff --git a/i18n/locales/ru.json b/i18n/locales/ru.json index 2919927..b511717 100644 --- a/i18n/locales/ru.json +++ b/i18n/locales/ru.json @@ -38,7 +38,7 @@ "Qayta urinish": "Повторить попытку", "Bosh sahifa": "Главная", "E'lon joylashtirish": "Разместить объявление", - "E'lonlar": "Объявления", + "E'lonlar": "Реклама", "Profil": "Профиль", "Davlat": "Страна", "Barchasi": "Все", @@ -146,7 +146,7 @@ "Rejimni tanlang": "Выберите режим", "Tungi rejim": "Ночной режим", "Yorug' rejim": "Дневной режим", - "Qo'shish": "Добавить", + "Qo'shish": "Рассылка", "Refferallarim": "Мои рефералы", "Davlat xizmatlari": "Государственные услуги", "foydalanuvchi": "пользователь", @@ -163,7 +163,7 @@ "Jinsi": "Пол", "male": "Мужской", "female": "Женщина", - "Foydalanish qo'lanmasi": "Инструкция по использованию", + "Foydalanish qo'llanmasi": "Инструкция по использованию", "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida": "Регистрация – за 1 минуту", "Platformaga kirish uchun avval ro'yxatdan o'ting.": "Сначала войдите в систему, зарегистрировавшись.", "Profilni to'ldirish va tasdiqlash": "Заполнение и подтверждение профиля", @@ -182,5 +182,21 @@ "Hozir": "Сейчас", "daqiqa oldin": "минут назад", "soat oldin": "час назад", - "kun oldin": "дней назад" + "kun oldin": "дней назад", + "Rasm yoki video yuklang": "Добавьте фото или видео", + "Media turi": "Тип медиа", + "Rasm": "Изображение", + "Video": "Видео", + "Rasm yuklash": "Добавить изображение", + "Rasm tanlang": "Выберите изображение", + "Video yuklash": "Добавить видео", + "Video tanlang": "Выберите видео", + "Quyidagi qisqa video yoki rasmlarni ko'rib chiqing": "Посмотрите следующие короткие видео или изображения", + "Foydalanish video qo'llanma": "Видеоинструкция по использованию", + "Foydalanish rasm qo‘llanma": "Инструкция по использованию изображений", + "Video tilini tanlang": "Выберите язык видео", + "Davlat xizmatlari kategoriyalari": "Категории государственных услуг", + "Kerakli xizmat turini tanlang": "Выберите нужный тип услуги", + "Bu kategoriya bo'yicha xizmat topilmadi": "Сервис в этой категории не найден.", + "Tez orada xizmat qo'shiladi": "Сервис скоро будет добавлен" } diff --git a/i18n/locales/uz.json b/i18n/locales/uz.json index eb04d97..764becc 100644 --- a/i18n/locales/uz.json +++ b/i18n/locales/uz.json @@ -163,7 +163,7 @@ "Jinsi": "Jinsi", "male": "Erkak", "female": "Ayol", - "Foydalanish qo'lanmasi": "Foydalanish qo'lanmasi", + "Foydalanish qo'llanmasi": "Foydalanish qo'llanmasi", "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida": "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida", "Platformaga kirish uchun avval ro'yxatdan o'ting.": "Platformaga kirish uchun avval ro'yxatdan o'ting.", "Profilni to'ldirish va tasdiqlash": "Profilni to'ldirish va tasdiqlash", @@ -182,5 +182,21 @@ "Hozir": "Hozir", "daqiqa oldin": "daqiqa oldin", "soat oldin": "soat oldin", - "kun oldin": "kun oldin" + "kun oldin": "kun oldin", + "Rasm yoki video yuklang": "Rasm yoki video yuklang", + "Media turi": "Media turi", + "Rasm": "Rasm", + "Video": "Video", + "Rasm yuklash": "Rasm yuklash", + "Rasm tanlang": "Rasm tanlang", + "Video yuklash": "Video yuklash", + "Video tanlang": "Video tanlang", + "Quyidagi qisqa video yoki rasmlarni ko'rib chiqing": "Quyidagi qisqa video yoki rasmlarni ko'rib chiqing", + "Foydalanish video qo'llanma": "Foydalanish video qo'llanma", + "Foydalanish rasm qo‘llanma": "Foydalanish rasm qo‘llanma", + "Video tilini tanlang": "Video tilini tanlang", + "Davlat xizmatlari kategoriyalari": "Davlat xizmatlari kategoriyalari", + "Kerakli xizmat turini tanlang": "Kerakli xizmat turini tanlang", + "Bu kategoriya bo'yicha xizmat topilmadi": "Bu kategoriya bo'yicha xizmat topilmadi", + "Tez orada xizmat qo'shiladi": "Tez orada xizmat qo'shiladi" } diff --git a/package-lock.json b/package-lock.json index 124e5c6..be7b4a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@legendapp/motion": "^2.4.0", "@nkzw/create-context-hook": "^1.1.0", "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-picker/picker": "^2.11.4", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/material-top-tabs": "^7.4.13", @@ -42,6 +43,7 @@ "expo-status-bar": "~3.0.9", "expo-symbols": "~1.0.8", "expo-system-ui": "~6.0.9", + "expo-video": "~3.0.15", "expo-web-browser": "~15.0.10", "i18next": "^25.7.4", "i18next-browser-languagedetector": "^8.2.0", @@ -55,7 +57,9 @@ "react-i18next": "^16.5.3", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-image-zoom-viewer": "^3.0.1", "react-native-keyboard-aware-scroll-view": "^0.9.5", + "react-native-modal": "^14.0.0-rc.1", "react-native-pager-view": "^8.0.0", "react-native-reanimated": "~4.1.0", "react-native-safe-area-context": "^5.6.1", @@ -5187,6 +5191,19 @@ "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, + "node_modules/@react-native-picker/picker": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.4.tgz", + "integrity": "sha512-Kf8h1AMnBo54b1fdiVylP2P/iFcZqzpMYcglC28EEFB1DEnOjsNr6Ucqc+3R9e91vHxEDnhZFbYDmAe79P2gjA==", + "license": "MIT", + "workspaces": [ + "example" + ], + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz", @@ -10280,6 +10297,17 @@ "expo": "*" } }, + "node_modules/expo-video": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/expo-video/-/expo-video-3.0.15.tgz", + "integrity": "sha512-KmxHVCtOBb1fnxXL6DpgLbEe7Qlv/vHNGTLfz0u/eY8fBC9s5cncD2BhPunEffrGvNMftBzYMYDaO86x+IYpnA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, "node_modules/expo-web-browser": { "version": "15.0.10", "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-15.0.10.tgz", @@ -14903,6 +14931,15 @@ } } }, + "node_modules/react-native-animatable": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.4.0.tgz", + "integrity": "sha512-DZwaDVWm2NBvBxf7I0wXKXLKb/TxDnkV53sWhCvei1pRyTX3MVFpkvdYBknNBqPrxYuAIlPxEp7gJOidIauUkw==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + } + }, "node_modules/react-native-css-interop": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/react-native-css-interop/-/react-native-css-interop-0.2.1.tgz", @@ -15189,6 +15226,29 @@ "react-native": "*" } }, + "node_modules/react-native-image-pan-zoom": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/react-native-image-pan-zoom/-/react-native-image-pan-zoom-2.1.12.tgz", + "integrity": "sha512-BF66XeP6dzuANsPmmFsJshM2Jyh/Mo1t8FsGc1L9Q9/sVP8MJULDabB1hms+eAoqgtyhMr5BuXV3E1hJ5U5H6Q==", + "license": "ISC", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-image-zoom-viewer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-native-image-zoom-viewer/-/react-native-image-zoom-viewer-3.0.1.tgz", + "integrity": "sha512-la6s5DNSuq4GCRLsi5CZ29FPjgTpdCuGIRdO5T9rUrAtxrlpBPhhSnHrbmPVxsdtOUvxHacTh2Gfa9+RraMZQA==", + "license": "MIT", + "dependencies": { + "react-native-image-pan-zoom": "^2.1.12" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-iphone-x-helper": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz", @@ -15221,6 +15281,19 @@ "react-native": ">=0.48.4" } }, + "node_modules/react-native-modal": { + "version": "14.0.0-rc.1", + "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-14.0.0-rc.1.tgz", + "integrity": "sha512-v5pvGyx1FlmBzdHyPqBsYQyS2mIJhVmuXyNo5EarIzxicKhuoul6XasXMviGcXboEUT0dTYWs88/VendojPiVw==", + "license": "MIT", + "dependencies": { + "react-native-animatable": "1.4.0" + }, + "peerDependencies": { + "react": "*", + "react-native": ">=0.70.0" + } + }, "node_modules/react-native-pager-view": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-8.0.0.tgz", diff --git a/package.json b/package.json index bead4be..636a6a7 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@legendapp/motion": "^2.4.0", "@nkzw/create-context-hook": "^1.1.0", "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-picker/picker": "^2.11.4", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/material-top-tabs": "^7.4.13", @@ -46,6 +47,7 @@ "expo-status-bar": "~3.0.9", "expo-symbols": "~1.0.8", "expo-system-ui": "~6.0.9", + "expo-video": "~3.0.15", "expo-web-browser": "~15.0.10", "i18next": "^25.7.4", "i18next-browser-languagedetector": "^8.2.0", @@ -59,7 +61,9 @@ "react-i18next": "^16.5.3", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-image-zoom-viewer": "^3.0.1", "react-native-keyboard-aware-scroll-view": "^0.9.5", + "react-native-modal": "^14.0.0-rc.1", "react-native-pager-view": "^8.0.0", "react-native-reanimated": "~4.1.0", "react-native-safe-area-context": "^5.6.1", diff --git a/screens/create-ads/ui/StepOne.tsx b/screens/create-ads/ui/StepOne.tsx index 9c7c503..3fc8c45 100644 --- a/screens/create-ads/ui/StepOne.tsx +++ b/screens/create-ads/ui/StepOne.tsx @@ -1,7 +1,7 @@ import { useTheme } from '@/components/ThemeContext'; import { formatPhone, normalizeDigits } from '@/constants/formatPhone'; import * as ImagePicker from 'expo-image-picker'; -import { Camera, Play, X } from 'lucide-react-native'; +import { Image as ImageIcon, Play, Video, X } from 'lucide-react-native'; import React, { forwardRef, useCallback, useImperativeHandle, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Image, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; @@ -16,12 +16,15 @@ type Errors = { media?: string; }; -const MAX_MEDIA = 10; +type MediaTabType = 'image' | 'video'; + +const MAX_MEDIA = 1; const StepOne = forwardRef(({ formData, updateForm }: StepProps, ref) => { const [phone, setPhone] = useState(formData.phone || ''); const [focused, setFocused] = useState(false); const [errors, setErrors] = useState({}); + const [selectedMediaTab, setSelectedMediaTab] = useState('image'); const { isDark } = useTheme(); const { t } = useTranslation(); @@ -37,8 +40,7 @@ const StepOne = forwardRef(({ formData, updateForm }: StepProps, ref) => { if (!formData.phone || formData.phone.length !== 9) e.phone = "Telefon raqam to'liq kiritilmadi"; - if (!formData.media || formData.media.length === 0) - e.media = 'Kamida bitta rasm yoki video yuklang'; + if (!formData.media || formData.media.length === 0) e.media = 'Rasm yoki video yuklang'; setErrors(e); return Object.keys(e).length === 0; @@ -49,18 +51,26 @@ const StepOne = forwardRef(({ formData, updateForm }: StepProps, ref) => { const pickMedia = async () => { if (formData.media.length >= MAX_MEDIA) return; + const mediaType = + selectedMediaTab === 'image' + ? ImagePicker.MediaTypeOptions.Images + : ImagePicker.MediaTypeOptions.Videos; + const result = await ImagePicker.launchImageLibraryAsync({ - mediaTypes: ImagePicker.MediaTypeOptions.All, - allowsMultipleSelection: true, + mediaTypes: mediaType, + allowsMultipleSelection: false, quality: 0.8, + videoMaxDuration: 60, // 60 seconds max for video }); if (!result.canceled) { - const assets = result.assets - .slice(0, MAX_MEDIA - formData.media.length) - .map((a) => ({ uri: a.uri, type: a.type as 'image' | 'video' })); + const asset = result.assets[0]; + const mediaItem = { + uri: asset.uri, + type: selectedMediaTab, + }; - updateForm('media', [...formData.media, ...assets]); + updateForm('media', [mediaItem]); } }; @@ -73,11 +83,9 @@ const StepOne = forwardRef(({ formData, updateForm }: StepProps, ref) => { [updateForm] ); - const removeMedia = (i: number) => - updateForm( - 'media', - formData.media.filter((_: any, idx: number) => idx !== i) - ); + const removeMedia = () => { + updateForm('media', []); + }; const theme = { background: isDark ? '#0f172a' : '#ffffff', @@ -89,6 +97,8 @@ const StepOne = forwardRef(({ formData, updateForm }: StepProps, ref) => { error: '#ef4444', primary: '#2563eb', divider: isDark ? '#475569' : '#cbd5e1', + tabActive: isDark ? '#2563eb' : '#3b82f6', + tabInactive: isDark ? '#334155' : '#e2e8f0', }; return ( @@ -165,35 +175,111 @@ const StepOne = forwardRef(({ formData, updateForm }: StepProps, ref) => { {t(errors.phone)} )} - {/* Media */} - - {t('Media')} ({formData.media.length}/{MAX_MEDIA}) - - + {/* Media Type Tabs */} + {t('Media turi')} + setSelectedMediaTab('image')} + activeOpacity={0.7} > - - {t('Yuklash')} + + + {t('Rasm')} + - {formData.media.map((m: MediaType, i: number) => ( - - - {m.type === 'video' && ( - - + setSelectedMediaTab('video')} + activeOpacity={0.7} + > + + + + {/* Media Upload/Preview */} + + {formData.media.length === 0 ? ( + + + {selectedMediaTab === 'image' ? ( + + ) : ( + + + {selectedMediaTab === 'image' ? t('Rasm yuklash') : t('Video yuklash')} + + + {selectedMediaTab === 'image' ? t('Rasm tanlang') : t('Video tanlang')} + + + ) : ( + + + {formData.media[0].type === 'video' && ( + + )} removeMedia(i)} + style={[styles.removeLarge, { backgroundColor: theme.error }]} + onPress={removeMedia} + activeOpacity={0.8} > - + + + {formData.media[0].type === 'image' ? ( + + ) : ( + - ))} + )} {errors.media && ( {t(errors.media)} @@ -206,7 +292,7 @@ export default StepOne; const styles = StyleSheet.create({ stepContainer: { gap: 10 }, - label: { fontWeight: '700' }, + label: { fontWeight: '700', fontSize: 15 }, error: { fontSize: 13, marginLeft: 6 }, inputBox: { flexDirection: 'row', @@ -216,36 +302,8 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, height: 56, }, - textArea: { height: 120, alignItems: 'flex-start' }, + textArea: { height: 120, alignItems: 'flex-start', paddingVertical: 12 }, input: { flex: 1, fontSize: 16 }, - media: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 }, - upload: { - width: 100, - height: 100, - borderRadius: 16, - borderWidth: 2, - borderStyle: 'dashed', - justifyContent: 'center', - alignItems: 'center', - }, - uploadText: { fontSize: 11, marginTop: 4 }, - preview: { width: 100, height: 100 }, - image: { width: '100%', height: '100%', borderRadius: 16 }, - play: { - position: 'absolute', - top: '40%', - left: '40%', - backgroundColor: 'rgba(0,0,0,.5)', - padding: 6, - borderRadius: 20, - }, - remove: { - position: 'absolute', - top: -6, - right: -6, - padding: 4, - borderRadius: 10, - }, prefixContainer: { flexDirection: 'row', alignItems: 'center', @@ -262,4 +320,123 @@ const styles = StyleSheet.create({ height: 24, marginLeft: 12, }, + + // Media Tabs + tabsContainer: { + flexDirection: 'row', + gap: 12, + }, + tab: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 14, + paddingHorizontal: 16, + borderRadius: 16, + borderWidth: 2, + gap: 8, + }, + tabActive: { + shadowColor: '#2563eb', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3, + }, + tabText: { + fontSize: 15, + fontWeight: '700', + letterSpacing: 0.3, + }, + + // Media Container + mediaContainer: { + marginTop: 4, + }, + uploadLarge: { + height: 240, + borderRadius: 20, + borderWidth: 2, + borderStyle: 'dashed', + justifyContent: 'center', + alignItems: 'center', + gap: 12, + }, + uploadIconWrapper: { + width: 72, + height: 72, + borderRadius: 36, + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 3, + }, + uploadLargeText: { + fontSize: 18, + fontWeight: '700', + marginTop: 4, + }, + uploadLargeSubtext: { + fontSize: 14, + fontWeight: '500', + textAlign: 'center', + paddingHorizontal: 32, + }, + previewLarge: { + height: 240, + borderRadius: 20, + overflow: 'hidden', + position: 'relative', + }, + imageLarge: { + width: '100%', + height: '100%', + borderRadius: 20, + }, + playLarge: { + position: 'absolute', + top: '50%', + left: '50%', + transform: [{ translateX: -28 }, { translateY: -28 }], + backgroundColor: 'rgba(0,0,0,.6)', + padding: 14, + borderRadius: 32, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3, + }, + removeLarge: { + position: 'absolute', + top: 12, + right: 12, + padding: 8, + borderRadius: 12, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3, + }, + mediaTypeBadge: { + position: 'absolute', + bottom: 12, + left: 12, + flexDirection: 'row', + alignItems: 'center', + gap: 6, + paddingVertical: 6, + paddingHorizontal: 12, + borderRadius: 10, + }, + mediaTypeBadgeText: { + color: '#ffffff', + fontSize: 13, + fontWeight: '600', + }, }); diff --git a/screens/e-services/lib/api.ts b/screens/e-services/lib/api.ts index 72ebfd3..0b75c96 100644 --- a/screens/e-services/lib/api.ts +++ b/screens/e-services/lib/api.ts @@ -1,14 +1,30 @@ import httpClient from '@/api/httpClient'; import { API_URLS } from '@/api/URLs'; import { AxiosResponse } from 'axios'; -import { GovermentServiceData } from './types'; +import { GovermentCategoryData, GovermentServiceData } from './types'; export const eservices_api = { async list(params: { page: number; page_size: number; + category?: number; }): Promise> { const res = await httpClient.get(API_URLS.Goverment_Service, { params }); return res; }, + + async category(): Promise> { + const res = await httpClient.get(API_URLS.Goverment_Category); + return res; + }, +}; + +export const search_api = { + search: async (query: string) => { + const res = await fetch(`https://server.myorg.uz/search?query=${encodeURIComponent(query)}`, { + headers: { Access: 'Bearer 12opNgslS4S2DDllcsnPKD789D2ek2HJhH23C' }, + }); + if (!res.ok) throw new Error('Search error'); + return res.json(); + }, }; diff --git a/screens/e-services/lib/types.ts b/screens/e-services/lib/types.ts index 3e7e0e3..3130c48 100644 --- a/screens/e-services/lib/types.ts +++ b/screens/e-services/lib/types.ts @@ -19,3 +19,24 @@ export interface GovermentServiceDataRes { url: string; logo: string; } + +export interface GovermentCategoryData { + status: boolean; + data: { + links: { + previous: null | string; + next: null | string; + }; + total_items: number; + total_pages: number; + page_size: number; + current_page: number; + results: GovermentCategoryDataRes[]; + }; +} + +export interface GovermentCategoryDataRes { + id: number; + name: string; + image: string; +} diff --git a/screens/e-services/ui/EServices.tsx b/screens/e-services/ui/EServices.tsx deleted file mode 100644 index 0343d57..0000000 --- a/screens/e-services/ui/EServices.tsx +++ /dev/null @@ -1,199 +0,0 @@ -// EServicesScreen.tsx -import { useTheme } from '@/components/ThemeContext'; -import { useInfiniteQuery } from '@tanstack/react-query'; -import { Image } from 'expo-image'; -import { ChevronLeft, XIcon } from 'lucide-react-native'; -import React, { useCallback, useRef, useState } from 'react'; -import { - ActivityIndicator, - Dimensions, - FlatList, - Modal, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import { SafeAreaView } from 'react-native-safe-area-context'; -import { WebView } from 'react-native-webview'; -import { eservices_api } from '../lib/api'; - -const { width: SCREEN_WIDTH } = Dimensions.get('window'); -const PAGE_SIZE = 10; - -export interface GovermentServiceDataRes { - id: number; - name: string; - url: string; - logo: string; -} - -export default function EServicesScreen() { - const { isDark } = useTheme(); - const [webUrl, setWebUrl] = useState(null); - const [modalVisible, setModalVisible] = useState(false); - const webviewRef = useRef(null); // WebView ref for goBack - - const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } = - useInfiniteQuery({ - queryKey: ['goverment_service'], - queryFn: async ({ pageParam = 1 }) => { - const response = await eservices_api.list({ - page: pageParam, - page_size: PAGE_SIZE, - }); - return response.data.data; - }, - getNextPageParam: (lastPage) => - lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined, - initialPageParam: 1, - }); - - const services: GovermentServiceDataRes[] = data?.pages.flatMap((p) => p.results) ?? []; - - const openWebView = (url: string) => { - setWebUrl(url); - setModalVisible(true); - }; - - const renderItem = useCallback( - ({ item }: { item: GovermentServiceDataRes }) => ( - openWebView(item.url)} - > - - {item.name} - - ), - [isDark] - ); - - if (isLoading) { - return ( - - - - ); - } - - if (isError) { - return ( - - Xatolik yuz berdi - - ); - } - - return ( - - item.id.toString()} - renderItem={renderItem} - numColumns={2} - columnWrapperStyle={{ justifyContent: 'space-between', marginBottom: 12 }} - contentContainerStyle={{ padding: 16 }} - onEndReached={() => hasNextPage && fetchNextPage()} - onEndReachedThreshold={0.4} - ListFooterComponent={ - isFetchingNextPage ? : null - } - showsVerticalScrollIndicator={false} - /> - - {/* WebView Modal */} - {/* WebView Modal */} - - - {/* Header */} - - {/* Back tugmasi */} - { - if (webviewRef.current) webviewRef.current.goBack(); - }} - hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} - > - - - - {/* Close tugmasi */} - setModalVisible(false)} - hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} - > - - - - - {/* WebView */} - {webUrl && ( - ( - - )} - /> - )} - - - - ); -} - -const CARD_WIDTH = (SCREEN_WIDTH - 48) / 2; - -const styles = StyleSheet.create({ - center: { flex: 1, justifyContent: 'center', alignItems: 'center' }, - card: { - width: CARD_WIDTH, - borderRadius: 12, - padding: 12, - alignItems: 'center', - }, - logo: { - width: 60, - height: 60, - marginBottom: 8, - }, - name: { - fontSize: 14, - fontWeight: '600', - textAlign: 'center', - }, - darkShadow: { - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.4, - shadowRadius: 6, - elevation: 3, - }, - lightShadow: { - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 2, - }, -}); diff --git a/screens/e-services/ui/EServicesCategoryScreen.tsx b/screens/e-services/ui/EServicesCategoryScreen.tsx new file mode 100644 index 0000000..8150dba --- /dev/null +++ b/screens/e-services/ui/EServicesCategoryScreen.tsx @@ -0,0 +1,258 @@ +import Express_diagnistika from '@/assets/images/Express_diagnistika.png'; +import { useTheme } from '@/components/ThemeContext'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { Image } from 'expo-image'; +import { router } from 'expo-router'; +import { ChevronLeft, XIcon } from 'lucide-react-native'; +import React, { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ActivityIndicator, FlatList, Modal, Text, TouchableOpacity, View } from 'react-native'; +import { RefreshControl } from 'react-native-gesture-handler'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { WebView } from 'react-native-webview'; +import { eservices_api } from '../lib/api'; + +const dark = { + bg: '#0f172a', + card: '#1f2937', + border: '#1e293b', + muted: '#334155', + text: '#f8fafc', + subText: '#cbd5f5', +}; + +export default function EServicesCategoryScreen() { + const { isDark } = useTheme(); + const { t } = useTranslation(); + const [modalVisible, setModalVisible] = useState(false); + const webviewRef = useRef(null); + const [webUrl, setWebUrl] = React.useState(null); + const [refreshing, setRefreshing] = useState(false); + const queryClient = useQueryClient(); + + const { data, isLoading } = useQuery({ + queryKey: ['goverment_category'], + queryFn: () => eservices_api.category(), + }); + + const onRefresh = async () => { + setRefreshing(true); + try { + await queryClient.refetchQueries({ queryKey: ['goverment_category'] }); + } finally { + setRefreshing(false); + } + }; + + if (isLoading) { + return ( + + + + ); + } + + const staticCategory = { + id: 0, + name: 'Express Diagnostika', + image: Express_diagnistika, + url: 'https://myorg.uz/ru', + }; + + const categories = [staticCategory, ...(data?.data?.data?.results ?? [])]; + + const handlePress = (item: any) => { + if (item.id === 0 && item.url) { + setWebUrl(item.url); + setModalVisible(true); + return; + } + + router.push({ + pathname: '/(dashboard)/e-service/e-services-category', + params: { + categoryId: item.id, + categoryName: item.name, + }, + }); + }; + + return ( + + `${item.id}-${item.name}`} + contentContainerStyle={{ gap: 12 }} + refreshControl={ + + } + ListHeaderComponent={() => ( + + + {t('Davlat xizmatlari kategoriyalari')} + + + + {t('Kerakli xizmat turini tanlang')} + + + )} + renderItem={({ item }) => ( + handlePress(item)} + style={{ + backgroundColor: isDark ? dark.card : '#ffffff', + padding: 14, + borderRadius: 16, + flexDirection: 'row', + alignItems: 'center', + borderWidth: isDark ? 1 : 0, + borderColor: isDark ? dark.border : 'transparent', + }} + > + + {item.image ? ( + + ) : ( + {item.name[0]} + )} + + + + + {item.name} + + + {item.id === 0 && ( + + Tezkor va qulay xizmat + + )} + + + )} + /> + + + {/* WebView Header */} + + webviewRef.current?.goBack()} + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} + > + + + + + {webUrl} + + + setModalVisible(false)} + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} + > + + + + + {/* WebView */} + {webUrl && ( + { + // iOS va Android uchun barcha URLlarni WebView ichida ochish + return true; + }} + source={{ uri: webUrl }} + style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }} + startInLoadingState + renderLoading={() => ( + + )} + /> + )} + + + + ); +} diff --git a/screens/e-services/ui/EServicesScreen.tsx b/screens/e-services/ui/EServicesScreen.tsx new file mode 100644 index 0000000..0d6fc08 --- /dev/null +++ b/screens/e-services/ui/EServicesScreen.tsx @@ -0,0 +1,313 @@ +// EServicesScreen.tsx +import { useTheme } from '@/components/ThemeContext'; +import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query'; +import { Image } from 'expo-image'; +import { router, useLocalSearchParams } from 'expo-router'; +import { ChevronLeft, XIcon } from 'lucide-react-native'; +import React, { useCallback, useRef, useState } from 'react'; +import { + ActivityIndicator, + Dimensions, + FlatList, + Modal, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { WebView } from 'react-native-webview'; + +import { useTranslation } from 'react-i18next'; +import { RefreshControl } from 'react-native-gesture-handler'; +import { eservices_api } from '../lib/api'; + +const { width: SCREEN_WIDTH } = Dimensions.get('window'); +const PAGE_SIZE = 10; + +export interface GovermentServiceDataRes { + id: number; + name: string; + url: string; + logo: string; +} + +export default function EServicesScreen() { + const { isDark } = useTheme(); + const [webUrl, setWebUrl] = useState(null); + const [modalVisible, setModalVisible] = useState(false); + const webviewRef = useRef(null); + const params = useLocalSearchParams(); + const { t } = useTranslation(); + const [refreshing, setRefreshing] = useState(false); + const queryClient = useQueryClient(); + + const { data, isLoading, isError, fetchNextPage, hasNextPage, isFetchingNextPage } = + useInfiniteQuery({ + queryKey: ['goverment_service', params.categoryId], + queryFn: async ({ pageParam = 1 }) => { + const response = await eservices_api.list({ + page: pageParam, + page_size: PAGE_SIZE, + category: Number(params.categoryId), + }); + return response.data.data; + }, + getNextPageParam: (lastPage) => + lastPage.current_page < lastPage.total_pages ? lastPage.current_page + 1 : undefined, + initialPageParam: 1, + }); + + const services: GovermentServiceDataRes[] = data?.pages.flatMap((p) => p.results) ?? []; + + const onRefresh = async () => { + setRefreshing(true); + try { + await queryClient.refetchQueries({ queryKey: ['goverment_service'] }); + } finally { + setRefreshing(false); + } + }; + + const openWebView = (url: string) => { + setWebUrl(url); + setModalVisible(true); + }; + + const renderItem = useCallback( + ({ item }: { item: GovermentServiceDataRes }) => ( + + {/* Logo (bosilganda WebView ochiladi) */} + openWebView(item.url)} + > + + + + {/* Name (alog‘ifa, faqat ko‘rsatish) */} + + {item.name} + + + ), + [isDark] + ); + + if (isLoading) { + return ( + + + + ); + } + + if (isError) { + return ( + + Xatolik yuz berdi + + ); + } + + return ( + + {/* Header */} + + router.back()} + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} + > + + + + + {params.categoryName} + + + + {/* Empty / List */} + {services.length === 0 && !isLoading ? ( + + + {t("Bu kategoriya bo'yicha xizmat topilmadi")} + + + {t("Tez orada xizmat qo'shiladi")} + + + ) : ( + + } + keyExtractor={(item) => item.id.toString()} + renderItem={renderItem} + numColumns={3} + columnWrapperStyle={{ justifyContent: 'space-between', marginBottom: 12 }} + contentContainerStyle={{ padding: 16, paddingBottom: 32 }} + onEndReached={() => hasNextPage && fetchNextPage()} + onEndReachedThreshold={0.4} + ListFooterComponent={ + isFetchingNextPage ? ( + + ) : null + } + showsVerticalScrollIndicator={false} + /> + )} + + {/* WebView Modal */} + + + {/* WebView Header */} + + webviewRef.current?.goBack()} + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} + > + + + + + {webUrl} + + + setModalVisible(false)} + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} + > + + + + + {/* WebView */} + {webUrl && ( + { + // iOS va Android uchun barcha URLlarni WebView ichida ochish + return true; + }} + source={{ uri: webUrl }} + style={{ flex: 1, backgroundColor: isDark ? '#0f172a' : '#f8fafc' }} + startInLoadingState + renderLoading={() => ( + + )} + /> + )} + + + + ); +} + +const CARD_WIDTH = (SCREEN_WIDTH - 50) / 3; + +const styles = StyleSheet.create({ + center: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + card: { + width: CARD_WIDTH, + borderRadius: 12, + padding: 12, + alignItems: 'center', + }, + logo: { + width: 80, + height: 80, + marginBottom: 8, + }, + name: { + fontSize: 14, + fontWeight: '600', + textAlign: 'center', + }, + darkShadow: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.4, + shadowRadius: 6, + elevation: 3, + }, + lightShadow: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, +}); diff --git a/screens/e-services/ui/SearchResultsScreen.tsx b/screens/e-services/ui/SearchResultsScreen.tsx new file mode 100644 index 0000000..7d5b913 --- /dev/null +++ b/screens/e-services/ui/SearchResultsScreen.tsx @@ -0,0 +1,658 @@ +// SearchResultsScreen.tsx +import { useTheme } from '@/components/ThemeContext'; +import { + Award, + Building2, + Calendar, + ChevronLeft, + DollarSign, + Info, + Mail, + MapPin, + Phone, + User, +} from 'lucide-react-native'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FlatList, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; + +type TabType = 'entity' | 'entrepreneur' | 'trademark'; + +interface SearchResult { + entity: any; + entrepreneur: any; + trademark: any; +} + +interface SearchResultsScreenProps { + searchData: SearchResult; + onBack: () => void; +} + +export default function SearchResultsScreen({ searchData, onBack }: SearchResultsScreenProps) { + const { isDark } = useTheme(); + const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState('entity'); + + const tabs = [ + { + key: 'entity' as TabType, + label: 'Korxonalar', + icon: Building2, + count: + searchData.entity.name.total + + searchData.entity.director.total + + searchData.entity.founder.total, + }, + { + key: 'entrepreneur' as TabType, + label: 'Tadbirkorlar', + icon: User, + count: searchData.entrepreneur.total, + }, + { + key: 'trademark' as TabType, + label: 'Tovar belgilari', + icon: Award, + count: searchData.trademark.total, + }, + ]; + + const renderTabs = () => ( + + {tabs.map((tab) => { + const Icon = tab.icon; + const isActive = activeTab === tab.key; + return ( + setActiveTab(tab.key)} + > + + + {tab.label} + + {tab.count > 0 && ( + + {tab.count > 9999 ? '9999+' : tab.count} + + )} + + ); + })} + + ); + + const renderEntityCard = (item: any, variant: string) => ( + + {/* Header */} + + + {item.activity_state === 1 ? 'Faol' : 'Faol emas'} + + {variant && ( + + + {variant === 'director' ? 'Direktor' : variant === 'founder' ? "Ta'sischi" : 'Nomi'} + + + )} + + + {/* Company Name */} + {item.name} + + {/* INN */} + + + INN: + + {item.inn} + + + + {/* Director */} + {item.director && ( + + + + Direktor: + + + {item.director} + + + )} + + {/* Address */} + {item.address && ( + + + Manzil: + + {item.address} + + + )} + + {/* Registration Date */} + {item.registration_date && ( + + + + Ro'yxatga olingan: + + + {new Date(item.registration_date).toLocaleDateString('uz-UZ')} + + + )} + + {/* Activity */} + + + {item.oked_code} + + + {item.oked_name} + + + + {/* Contact Info */} + + {item.email && ( + + + + {item.email} + + + )} + {item.phones?.length > 0 && ( + + + + {item.phones[0]} + + + )} + + + {/* Statutory Fund */} + {item.statutory_fund && ( + + + + Ustav fondi: + + + {parseFloat(item.statutory_fund).toLocaleString('uz-UZ')} so'm + + + )} + + ); + + const renderEntrepreneurCard = (item: any) => ( + + + + + + + + {item.entrepreneur} + + + PINFL: {item.pinfl} + + + + + {item.registration_date && ( + + + + Ro'yxatga olingan: + + + {new Date(item.registration_date).toLocaleDateString('uz-UZ')} + + + )} + + {item.email && ( + + + + {item.email} + + + )} + + {item.phone && ( + + + + {item.phone} + + + )} + + ); + + const renderTrademarkCard = (item: any) => ( + + + {item.logo && ( + + )} + + + {item.transliteration} + + + {item.status_name} + + + + + + + + Ariza beruvchi: + + + {item.applicant} + + + + {item.registration_date && ( + + + + Ro'yxatga olingan: + + + {new Date(item.registration_date).toLocaleDateString('uz-UZ')} + + + )} + + {item.relevance_date && ( + + + + Amal qilish muddati: + + + {new Date(item.relevance_date).toLocaleDateString('uz-UZ')} + + + )} + + ); + + const renderContent = () => { + switch (activeTab) { + case 'entity': + const allEntities = [ + ...searchData.entity.name.rows.map((r: any) => ({ ...r, variant: 'name' })), + ...searchData.entity.director.rows.map((r: any) => ({ ...r, variant: 'director' })), + ...searchData.entity.founder.rows.map((r: any) => ({ ...r, variant: 'founder' })), + ]; + + return ( + `entity-${item.id}-${index}`} + renderItem={({ item }) => renderEntityCard(item, item.variant)} + contentContainerStyle={styles.listContent} + showsVerticalScrollIndicator={false} + ListEmptyComponent={ + + + + Korxonalar topilmadi + + + } + /> + ); + + case 'entrepreneur': + return ( + `entrepreneur-${item.id}`} + renderItem={({ item }) => renderEntrepreneurCard(item)} + contentContainerStyle={styles.listContent} + showsVerticalScrollIndicator={false} + ListEmptyComponent={ + + + + Tadbirkorlar topilmadi + + + } + /> + ); + + case 'trademark': + return ( + `trademark-${item.id}`} + renderItem={({ item }) => renderTrademarkCard(item)} + contentContainerStyle={styles.listContent} + showsVerticalScrollIndicator={false} + ListEmptyComponent={ + + + + Tovar belgilari topilmadi + + + } + /> + ); + } + }; + + return ( + + {/* Header */} + + + + + + Qidiruv natijalari + + + + + {/* Tabs */} + {renderTabs()} + + {/* Content */} + {renderContent()} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 12, + }, + backButton: { + padding: 4, + }, + headerTitle: { + fontSize: 18, + fontWeight: '700', + }, + tabsContainer: { + flexDirection: 'row', + paddingHorizontal: 16, + gap: 8, + marginBottom: 16, + }, + tab: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 12, + paddingHorizontal: 8, + borderRadius: 12, + gap: 6, + }, + activeTab: { + shadowColor: '#3b82f6', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3, + }, + tabText: { + fontSize: 13, + fontWeight: '600', + }, + badge: { + paddingHorizontal: 6, + paddingVertical: 2, + borderRadius: 10, + minWidth: 20, + alignItems: 'center', + }, + badgeText: { + color: '#ffffff', + fontSize: 10, + fontWeight: '700', + }, + listContent: { + paddingHorizontal: 16, + paddingBottom: 24, + }, + card: { + borderRadius: 16, + padding: 16, + marginBottom: 12, + borderWidth: 1, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 2, + }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 12, + }, + statusBadge: { + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: 8, + }, + statusText: { + color: '#ffffff', + fontSize: 12, + fontWeight: '600', + }, + variantBadge: { + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: 8, + }, + variantText: { + fontSize: 12, + fontWeight: '600', + }, + cardTitle: { + fontSize: 16, + fontWeight: '700', + marginBottom: 12, + }, + infoRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + marginBottom: 8, + }, + infoLabel: { + fontSize: 13, + fontWeight: '500', + }, + infoValue: { + fontSize: 13, + fontWeight: '600', + flex: 1, + }, + activityBox: { + padding: 12, + borderRadius: 8, + marginTop: 8, + marginBottom: 8, + }, + activityCode: { + fontSize: 14, + fontWeight: '700', + marginBottom: 4, + }, + activityName: { + fontSize: 12, + lineHeight: 18, + }, + contactRow: { + gap: 8, + marginTop: 8, + }, + contactItem: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + contactText: { + fontSize: 12, + }, + fundRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + marginTop: 12, + paddingTop: 12, + borderTopWidth: 1, + borderTopColor: '#334155', + }, + fundLabel: { + fontSize: 13, + fontWeight: '500', + }, + fundValue: { + fontSize: 14, + fontWeight: '700', + }, + entrepreneurHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + marginBottom: 16, + }, + avatarCircle: { + width: 56, + height: 56, + borderRadius: 28, + alignItems: 'center', + justifyContent: 'center', + }, + entrepreneurName: { + fontSize: 16, + fontWeight: '700', + marginBottom: 4, + }, + pinfl: { + fontSize: 12, + fontWeight: '500', + }, + trademarkHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + marginBottom: 16, + }, + trademarkLogo: { + width: 64, + height: 64, + borderRadius: 8, + }, + trademarkName: { + fontSize: 15, + fontWeight: '700', + marginBottom: 8, + }, + emptyState: { + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 60, + }, + emptyText: { + fontSize: 16, + fontWeight: '500', + marginTop: 12, + }, +}); diff --git a/screens/profile/ui/AddEmployee.tsx b/screens/profile/ui/AddEmployee.tsx index e9c9f15..c2075c5 100644 --- a/screens/profile/ui/AddEmployee.tsx +++ b/screens/profile/ui/AddEmployee.tsx @@ -47,7 +47,7 @@ export default function AddEmployee() { user_api.create_employee(body), onSuccess: () => { router.push('/profile/employees'); - queryClient.refetchQueries({ queryKey: ['employees-list'] }); + queryClient.refetchQueries({ queryKey: ['employees_list'] }); }, onError: (err: AxiosError) => { const errMessage = (err.response?.data as { data: { phone: string[] } }).data.phone[0]; diff --git a/screens/profile/ui/ManualTab.tsx b/screens/profile/ui/ManualTab.tsx index 7b03eae..958c93f 100644 --- a/screens/profile/ui/ManualTab.tsx +++ b/screens/profile/ui/ManualTab.tsx @@ -1,190 +1,395 @@ import { useTheme } from '@/components/ThemeContext'; -import { ResizeMode, Video } from 'expo-av'; -import { useRouter } from 'expo-router'; -import { ArrowLeft } from 'lucide-react-native'; -import React, { useState } from 'react'; +import { router } from 'expo-router'; +import { VideoView, useVideoPlayer } from 'expo-video'; +import { ArrowLeft, Check, ChevronDown, X } from 'lucide-react-native'; +import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Dimensions, + FlatList, Image, Pressable, ScrollView, StyleSheet, Text, - TouchableOpacity, View, } from 'react-native'; +import ImageViewer from 'react-native-image-zoom-viewer'; +import Modal from 'react-native-modal'; const { width } = Dimensions.get('window'); type ManualStep = { - title: string; - text: string; - image?: any; + image: any; + description?: string; }; +type Language = { + code: 'uz' | 'ru' | 'en'; + label: string; + flag: string; +}; + +const languages: Language[] = [ + { code: 'uz', label: "O'zbekcha", flag: '🇺🇿' }, + { code: 'ru', label: 'Русский', flag: '🇷🇺' }, + { code: 'en', label: 'English', flag: '🇬🇧' }, +]; + export function ManualTab() { const { isDark } = useTheme(); - const { t } = useTranslation(); - const router = useRouter(); - const [videoLang, setVideoLang] = useState<'uz' | 'ru' | 'en'>('uz'); + const { t, i18n } = useTranslation(); + /** 🔹 Modal states */ + const [imageVisible, setImageVisible] = useState(false); + const [imageIndex, setImageIndex] = useState(0); + const [langPickerVisible, setLangPickerVisible] = useState(false); + + /** 🔹 Video tili (SELECT uchun) */ + const userLang = i18n.language.startsWith('ru') + ? 'ru' + : i18n.language.startsWith('en') + ? 'en' + : 'uz'; + + const [selectedLang, setSelectedLang] = useState<'uz' | 'ru' | 'en'>(userLang); + + /** 🔹 RASM tili (faqat app tili bo‘yicha) */ + const imageLang: 'uz' | 'ru' | 'en' = userLang; + + /** 🔹 Theme */ const theme = { background: isDark ? '#0f172a' : '#f8fafc', cardBg: isDark ? '#1e293b' : '#ffffff', text: isDark ? '#ffffff' : '#0f172a', - textSecondary: isDark ? '#94a3b8' : '#64748b', primary: '#3b82f6', + textMuted: isDark ? '#94a3b8' : '#64748b', + border: isDark ? '#334155' : '#e2e8f0', }; - const steps: ManualStep[] = [ - { - title: "Foydalanish qo'lanmasi", - text: "Foydalanish qo'lanmasi", - image: require('@/assets/manual/step1.jpg'), - }, - { - title: "Ro'yxatdan o'tish (Registratsiya) – 1 daqiqa ichida", - text: "Platformaga kirish uchun avval ro'yxatdan o'ting.", - image: require('@/assets/manual/step2.jpg'), - }, - { - title: "Profilni to'ldirish va tasdiqlash", - text: "Muhim: Ro'yxatdan o'tgandan keyin profil to'liq bo'lishi kerak — aks holda platforma cheklangan rejimda ishlaydi.", - image: require('@/assets/manual/step3.jpg'), - }, - { - title: "Xodimlarni qo'shish", - text: "Profil ichida Xodimlar bo'limiga o'ting va + tugmasi orqali xodim qo'shing.", - image: require('@/assets/manual/step4.jpg'), - }, - { - title: "E'lon berish va o'z mahsulot/xizmatlaringizni ko'rsatish", - text: "Pastki menyudan E'lonlar bo'limiga o'ting va yangi e'lon yarating.", - image: require('@/assets/manual/step5.jpg'), - }, - { - title: 'Mijozlarni qidirish va topish', - text: 'Filtrdan foydalanib kerakli kompaniyalarni toping va profiliga kirib xabar yuboring.', - image: require('@/assets/manual/step6.jpg'), - }, - { - title: 'Muhim maslahatlar va xavfsizlik', - text: 'Faqat tasdiqlangan profillarga ishonch bilan murojaat qiling.', - image: require('@/assets/manual/step7.jpg'), - }, - ]; - - const videos: Record<'uz' | 'ru' | 'en', any> = { + /** 🔹 Video manbalari (SELECT ga bog‘liq) */ + const videos = { uz: require('@/assets/manual/manual_video_uz.mp4'), ru: require('@/assets/manual/manual_video_ru.mp4'), en: require('@/assets/manual/manual_video_en.mp4'), }; - const handleVideoChange = (lang: 'uz' | 'ru' | 'en') => { - setVideoLang(lang); - }; + const player = useVideoPlayer(videos[selectedLang], (player) => { + player.loop = false; + }); + /** 🔹 Rasmlar (FAqat imageLang) */ + const steps: ManualStep[] = [ + { + image: + imageLang === 'uz' + ? require('@/assets/manual/image_uz/step1.jpg') + : imageLang === 'ru' + ? require('@/assets/manual/image_ru/step1.jpg') + : require('@/assets/manual/image_en/step1.jpg'), + description: t("Ilovani oching va ro'yxatdan o'ting"), + }, + { + image: + imageLang === 'uz' + ? require('@/assets/manual/image_uz/step2.jpg') + : imageLang === 'ru' + ? require('@/assets/manual/image_ru/step2.jpg') + : require('@/assets/manual/image_en/step2.jpg'), + description: t("Profilni to'ldiring"), + }, + { + image: + imageLang === 'uz' + ? require('@/assets/manual/image_uz/step3.jpg') + : imageLang === 'ru' + ? require('@/assets/manual/image_ru/step3.jpg') + : require('@/assets/manual/image_en/step3.jpg'), + description: t("To'lov usulini qo'shing"), + }, + { + image: + imageLang === 'uz' + ? require('@/assets/manual/image_uz/step4.jpg') + : imageLang === 'ru' + ? require('@/assets/manual/image_ru/step4.jpg') + : require('@/assets/manual/image_en/step4.jpg'), + description: t('Xaridni tanlang'), + }, + { + image: + imageLang === 'uz' + ? require('@/assets/manual/image_uz/step5.jpg') + : imageLang === 'ru' + ? require('@/assets/manual/image_ru/step5.jpg') + : require('@/assets/manual/image_en/step5.jpg'), + description: t("To'lovni amalga oshiring"), + }, + { + image: + imageLang === 'uz' + ? require('@/assets/manual/image_uz/step6.jpg') + : imageLang === 'ru' + ? require('@/assets/manual/image_ru/step6.jpg') + : require('@/assets/manual/image_en/step6.jpg'), + description: t("Natijani ko'ring"), + }, + { + image: + imageLang === 'uz' + ? require('@/assets/manual/image_uz/step7.jpg') + : imageLang === 'ru' + ? require('@/assets/manual/image_ru/step7.jpg') + : require('@/assets/manual/image_en/step7.jpg'), + description: t("Natijani ko'ring"), + }, + { + image: + imageLang === 'uz' + ? require('@/assets/manual/image_uz/step8.jpg') + : imageLang === 'ru' + ? require('@/assets/manual/image_ru/step8.jpg') + : require('@/assets/manual/image_en/step8.jpg'), + description: t("Natijani ko'ring"), + }, + ]; + + /** 🔹 Image viewer */ + const images = useMemo( + () => steps.map((step) => ({ url: '', props: { source: step.image } })), + [imageLang] + ); + + const selectedLanguage = languages.find((l) => l.code === selectedLang); + + const renderStep = ({ item, index }: { item: ManualStep; index: number }) => ( + { + setImageIndex(index); + setImageVisible(true); + }} + > + + + + + ); return ( - - router.push('/profile')}> - - + {/* HEADER */} + + + router.back()}> + + + + {t("Foydalanish qo'llanmasi")} + + - - {t("Foydalanish qo'lanmasi")} + + {t("Quyidagi qisqa video yoki rasmlarni ko'rib chiqing")} - {steps.map((step, index) => ( - - {t(step.title)} - - {t(step.text)} - - {step.image && } - - ))} - - {/* Video bo'limi */} - - {t("Qo'llanma video")} - - {/* Til tanlash tugmalari */} - - + + {t("Foydalanish video qo'llanma")} + + + handleVideoChange('uz')} + onPress={() => setLangPickerVisible(true)} > - O'zbek - - - handleVideoChange('ru')} - > - Русский - - - handleVideoChange('en')} - > - English - + + {selectedLanguage?.flag} + + + - + + + {/* RASMLAR */} + + + {t('Foydalanish rasm qo‘llanma')} + + + + + {/* IMAGE MODAL */} + + setImageVisible(false)} + renderHeader={() => ( + setImageVisible(false)}> + + + )} + /> + + + {/* LANGUAGE MODAL */} + setLangPickerVisible(false)}> + + + {t('Video tilini tanlang')} + + {languages.map((lang) => { + const isSelected = selectedLang === lang.code; + + return ( + { + setSelectedLang(lang.code); + setLangPickerVisible(false); + }} + style={[ + styles.langOption, + { + borderColor: isSelected ? theme.primary : theme.border, + backgroundColor: isSelected ? theme.primary + '15' : 'transparent', + }, + ]} + > + + {lang.flag} + + {lang.label} + + + + {isSelected && ( + + + + )} + + ); + })} + + ); } const styles = StyleSheet.create({ - container: { flex: 1, padding: 16 }, - card: { borderRadius: 16, padding: 16, marginBottom: 16 }, - headerTitle: { fontSize: 18, fontWeight: '700', lineHeight: 24 }, - text: { fontSize: 14, lineHeight: 20 }, - image: { width: width - 64, height: 200, marginTop: 12, borderRadius: 12 }, - video: { width: width - 64, height: 320, marginTop: 12, borderRadius: 12 }, - buttonRow: { flexDirection: 'row', justifyContent: 'space-around', marginVertical: 12 }, - langButton: { - paddingVertical: 8, - paddingHorizontal: 16, - borderRadius: 8, - borderWidth: 1, + container: { flex: 1 }, + hero: { padding: 20 }, + topHeader: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + headerTitle: { fontSize: 22, fontWeight: '700' }, + subtitle: { marginTop: 8 }, + + section: { marginBottom: 28 }, + sectionTitle: { fontSize: 20, fontWeight: '700', marginLeft: 16 }, + + langSelector: { + margin: 5, + width: 80, + gap: 5, + paddingVertical: 5, + borderRadius: 14, + borderWidth: 1.5, + alignContent: 'center', + alignItems: 'center', + flexDirection: 'row', + justifyContent: 'center', }, - header: { - padding: 16, + + videoCard: { + marginHorizontal: 16, + borderRadius: 20, + overflow: 'hidden', + position: 'relative', + }, + video: { width: '100%', aspectRatio: 9 / 13 }, + + stepCard: { borderRadius: 16, overflow: 'hidden' }, + stepImage: { width: 300, height: 300 }, + + closeButton: { + position: 'absolute', + top: 50, + right: 20, + padding: 8, + zIndex: 50, + backgroundColor: 'rgba(0,0,0,0.5)', + borderRadius: 20, + }, + + langModalContent: { + padding: 20, + borderRadius: 20, + }, + langOption: { flexDirection: 'row', alignItems: 'center', - justifyContent: 'flex-start', - gap: 10, - elevation: 3, + justifyContent: 'space-between', + paddingVertical: 14, + paddingHorizontal: 16, + borderRadius: 14, + borderWidth: 1.5, + marginBottom: 10, + }, + + langOptionLeft: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + }, + + checkmark: { + width: 24, + height: 24, + borderRadius: 12, + alignItems: 'center', + justifyContent: 'center', + }, + + checkmarkText: { + color: '#fff', + fontSize: 14, + fontWeight: 'bold', + }, + playOverlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'rgba(0,0,0,0.25)', }, }); diff --git a/screens/profile/ui/NotificationTab.tsx b/screens/profile/ui/NotificationTab.tsx index 8b5438a..3b68aed 100644 --- a/screens/profile/ui/NotificationTab.tsx +++ b/screens/profile/ui/NotificationTab.tsx @@ -41,7 +41,14 @@ export function NotificationTab() { if (isLoading) { return ( - + @@ -51,10 +58,12 @@ export function NotificationTab() { if (isError) { return ( - - + + {t('Xatolik yuz berdi')} - {t("Bildirishnomalarni yuklashda muammo bo'ldi")} + + {t("Bildirishnomalarni yuklashda muammo bo'ldi")} + refetch()}> {t('Qayta urinish')} @@ -64,13 +73,13 @@ export function NotificationTab() { } return ( - - + + router.push('/profile')}> - + {t('Bildirishnomalar')} @@ -102,12 +111,14 @@ export function NotificationTab() { function NotificationCard({ item }: { item: NotificationListDataRes }) { const queryClient = useQueryClient(); + const { isDark } = useTheme(); const { t } = useTranslation(); const [scaleAnim] = useState(new Animated.Value(1)); const { mutate } = useMutation({ mutationFn: (id: number) => user_api.is_ready_id(id), onSuccess: () => { queryClient.refetchQueries({ queryKey: ['notification-list'] }); + queryClient.refetchQueries({ queryKey: ['notifications-list'] }); }, }); @@ -145,21 +156,54 @@ function NotificationCard({ item }: { item: NotificationListDataRes }) { onPressIn={handlePressIn} onPressOut={handlePressOut} onPress={() => handlePress(item.id)} - style={[styles.card, !item.is_read && styles.unreadCard]} + style={[ + styles.card, + !item.is_read && styles.unreadCard, + { + backgroundColor: isDark ? '#283046' : '#e0f2fe', + borderColor: isDark ? '#3b82f6' : '#3b82f6', + }, + ]} > - + {item.title} - {!item.is_read && } + {!item.is_read && ( + + )} - + {item.description} - {formatDate(item.created_at, t)} + + {formatDate(item.created_at, t)} + @@ -187,12 +231,9 @@ function formatDate(date: string, t: any) { }); } -/* ---------------- STYLES ---------------- */ - const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#0a0c17', }, listContent: { padding: 16, @@ -202,41 +243,23 @@ const styles = StyleSheet.create({ /* Card Styles */ card: { flexDirection: 'row', - backgroundColor: '#121826', padding: 16, borderRadius: 24, borderWidth: 1, - borderColor: 'rgba(60, 70, 90, 0.18)', shadowColor: '#000', shadowOffset: { width: 0, height: 8 }, - shadowOpacity: 0.35, - shadowRadius: 16, - elevation: 10, + shadowOpacity: 0.25, + shadowRadius: 12, + elevation: 8, overflow: 'hidden', }, unreadCard: { - backgroundColor: '#1a2236', - borderColor: 'rgba(59, 130, 246, 0.4)', shadowColor: '#3b82f6', - shadowOpacity: 0.28, - shadowRadius: 20, - elevation: 12, - }, - iconContainer: { - width: 56, - height: 56, - borderRadius: 20, - backgroundColor: 'rgba(30, 38, 56, 0.7)', - alignItems: 'center', - justifyContent: 'center', - marginRight: 16, - }, - unreadIconContainer: { - backgroundColor: 'rgba(59, 130, 246, 0.22)', - }, - iconText: { - fontSize: 32, + shadowOpacity: 0.3, + shadowRadius: 16, + elevation: 10, }, + cardContent: { flex: 1, justifyContent: 'center', @@ -248,20 +271,20 @@ const styles = StyleSheet.create({ }, cardTitle: { flex: 1, - color: '#d1d5db', + fontSize: 16.5, fontWeight: '600', letterSpacing: -0.1, }, unreadTitle: { - color: '#f1f5f9', fontWeight: '700', + // unread title rang }, unreadIndicator: { width: 10, height: 10, borderRadius: 5, - backgroundColor: '#3b82f6', + marginLeft: 8, shadowColor: '#3b82f6', shadowOffset: { width: 0, height: 0 }, @@ -269,13 +292,11 @@ const styles = StyleSheet.create({ shadowRadius: 6, }, cardMessage: { - color: '#9ca3af', fontSize: 14.5, lineHeight: 21, marginBottom: 8, }, cardTime: { - color: '#64748b', fontSize: 12.5, fontWeight: '500', opacity: 0.9, @@ -284,7 +305,7 @@ const styles = StyleSheet.create({ /* Loading State */ loadingContainer: { flex: 1, - backgroundColor: '#0a0c17', + alignItems: 'center', justifyContent: 'center', }, @@ -292,40 +313,22 @@ const styles = StyleSheet.create({ alignItems: 'center', padding: 32, }, - loadingText: { - marginTop: 16, - color: '#94a3b8', - fontSize: 15, - fontWeight: '500', - }, /* Error State */ errorContainer: { flex: 1, - backgroundColor: '#0a0c17', + alignItems: 'center', justifyContent: 'center', padding: 24, }, errorContent: { alignItems: 'center', - backgroundColor: '#151b2e', + padding: 32, borderRadius: 24, maxWidth: 320, }, - errorIconContainer: { - width: 80, - height: 80, - borderRadius: 40, - backgroundColor: '#1e2638', - alignItems: 'center', - justifyContent: 'center', - marginBottom: 20, - }, - errorIcon: { - fontSize: 40, - }, errorTitle: { color: '#ef4444', fontSize: 20, @@ -333,7 +336,6 @@ const styles = StyleSheet.create({ marginBottom: 8, }, errorMessage: { - color: '#94a3b8', fontSize: 14, textAlign: 'center', marginBottom: 24, @@ -356,52 +358,19 @@ const styles = StyleSheet.create({ fontWeight: '700', }, - /* Empty State */ - emptyContainer: { - flex: 1, - backgroundColor: '#0a0c17', - alignItems: 'center', - justifyContent: 'center', - padding: 24, - }, - emptyContent: { - alignItems: 'center', - maxWidth: 300, - }, - emptyIconContainer: { - width: 100, - height: 100, - borderRadius: 50, - backgroundColor: '#151b2e', - alignItems: 'center', - justifyContent: 'center', - marginBottom: 24, - borderWidth: 2, - borderColor: '#1e2638', - }, - emptyIcon: { - fontSize: 50, - }, - emptyTitle: { - color: '#ffffff', - fontSize: 22, - fontWeight: '700', - marginBottom: 12, - textAlign: 'center', - }, - emptyMessage: { - color: '#94a3b8', - fontSize: 15, - textAlign: 'center', - lineHeight: 22, - }, + /* Header */ header: { padding: 16, flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', gap: 10, + elevation: 3, }, - headerTitle: { fontSize: 18, fontWeight: '700', lineHeight: 24 }, + headerTitle: { + fontSize: 18, + fontWeight: '700', + lineHeight: 24, + }, }); diff --git a/screens/profile/ui/ProfileScreen.tsx b/screens/profile/ui/ProfileScreen.tsx index 67e27a5..240bae0 100644 --- a/screens/profile/ui/ProfileScreen.tsx +++ b/screens/profile/ui/ProfileScreen.tsx @@ -25,7 +25,13 @@ export default function Profile() { const { isDark } = useTheme(); const { t } = useTranslation(); - const { data: me, isLoading } = useQuery({ + const { data } = useQuery({ + queryKey: ['notification-list'], + queryFn: () => user_api.notification_list({ page: 1, page_size: 1 }), + }); + const unreadCount = data?.data?.data.unread_count ?? 0; + + const { data: me } = useQuery({ queryKey: ['get_me'], queryFn: () => user_api.getMe(), }); @@ -36,7 +42,12 @@ export default function Profile() { items: [ { icon: User, label: "Shaxsiy ma'lumotlar", route: '/profile/personal-info' }, { icon: Users, label: 'Xodimlar', route: '/profile/employees' }, - { icon: Bell, label: 'Bildirishnomalar', route: '/profile/notification' }, + { + icon: Bell, + label: 'Bildirishnomalar', + route: '/profile/notification', + badge: unreadCount, + }, ], }, { @@ -60,7 +71,7 @@ export default function Profile() { title: 'Sozlamalar', items: [ { icon: Settings, label: 'Sozlamalar', route: '/profile/settings' }, - { icon: BookAIcon, label: "Foydalanish qo'lanmasi", route: '/profile/manual' }, + { icon: BookAIcon, label: "Foydalanish qo'llanmasi", route: '/profile/manual' }, ], }, ]; @@ -95,6 +106,12 @@ export default function Profile() { style={[styles.iconContainer, isDark ? styles.darkIconBg : styles.lightIconBg]} > + + {item.badge && ( + + {item.badge} + + )} @@ -202,4 +219,23 @@ const styles = StyleSheet.create({ lightText: { color: '#0f172a', }, + badge: { + position: 'absolute', + top: -5, + right: -5, + backgroundColor: '#ef4444', // qizil badge + borderRadius: 8, + minWidth: 16, + height: 16, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 3, + }, + + badgeText: { + color: '#ffffff', + fontSize: 10, + fontWeight: '700', + textAlign: 'center', + }, }); diff --git a/screens/profile/ui/StepOneService.tsx b/screens/profile/ui/StepOneService.tsx index 0f8a7f5..d61c9ad 100644 --- a/screens/profile/ui/StepOneService.tsx +++ b/screens/profile/ui/StepOneService.tsx @@ -1,6 +1,6 @@ import { useTheme } from '@/components/ThemeContext'; import * as ImagePicker from 'expo-image-picker'; -import { Camera, Play, X } from 'lucide-react-native'; +import { Image as ImageIcon, Play, Video, X } from 'lucide-react-native'; import React, { forwardRef, useImperativeHandle, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Image, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; @@ -18,12 +18,15 @@ type Errors = { media?: string; }; -const MAX_MEDIA = 10; +type MediaTabType = 'image' | 'video'; + +const MAX_MEDIA = 1; const StepOneServices = forwardRef(({ formData, updateForm, removeMedia }: StepProps, ref) => { const { isDark } = useTheme(); const { t } = useTranslation(); const [errors, setErrors] = useState({}); + const [selectedMediaTab, setSelectedMediaTab] = useState('image'); const validate = () => { const e: Errors = {}; @@ -34,8 +37,7 @@ const StepOneServices = forwardRef(({ formData, updateForm, removeMedia }: StepP if (!formData.description || formData.description.trim().length < 10) e.description = t("Tavsif kamida 10 ta belgidan iborat bo'lishi kerak"); - if (!formData.media || formData.media.length === 0) - e.media = t('Kamida bitta rasm yoki video yuklang'); + if (!formData.media || formData.media.length === 0) e.media = t('Rasm yoki video yuklang'); setErrors(e); return Object.keys(e).length === 0; @@ -46,101 +48,199 @@ const StepOneServices = forwardRef(({ formData, updateForm, removeMedia }: StepP const pickMedia = async () => { if (formData.media.length >= MAX_MEDIA) return; + const mediaType = + selectedMediaTab === 'image' + ? ImagePicker.MediaTypeOptions.Images + : ImagePicker.MediaTypeOptions.Videos; + const result = await ImagePicker.launchImageLibraryAsync({ - mediaTypes: ImagePicker.MediaTypeOptions.All, - allowsMultipleSelection: true, + mediaTypes: mediaType, + allowsMultipleSelection: false, quality: 0.8, + videoMaxDuration: 60, // 60 seconds max for video }); if (!result.canceled) { - const assets = result.assets - .slice(0, MAX_MEDIA - formData.media.length) - .map((a) => ({ uri: a.uri, type: a.type as 'image' | 'video' })); + const asset = result.assets[0]; + const mediaItem = { + uri: asset.uri, + type: selectedMediaTab, + }; - updateForm('media', [...formData.media, ...assets]); + updateForm('media', [mediaItem]); } }; + const theme = { + background: isDark ? '#0f172a' : '#ffffff', + inputBg: isDark ? '#1e293b' : '#f1f5f9', + inputBorder: isDark ? '#334155' : '#e2e8f0', + text: isDark ? '#f8fafc' : '#0f172a', + textSecondary: isDark ? '#cbd5e1' : '#475569', + placeholder: isDark ? '#94a3b8' : '#94a3b8', + error: '#ef4444', + primary: '#2563eb', + tabActive: isDark ? '#2563eb' : '#3b82f6', + tabInactive: isDark ? '#334155' : '#e2e8f0', + }; + return ( {/* Sarlavha */} - {t('Sarlavha')} + {t('Sarlavha')} updateForm('title', t)} /> - {errors.title && {errors.title}} + {errors.title && {errors.title}} {/* Tavsif */} - {t('Tavsif')} + {t('Tavsif')} updateForm('description', t)} /> - {errors.description && {errors.description}} + {errors.description && ( + {errors.description} + )} - {/* Media */} - - {t('Media')} ({formData.media.length}/{MAX_MEDIA}) - - + {/* Media Type Tabs */} + {t('Media turi')} + setSelectedMediaTab('image')} + activeOpacity={0.7} > - - {t('Yuklash')} + + + {t('Rasm')} + - {formData.media.map((m: MediaType, i: number) => ( - - - {m.type === 'video' && ( - - + setSelectedMediaTab('video')} + activeOpacity={0.7} + > + + + + {/* Media Upload/Preview */} + + {formData.media.length === 0 ? ( + + + {selectedMediaTab === 'image' ? ( + + ) : ( + + + {selectedMediaTab === 'image' ? t('Rasm yuklash') : t('Video yuklash')} + + + {selectedMediaTab === 'image' ? t('Rasm tanlang') : t('Video tanlang')} + + + ) : ( + + + {formData.media[0].type === 'video' && ( + + )} - removeMedia(i)}> - + removeMedia(0)} + activeOpacity={0.8} + > + + + {formData.media[0].type === 'image' ? ( + + ) : ( + - ))} + )} - {errors.media && {errors.media}} + {errors.media && ( + {t(errors.media)} + )} ); }); @@ -150,7 +250,7 @@ export default StepOneServices; const styles = StyleSheet.create({ stepContainer: { gap: 10 }, label: { fontWeight: '700', fontSize: 15 }, - error: { color: '#ef4444', fontSize: 13, marginLeft: 6 }, + error: { fontSize: 13, marginLeft: 6 }, inputBox: { flexDirection: 'row', alignItems: 'center', @@ -159,53 +259,125 @@ const styles = StyleSheet.create({ paddingHorizontal: 16, height: 56, }, - textArea: { height: 120, alignItems: 'flex-start', paddingTop: 16 }, + textArea: { height: 120, alignItems: 'flex-start', paddingVertical: 12 }, input: { flex: 1, fontSize: 16 }, - media: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 }, - upload: { - width: 100, - height: 100, + + // Media Tabs + tabsContainer: { + flexDirection: 'row', + gap: 12, + }, + tab: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 14, + paddingHorizontal: 16, borderRadius: 16, borderWidth: 2, + gap: 8, + }, + tabActive: { + shadowColor: '#2563eb', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3, + }, + tabText: { + fontSize: 15, + fontWeight: '700', + letterSpacing: 0.3, + }, + + // Media Container + mediaContainer: { + marginTop: 4, + }, + uploadLarge: { + height: 240, + borderRadius: 20, + borderWidth: 2, borderStyle: 'dashed', justifyContent: 'center', alignItems: 'center', + gap: 12, }, - uploadText: { color: '#2563eb', fontSize: 11, marginTop: 4, fontWeight: '600' }, - preview: { width: 100, height: 100 }, - image: { width: '100%', height: '100%', borderRadius: 16 }, - play: { - position: 'absolute', - top: '40%', - left: '40%', - backgroundColor: 'rgba(0,0,0,.5)', - padding: 6, + uploadIconWrapper: { + width: 72, + height: 72, + borderRadius: 36, + justifyContent: 'center', + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.2, + shadowRadius: 4, + elevation: 3, + }, + uploadLargeText: { + fontSize: 18, + fontWeight: '700', + marginTop: 4, + }, + uploadLargeSubtext: { + fontSize: 14, + fontWeight: '500', + textAlign: 'center', + paddingHorizontal: 32, + }, + previewLarge: { + height: 240, + borderRadius: 20, + overflow: 'hidden', + position: 'relative', + }, + imageLarge: { + width: '100%', + height: '100%', borderRadius: 20, }, - remove: { + playLarge: { position: 'absolute', - top: -6, - right: -6, - backgroundColor: '#ef4444', - padding: 4, - borderRadius: 10, + top: '50%', + left: '50%', + transform: [{ translateX: -28 }, { translateY: -28 }], + backgroundColor: 'rgba(0,0,0,.6)', + padding: 14, + borderRadius: 32, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3, }, - prefixContainer: { + removeLarge: { + position: 'absolute', + top: 12, + right: 12, + padding: 8, + borderRadius: 12, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.3, + shadowRadius: 4, + elevation: 3, + }, + mediaTypeBadge: { + position: 'absolute', + bottom: 12, + left: 12, flexDirection: 'row', alignItems: 'center', - marginRight: 12, + gap: 6, + paddingVertical: 6, + paddingHorizontal: 12, + borderRadius: 10, }, - prefix: { - fontSize: 16, + mediaTypeBadgeText: { + color: '#ffffff', + fontSize: 13, fontWeight: '600', - letterSpacing: 0.3, - }, - prefixFocused: { - color: '#fff', - }, - divider: { - width: 1.5, - height: 24, - marginLeft: 12, }, });