updated code with dsc and device binding
This commit is contained in:
+171
-96
@@ -1,124 +1,173 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
Dimensions,
|
||||
Animated,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import * as LucideIcons from 'lucide-react-native';
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withSpring,
|
||||
withTiming,
|
||||
interpolate,
|
||||
Extrapolation
|
||||
} from 'react-native-reanimated';
|
||||
import Screens from '../Screens';
|
||||
import colors from '../Constant/Color';
|
||||
|
||||
import NavigationString from '../Constant/NavigationString';
|
||||
import EditableExcel from '../Screens/Tender/Component/EnvelopeCExcelPreview';
|
||||
import { useTheme } from '../Hooks/useTheme';
|
||||
import { typography } from '../theme/typography';
|
||||
// import EditableExcel from '../Screens/Tender/Component/EnvelopeCExcelPreview';
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const TAB_BAR_HEIGHT = 60;
|
||||
const TAB_BAR_HEIGHT = 70; // Slightly taller for premium feel
|
||||
const TAB_BUTTON_WIDTH = width / 4;
|
||||
|
||||
const Tab = createBottomTabNavigator();
|
||||
|
||||
const TabButton = ({ route, isFocused, onPress }: any) => {
|
||||
const icons: Record<string, { outline: any; filled: any }> = {
|
||||
Home: { outline: "Home", filled: "Home" },
|
||||
const { colors } = useTheme();
|
||||
const icons: Record<string, { outline: keyof typeof LucideIcons; filled: keyof typeof LucideIcons }> = {
|
||||
Home: { outline: "House", filled: "House" },
|
||||
Auction: { outline: "Gavel", filled: "Gavel" },
|
||||
Tender: { outline: "FileText", filled: "FileText" },
|
||||
Profile: { outline: "User", filled: "User" },
|
||||
|
||||
};
|
||||
const IconName = LucideIcons[isFocused ? icons[route.name].filled : icons[route.name].outline];
|
||||
const scaleAnim = useRef(new Animated.Value(1)).current;
|
||||
const translateYAnim = useRef(new Animated.Value(0)).current;
|
||||
const opacityAnim = useRef(new Animated.Value(1)).current;
|
||||
// Lucide icons don't always have 'filled' variants in the set, so we might use the same icon
|
||||
// or rely on color/strokeWidth to indicate state.
|
||||
const iconKey = icons[route.name]?.outline || "HelpCircle"; // Fallback
|
||||
const IconName = LucideIcons[iconKey] as React.ElementType;
|
||||
|
||||
const scale = useSharedValue(0);
|
||||
const translateY = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocused) {
|
||||
Animated.parallel([
|
||||
Animated.sequence([
|
||||
Animated.timing(translateYAnim, {
|
||||
toValue: -8,
|
||||
duration: 120,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.spring(translateYAnim, {
|
||||
toValue: 0,
|
||||
friction: 3,
|
||||
tension: 80,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]),
|
||||
Animated.spring(scaleAnim, {
|
||||
toValue: 1.3,
|
||||
friction: 5,
|
||||
tension: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(opacityAnim, {
|
||||
toValue: 1,
|
||||
duration: 250,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start();
|
||||
scale.value = withSpring(1, { damping: 12, stiffness: 100 });
|
||||
translateY.value = withSpring(-4, { damping: 12, stiffness: 100 });
|
||||
} else {
|
||||
Animated.parallel([
|
||||
Animated.spring(scaleAnim, {
|
||||
toValue: 1,
|
||||
friction: 5,
|
||||
tension: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(opacityAnim, {
|
||||
toValue: 0.6,
|
||||
duration: 150,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start();
|
||||
scale.value = withTiming(0, { duration: 200 });
|
||||
translateY.value = withSpring(0, { damping: 12, stiffness: 100 });
|
||||
}
|
||||
}, [isFocused]);
|
||||
|
||||
const animatedPillStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{ scale: scale.value }],
|
||||
opacity: interpolate(scale.value, [0, 1], [0, 1], Extrapolation.CLAMP),
|
||||
};
|
||||
});
|
||||
|
||||
const animatedIconStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{ scale: interpolate(scale.value, [0, 1], [1, 0.9]) }], // Slight shrink on press
|
||||
};
|
||||
});
|
||||
|
||||
const animatedTextStyle = useAnimatedStyle(() => {
|
||||
const opacity = interpolate(scale.value, [0, 1], [0, 1]);
|
||||
const translateX = interpolate(scale.value, [0, 1], [20, 0]); // Slide in from right
|
||||
return {
|
||||
opacity,
|
||||
transform: [{ translateX }],
|
||||
display: scale.value === 0 ? 'none' : 'flex', // Hide when inactive
|
||||
marginLeft: 4, // Reduced space
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
style={[styles.tabButton, { width: TAB_BUTTON_WIDTH }]}
|
||||
style={styles.tabButton}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.iconContainer,
|
||||
{
|
||||
transform: [{ scale: scaleAnim }, { translateY: translateYAnim }],
|
||||
opacity: opacityAnim,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{IconName && (
|
||||
<IconName
|
||||
size={20}
|
||||
color={isFocused ? colors.primary : '#888'}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
)}
|
||||
</Animated.View>
|
||||
<View style={styles.tabContent}>
|
||||
<Animated.View style={animatedIconStyle}>
|
||||
{IconName && (
|
||||
<IconName
|
||||
size={24}
|
||||
color={isFocused ? colors.primary : colors.text}
|
||||
strokeWidth={isFocused ? 2.5 : 2}
|
||||
/>
|
||||
)}
|
||||
</Animated.View>
|
||||
|
||||
<Text
|
||||
style={[
|
||||
styles.label,
|
||||
{
|
||||
color: isFocused ? colors.primary : '#888',
|
||||
fontWeight: isFocused ? '600' : '400',
|
||||
},
|
||||
]}
|
||||
>
|
||||
{route.name === 'BrandDay' ? 'Coupons' : route.name}
|
||||
</Text>
|
||||
{isFocused && (
|
||||
<Animated.Text
|
||||
style={[
|
||||
styles.label,
|
||||
animatedTextStyle,
|
||||
{
|
||||
color: colors.primary,
|
||||
fontFamily: typography.fonts.medium,
|
||||
fontWeight: undefined, // Remove fontWeight as we use fontFamily
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{route.name === 'BrandDay' ? 'Coupons' : route.name}
|
||||
</Animated.Text>
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomTabBar = ({ state, descriptors, navigation }: any) => {
|
||||
const { colors, shadows } = useTheme();
|
||||
const { width: windowWidth } = Dimensions.get('window');
|
||||
const TAB_BAR_WIDTH = windowWidth - 40; // 20 margin each side
|
||||
const TAB_PADDING_H = 10; // 5 padding left + 5 padding right
|
||||
const TAB_WIDTH = (TAB_BAR_WIDTH - TAB_PADDING_H) / state.routes.length;
|
||||
|
||||
const translateX = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
translateX.value = withSpring(state.index * TAB_WIDTH, {
|
||||
damping: 15,
|
||||
stiffness: 150,
|
||||
mass: 0.5,
|
||||
});
|
||||
}, [state.index]);
|
||||
|
||||
const animatedIndicatorStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{ translateX: translateX.value }],
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.tabBar}>
|
||||
<View style={[
|
||||
styles.tabBar,
|
||||
{
|
||||
backgroundColor: colors.background, // Use transparent to allow shadow to show
|
||||
shadowColor: colors.shadow, // Dynamic shadow color
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: 0.2, // Consistent opacity
|
||||
shadowRadius: 10,
|
||||
borderColor: colors.border, // Subtle border for definition
|
||||
borderWidth: 3,
|
||||
elevation: 20,
|
||||
}
|
||||
]}>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.slidingIndicator,
|
||||
{
|
||||
width: TAB_WIDTH,
|
||||
backgroundColor: colors.primary,
|
||||
opacity: 0.3,
|
||||
elevation: 10, // Subtle elevation for the indicator
|
||||
},
|
||||
animatedIndicatorStyle,
|
||||
]}
|
||||
/>
|
||||
{state.routes.map((route: any, index: number) => {
|
||||
const isFocused = state.index === index;
|
||||
|
||||
@@ -136,8 +185,9 @@ const CustomTabBar = ({ state, descriptors, navigation }: any) => {
|
||||
};
|
||||
|
||||
export default function BottomTab() {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={[styles.container, { backgroundColor: colors.background }]}>
|
||||
<Tab.Navigator
|
||||
tabBar={(props) => <CustomTabBar {...props} />}
|
||||
screenOptions={{
|
||||
@@ -148,9 +198,9 @@ export default function BottomTab() {
|
||||
<Tab.Screen name="Home" component={Screens.Home} />
|
||||
<Tab.Screen name={NavigationString.Auction} component={Screens.Auction} />
|
||||
<Tab.Screen name={NavigationString.Tender} component={Screens.Tender} />
|
||||
{/* <Tab.Screen name={'EditableExcel'} component={EditableExcel} /> */}
|
||||
<Tab.Screen name={NavigationString.Profile} component={Screens.ProfileTab} />
|
||||
|
||||
{/* <Tab.Screen name={'EditableExcel'} component={EditableExcel} /> */}
|
||||
<Tab.Screen name={NavigationString.Profile} component={Screens.ProfileTab} />
|
||||
|
||||
</Tab.Navigator>
|
||||
</View>
|
||||
);
|
||||
@@ -159,29 +209,54 @@ export default function BottomTab() {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#fff',
|
||||
|
||||
},
|
||||
tabBar: {
|
||||
position: 'absolute',
|
||||
bottom: 20,
|
||||
left: 20,
|
||||
right: 20,
|
||||
flexDirection: 'row',
|
||||
height: TAB_BAR_HEIGHT,
|
||||
backgroundColor: '#fff',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#eee',
|
||||
elevation: 5,
|
||||
height: 70,
|
||||
borderRadius: 35,
|
||||
elevation: 20, // stronger Android elevation
|
||||
shadowColor: '#000', // iOS shadow color
|
||||
shadowOffset: { width: 0, height: 10 },
|
||||
shadowOpacity: Platform.OS === 'ios' ? 0.25 : 0.2,
|
||||
shadowRadius: 12,
|
||||
overflow: 'visible', // allow shadow to render outside rounded corners
|
||||
zIndex: 9999,
|
||||
paddingBottom: 0,
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 5,
|
||||
},
|
||||
tabButton: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
},
|
||||
iconContainer: {
|
||||
marginBottom: 4,
|
||||
justifyContent: 'center',
|
||||
tabContent: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 4, // Drastically reduced from 12
|
||||
paddingVertical: 4,
|
||||
borderRadius: 20,
|
||||
},
|
||||
slidingIndicator: {
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
left: 5,
|
||||
height: 40,
|
||||
borderRadius: 30,
|
||||
zIndex: 0,
|
||||
},
|
||||
label: {
|
||||
fontSize: 11,
|
||||
textAlign: 'center',
|
||||
includeFontPadding: false,
|
||||
fontSize: 11, // Slightly smaller text
|
||||
textAlign: 'left',
|
||||
textTransform: 'capitalize',
|
||||
letterSpacing: 0.2, // Tighter spacing
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet, TouchableOpacity, Switch, Image } from 'react-native';
|
||||
import { DrawerContentScrollView, DrawerItemList } from '@react-navigation/drawer';
|
||||
import { useTheme } from '../Hooks/useTheme';
|
||||
import AppText from '../components/AppText';
|
||||
import { Moon, Sun, Monitor } from 'lucide-react-native';
|
||||
import ImagePath from '../Constant/ImagePath';
|
||||
|
||||
const CustomDrawer = (props: any) => {
|
||||
const { colors, isDarkMode, themeMode, toggleTheme } = useTheme();
|
||||
|
||||
const renderThemeOption = (mode: 'light' | 'dark' | 'system', label: string, Icon: any) => {
|
||||
const isActive = themeMode === mode;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.themeOption,
|
||||
isActive && { backgroundColor: colors.primary + '20' } // 20% opacity
|
||||
]}
|
||||
onPress={() => toggleTheme(mode)}
|
||||
>
|
||||
<Icon size={20} color={isActive ? colors.primary : colors.textSecondary} />
|
||||
<AppText variant="medium" style={{ marginLeft: 10, color: isActive ? colors.primary : colors.textSecondary }}>
|
||||
{label}
|
||||
</AppText>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: colors.background }}>
|
||||
<DrawerContentScrollView {...props} contentContainerStyle={{ paddingTop: 0 }}>
|
||||
{/* Header */}
|
||||
<View style={[styles.header, { backgroundColor: colors.surface, borderBottomColor: colors.border }]}>
|
||||
<Image source={ImagePath.Icon.Logo} style={styles.logo} resizeMode="contain" />
|
||||
<AppText variant="bold" size={20} style={{ color: colors.text }}>ProcureLinc</AppText>
|
||||
<AppText variant="regular" size={12} style={{ color: colors.textSecondary }}>Smart Procurement</AppText>
|
||||
</View>
|
||||
|
||||
<View style={{ flex: 1, paddingTop: 10 }}>
|
||||
<DrawerItemList {...props} />
|
||||
</View>
|
||||
|
||||
{/* Theme Switch Section */}
|
||||
<View style={[styles.section, { borderTopColor: colors.border }]}>
|
||||
<AppText variant="bold" style={{ marginLeft: 16, marginBottom: 10, color: colors.text }}>Running Theme</AppText>
|
||||
<View style={styles.themeRow}>
|
||||
{renderThemeOption('light', 'Light', Sun)}
|
||||
{renderThemeOption('dark', 'Dark', Moon)}
|
||||
{renderThemeOption('system', 'System', Monitor)}
|
||||
</View>
|
||||
</View>
|
||||
</DrawerContentScrollView>
|
||||
|
||||
<View style={[styles.footer, { borderTopColor: colors.border }]}>
|
||||
<AppText variant="regular" size={12} style={{ color: colors.textSecondary, textAlign: 'center' }}>
|
||||
Version 1.0.0
|
||||
</AppText>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
header: {
|
||||
padding: 20,
|
||||
paddingTop: 50, // safe area approximation
|
||||
alignItems: 'center',
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
logo: {
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: 40,
|
||||
marginBottom: 10,
|
||||
},
|
||||
section: {
|
||||
padding: 10,
|
||||
borderTopWidth: 1,
|
||||
marginTop: 'auto',
|
||||
marginBottom: 20
|
||||
},
|
||||
themeRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
themeOption: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 10,
|
||||
borderRadius: 12,
|
||||
flexDirection: 'row'
|
||||
},
|
||||
footer: {
|
||||
padding: 20,
|
||||
borderTopWidth: 1,
|
||||
}
|
||||
});
|
||||
|
||||
export default CustomDrawer;
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { createDrawerNavigator } from '@react-navigation/drawer';
|
||||
import BottomTab from './Bottom';
|
||||
import CustomDrawer from './CustomDrawer';
|
||||
import { useTheme } from '../Hooks/useTheme';
|
||||
import NavigationString from '../Constant/NavigationString';
|
||||
import { Dimensions } from 'react-native';
|
||||
|
||||
const Drawer = createDrawerNavigator();
|
||||
const { width } = Dimensions.get('window');
|
||||
|
||||
const DrawerStack = () => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
return (
|
||||
<Drawer.Navigator
|
||||
drawerContent={(props) => <CustomDrawer {...props} />}
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
drawerType: 'slide',
|
||||
drawerStyle: {
|
||||
backgroundColor: colors.background,
|
||||
width: width * 0.75,
|
||||
},
|
||||
drawerActiveTintColor: colors.primary,
|
||||
drawerInactiveTintColor: colors.textSecondary,
|
||||
overlayColor: 'rgba(0,0,0,0.5)',
|
||||
}}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="MainTabs"
|
||||
component={BottomTab}
|
||||
options={{ title: 'Home' }}
|
||||
/>
|
||||
{/* Add other drawer screens here that are NOT in the bottom tab */}
|
||||
</Drawer.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
export default DrawerStack;
|
||||
@@ -4,35 +4,20 @@ import NavigationString from '../Constant/NavigationString';
|
||||
import Screens from '../Screens';
|
||||
import colors from '../Constant/Color';
|
||||
import BottomTab from './Bottom';
|
||||
import Login from '../Screens/Login/Login';
|
||||
|
||||
export default function MainStack() {
|
||||
const Stack = createStackNavigator();
|
||||
const option = { headerShown: false };
|
||||
const fadeTransition = {
|
||||
gestureDirection: 'horizontal',
|
||||
transitionSpec: {
|
||||
open: { animation: 'timing', config: { duration: 300 } },
|
||||
close: { animation: 'timing', config: { duration: 300 } },
|
||||
},
|
||||
cardStyleInterpolator: ({ current }) => ({
|
||||
cardStyle: {
|
||||
opacity: current.progress,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
// ...
|
||||
return (
|
||||
<Stack.Navigator
|
||||
screenOptions={{
|
||||
cardStyle: { backgroundColor: colors.white },
|
||||
...fadeTransition
|
||||
}}
|
||||
// initialRouteName={NavigationString.DecryptionScreen}
|
||||
// ...
|
||||
>
|
||||
<Stack.Screen name={NavigationString.Splash} component={Screens.Splash} options={option} />
|
||||
<Stack.Screen name={NavigationString.Signup} component={Screens.Signup} options={option} />
|
||||
<Stack.Screen name={NavigationString.Onboarding} component={Screens.Onboarding} options={option} />
|
||||
<Stack.Screen name={NavigationString.Login} component={Screens.Login} options={option} />
|
||||
<Stack.Screen name={NavigationString.Login} component={Login} options={option} />
|
||||
<Stack.Screen name={NavigationString.Home} component={BottomTab} options={option} />
|
||||
<Stack.Screen name={NavigationString.PurchaseList} component={Screens.PurchaseList} options={option} />
|
||||
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} options={option} />
|
||||
@@ -46,10 +31,12 @@ export default function MainStack() {
|
||||
<Stack.Screen name={NavigationString.Registration} component={Screens.Registration} options={option} />
|
||||
<Stack.Screen name={NavigationString.Partnership} component={Screens.Partnership} options={option} />
|
||||
<Stack.Screen name={NavigationString.LiveTenderDetail} component={Screens.LiveTenderDetail} options={option} />
|
||||
<Stack.Screen name={NavigationString.ResetPassword} component={Screens.ResetPassword} options={option} />
|
||||
<Stack.Screen name={NavigationString.ResetPassword} component={Screens.ResetPassword} options={option} />
|
||||
<Stack.Screen name={NavigationString.KycWebViewScreen} component={Screens.KycWebViewScreen} options={option} />
|
||||
<Stack.Screen name={NavigationString.StageThreeScreen} component={Screens.StageThreeScreen} options={option} />
|
||||
<Stack.Screen name={NavigationString.DecryptionScreen} component={Screens.DecryptionScreen} options={option} />
|
||||
<Stack.Screen name={NavigationString.StageThreeScreen} component={Screens.StageThreeScreen} options={option} />
|
||||
<Stack.Screen name={NavigationString.DecryptionScreen} component={Screens.DecryptionScreen} options={option} />
|
||||
<Stack.Screen name={NavigationString.DeviceBinding} component={Screens.DeviceBinding} options={option} />
|
||||
<Stack.Screen name={NavigationString.DscBinding} component={Screens.DscBinding} options={option} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user