app.js
import 'react-native-gesture-handler';
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import DrawerNavigator from './src/views/navigators/DrawerNavigator';
import DetailsScreen from './src/views/screens/DetailsScreen';
const Stack = createNativeStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="HomeScreen" component={DrawerNavigator} />
<Stack.Screen name="DetailsScreen" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
src\views\screens\DetailsScreen.js
mport { StyleSheet, Text, View, SafeAreaView, StatusBar, Image, ImageBackground } from 'react-native';
import React from 'react';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import COLORS from "../../const/colors"
import { Colors } from 'react-native/Libraries/NewAppScreen';
const DetailsScreen = ({ navigation, route }) => {
// 這邊注意 怎麼跟上一頁(homescreen) 拿資料到這一頁來.
const pet = route.params;
console.log("pet= ", pet)
return (
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.white }}>
<StatusBar backgroundColor={COLORS.background} />
{/* 標頭 */}
<View style={{ height: 400, backgroundColor: COLORS.background }}>
{/* 背景圖 */}
<ImageBackground
source={pet?.image}
resizeMode="contain" style={{ height: 280, top: 20 }}
>
<View style={styles.Header}>
<Icon name="arrow-left" size={28} color={COLORS.dark} onPress={navigation.goBack} />
<Icon name="dots-vertical" size={28} color={COLORS.dark} />
</View>
</ImageBackground>
{/* 中間的資訊欄 */}
<View style={styles.DetailContainer}>
<View style={{
flexDirection: "row",
justifyContent: "space-between"
}}>
<Text style={{
fontSize: 20,
color: COLORS.dark,
fontWeight: "bold",
}}>
{pet?.name}</Text>
<Icon name="gender-male" size={25} color={COLORS.dark} />
</View>
<View style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 5,
}}>
<Text style={{
fontSize: 12,
color: COLORS.dark,
}}>{pet?.type}</Text>
<Text style={{
fontSize: 13,
color: COLORS.dark,
}}>{pet?.age}</Text>
</View>
<View style={{ flexDirection: "row", marginTop: 5 }}>
<Icon name='map-marker' size={20} color={COLORS.primary} />
<Text style={{ fontSize: 14, marginLeft: 5, color: COLORS.grey }}>5 Green Swamp Road, Nyora, New South Wales, 2646 Australia</Text>
</View>
</View>
</View>
{/* 下方資訊欄 */}
<View style={{ flex: 1, marginTop: 80, justifyContent: "space-between" }}>
<View>
<View style={{ flexDirection: "row", paddingHorizontal: 20 }}>
{/* 左邊:頭像 */}
<Image source={require("../../assets/person.png")}
style={{
width: 40,
height: 40,
borderRadius: 20
}}
/>
{/* 右邊: 個人資訊 */}
<View style={{ flex: 1, paddingLeft: 10, height: 20 }}>
<Text style={{ fontSize: 12, color: COLORS.dark, fontWeight: "bold" }}>Smile Hsu</Text>
<Text style={{ fontSize: 11, color: COLORS.grey, fontWeight: "bold", marginTop: 2 }}>Owner</Text>
</View>
{/* 注意: 這邊文字一開始無法顯示 是要去 最上層 SafeAreaView 把 flex:1 加上去才正常 */}
<Text style={{ fontSize: 12, color: COLORS.grey }}>Feb 10, 2022</Text>
</View>
<Text style={styles.Comment}>Lorem ipsum dolor sit amet consectetur adipisicing elit. Minima id exercitationem impedit eveniet nobis molestias nostrum dolorem officiis?</Text>
</View>
{/* 頁尾 按鈕 */}
<View style={styles.Footer}>
<View style={styles.IconContainer}>
<Icon name='heart-outline' size={22} color={COLORS.white} />
</View>
<View style={styles.Btn}>
<Text style={{ color: COLORS.white, fontWeight: "bold" }}>ADOPTION</Text>
</View>
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
Header: {
flexDirection: 'row',
justifyContent: "space-between",
padding: 20,
}, DetailContainer: {
flex: 1,
height: 120,
backgroundColor: COLORS.white,
padding: 20,
marginHorizontal: 20,
bottom: -60,
elevation: 10,
borderRadius: 20,
justifyContent: "center",
},
Comment: {
marginTop: 10,
fontSize: 12.5,
color: COLORS.dark,
marginHorizontal: 20,
lineHeight: 20,
},
Footer: {
flexDirection: "row",
height: 100,
backgroundColor: COLORS.light,
borderTopRightRadius: 30,
borderTopLeftRadius: 30,
alignItems: "center",
paddingHorizontal: 20,
},
IconContainer: {
backgroundColor: COLORS.primary,
width: 50,
height: 50,
borderRadius: 12,
justifyContent: "center",
alignItems: "center",
marginTop: 15
},
Btn: {
flex: 1,
backgroundColor: COLORS.primary,
height: 50,
borderRadius: 12,
justifyContent: "center",
alignItems: "center",
marginTop: 15,
marginLeft: 20,
},
});
export default DetailsScreen;
src\views\navigators\DrawerNavigator.js
import { StatusBar, View, Image, Text } from 'react-native';
import React from 'react';
import {
createDrawerNavigator,
DrawerContentScrollView,
DrawerItemList,
useDrawerProgress,
useDrawerStatus
} from '@react-navigation/drawer';
import HomeScreen from "../screens/HomeScreen"
import COLORS from '../../const/colors';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import Animated from 'react-native-reanimated';
const Drawer = createDrawerNavigator();
//
const CustomDrawerContent = props => {
return (
<DrawerContentScrollView style={{ paddingVertical: 30 }}>
<View style={{ marginLeft: 20, marginVertical: 40 }}>
<Image source={require("../../assets/person.png")}
style={{
width: 70,
height: 70,
borderRadius: 20
}}
/>
<Text style={{ color: COLORS.white, fontSize: 13, fontWeight: "bold", marginTop: 10 }}> Smile Hsu </Text>
</View>
<DrawerItemList
{...props}
/>
</DrawerContentScrollView>
)
}
const DrawerScreenContainer = ({ children }) => {
// 判斷 Drawer 頁面是否作用中
const isDrawerOpen = useDrawerStatus()
//
const progress = useDrawerProgress()
//
const scale = Animated.interpolateNode(progress, { inputRange: [0, 1], outputRange: [1, 0.8] })
//
const borderRadius = Animated.interpolateNode(progress, { inputRange: [0, 1], outputRange: [0, 25] })
return (
<Animated.View style={{ backgroundColor: COLORS.white, flex: 1, transform: [{ scale }], overflow: "hidden", borderRadius }}>
<StatusBar barStyle='dark-content'
// 根據 Drawer 頁面是否作用中 改變 statusbar 的顏色
backgroundColor={isDrawerOpen == "open" ? COLORS.primary : COLORS.white} />
{children}
</Animated.View>
)
}
const DrawerNavigator = () => {
return (
<Drawer.Navigator screenOptions={{
headerShown: false,
drawerType: "slide",
drawerStyle: {
width: 200,
backgroundColor: COLORS.primary,
},
overlayColor: null,
sceneContainerStyle: {
// 此設定 在 android無效? 影片是用iso
backgroundColor: COLORS.primary
},
// 作用中的顏色
drawerActiveTintColor: COLORS.white,
//非作用中的顏色
drawerInactiveTintColor: COLORS.secondary,
//取消 選項的 背景顏色
drawerItemStyle: { backgroundColor: null },
//
drawerLabelStyle: { fontWeight: "bold" },
}}
drawerContent={props => <CustomDrawerContent {...props} />}
>
{/* ==================== 選單項目 ==================== */}
<Drawer.Screen name="Home" options={{
title: "ADOPTION", drawerIcon: ({ color }) =>
<Icon name="paw"
size={25}
color={color}
style={{ marginRight: -20 }}
/>
}} >
{(props) =>
(<DrawerScreenContainer>
<HomeScreen {...props} />
</DrawerScreenContainer>)
}
</Drawer.Screen>
{/* ==================== 選單項目 ==================== */}
<Drawer.Screen name="DONATION" options={{
drawerIcon: ({ color }) =>
<Icon name="gift"
size={25}
color={color}
style={{ marginRight: -20 }}
/>
}} >
{(props) =>
(<DrawerScreenContainer>
<HomeScreen {...props} />
</DrawerScreenContainer>)
}
</Drawer.Screen>
{/* ==================== 選單項目 ==================== */}
<Drawer.Screen name="ADD PET" options={{
drawerIcon: ({ color }) =>
<Icon name="plus-box"
size={25}
color={color}
style={{ marginRight: -20 }}
/>
}} >
{(props) =>
(<DrawerScreenContainer>
<HomeScreen {...props} />
</DrawerScreenContainer>)
}
</Drawer.Screen>
{/* ==================== 選單項目 ==================== */}
<Drawer.Screen name="FAVORITES" options={{
drawerIcon: ({ color }) =>
<Icon name="heart"
size={25}
color={color}
style={{ marginRight: -20 }}
/>
}} >
{(props) =>
(<DrawerScreenContainer>
<HomeScreen {...props} />
</DrawerScreenContainer>)
}
</Drawer.Screen>
{/* ==================== 選單項目 ==================== */}
<Drawer.Screen name="PROFILE" options={{
drawerIcon: ({ color }) =>
<Icon name="account"
size={25}
color={color}
style={{ marginRight: -20 }}
/>
}} >
{(props) =>
(<DrawerScreenContainer>
<HomeScreen {...props} />
</DrawerScreenContainer>)
}
</Drawer.Screen>
</Drawer.Navigator>
);
};
export default DrawerNavigator;
src\views\screens\HomeScreen.js
import React, { useEffect, useState } from 'react';
import {
Dimensions,
StyleSheet,
Text,
View,
SafeAreaView,
Image,
TextInput,
TouchableOpacity,
FlatList
} from 'react-native';
import { ScrollView } from 'react-native-virtualized-view';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import COLORS from "../../const/colors"
//寵物資料的api
import pets from '../../const/pets';
//
const { height } = Dimensions.get("window")
//寵物分類的選單項目
const petCategories = [
{ name: 'CATS', icon: 'cat' },
{ name: 'DOGS', icon: 'dog' },
{ name: 'BIRDS', icon: 'ladybug' },
{ name: 'BUNNIES', icon: 'rabbit' },
];
// 寵物卡片
const Card = ({ pet, navigation }) => {
return (
<TouchableOpacity activeOpacity={0.8} onPress={() => navigation.navigate("DetailsScreen", pet)}>
<View style={styles.CardContainer}>
{/* 寵物的圖片 */}
<View style={styles.CardImageContainer}>
<Image source={pet.image}
style={{ width: "100%", height: "100%", resizeMode: "contain" }}
/>
</View>
{/* 寵物的簡介 */}
<View style={styles.CardDetailContainer}>
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text style={{ color: COLORS.dark, fontSize: 20, fontWeight: "bold" }}>{pet?.name}</Text>
<Icon name='gender-male' size={22} color={COLORS.grey}
/>
</View>
<Text style={{ fontSize: 12, marginTop: 5, color: COLORS.dark }}>{pet?.type}</Text>
<Text style={{ fontSize: 10, marginTop: 5, color: COLORS.grey }}>{pet?.age}</Text>
<View style={{ flexDirection: "row", marginTop: 5 }}>
<Icon name='map-marker' size={18} color={COLORS.primary} />
<Text style={{ fontSize: 12, marginLeft: 5, color: COLORS.primary }}>Distance:7.8km</Text>
</View>
</View>
</View>
</TouchableOpacity >
)
}
const HomeScreen = ({ navigation }) => {
//
const [selectedCategoryIndex, setSelectedCategoryIndex] = useState(0)
const [filteredPets, setFilteredPet] = useState([])
//篩選寵物
const filterPet = (index) => {
const currentPets = pets.filter((item) => item?.pet?.toLocaleUpperCase() == petCategories[index].name)[0].pets
// console.log("currentPets =", currentPets[0])
setFilteredPet(currentPets)
}
//
useEffect(() => {
filterPet(0)
}, [])
return (
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.white }}>
{/* 標頭 header */}
<View style={styles.Header}>
<Icon name="sort-variant" size={28} onPress={navigation.toggleDrawer} />
<Text style={{ color: COLORS.primary, fontSize: 16, fontWeight: "bold" }}>Smile Hsu</Text>
<Image source={require(".././../assets/person.png")} style={{ width: 30, height: 30, borderRadius: 15 }} />
</View>
{/* 主要內容 main */}
<ScrollView showsVerticalScrollIndicator={false}>
<View style={styles.MainContainer}>
{/* 搜尋列 search bar */}
<View style={styles.SearchBarContainer}>
<Icon name='magnify' size={24} color={COLORS.grey} />
{/* flex:1 讓字靠右 */}
<TextInput
placeholder='Search pet to adopt'
placeholderTextColor={COLORS.grey}
style={{ flex: 1 }} />
<Icon name="sort-ascending" size={24} color={COLORS.grey} />
</View>
{/* 寵物分類選單 */}
<View style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 20,
}}>
{/* 用map輸出 寵物分類選單的項目 */}
{petCategories.map((item, index) => (
// 讓icon跟下方文字 置中對齊 要寫在這邊
<View key={"pet" + index} style={{ alignItems: "center" }}>
{/* 這邊注意 點選icon 會改變顏色 的寫法 */}
{/* 注意 onpress 加入 filterPet(index) 第一次用到 大刮號 加兩個函式 */}
<TouchableOpacity style={[styles.CategoryBtn, { backgroundColor: selectedCategoryIndex == index ? COLORS.primary : COLORS.white }]}
onPress={() => {
filterPet(index)
setSelectedCategoryIndex(index)
}}
>
<Icon name={item.icon} size={30} color={selectedCategoryIndex == index ? COLORS.white : COLORS.primary} />
</TouchableOpacity>
<Text style={styles.CategoryBtnName}>{item.name}</Text>
</View>
))}
</View>
{/* 下方的寵物展示 內容區塊 */}
<View style={{ marginTop: 20 }}>
<FlatList
showsVerticalScrollIndicator={false}
data={filteredPets}
renderItem={({ item }) => <Card pet={item} navigation={navigation} />}
/>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
Header: {
padding: 20,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
MainContainer: {
minHeight: height,
backgroundColor: COLORS.light,
marginTop: 20,
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
paddingHorizontal: 20,
paddingVertical: 40,
},
SearchBarContainer: {
flexDirection: "row",
height: 50,
backgroundColor: COLORS.white,
borderRadius: 7,
paddingHorizontal: 20,
justifyContent: "space-between",
alignItems: "center",
},
CategoryBtn: {
width: 50,
height: 50,
justifyContent: "center",
alignItems: "center",
borderRadius: 10,
backgroundColor: COLORS.primary,
},
CategoryBtnName: {
color: COLORS.dark,
fontSize: 10,
fontWeight: "bold",
marginTop: 5,
},
CardContainer: {
flexDirection: "row",
alignItems: "center",
marginBottom: 20,
}, CardImageContainer: {
width: 140,
height: 150,
backgroundColor: COLORS.background,
borderRadius: 20,
},
CardDetailContainer: {
flex: 1,
height: 120,
backgroundColor: COLORS.white,
borderTopRightRadius: 10,
borderBottomRightRadius: 10,
padding: 20,
justifyContent: "center"
},
});
export default HomeScreen;
src\const\pet.js
const pets = [
{
pet: 'cats',
pets: [
{
id: '1',
name: 'Lily',
image: require('../assets/cat1.png'),
type: 'Chausie',
age: '5 years old',
},
{
id: '2',
name: 'Lucy',
image: require('../assets/cat2.png'),
type: 'Bobtail',
age: '2 years old',
},
{
id: '3',
name: 'Nala',
image: require('../assets/cat3.png'),
type: 'Ragamuffin',
age: '2 years old',
},
],
},
{
pet: 'dogs',
pets: [
{
id: '1',
name: 'Bally',
image: require('../assets/dog1.png'),
type: 'German Shepherd',
age: '2 years old',
},
{
id: '2',
name: 'Max',
image: require('../assets/dog2.png'),
type: 'Foxhound',
age: '2 years old',
},
],
},
{
pet: 'birds',
pets: [
{
id: '1',
name: 'Coco',
image: require('../assets/bird1.png'),
type: 'Parrot',
age: '2 years old',
},
{
id: '2',
name: 'Alfie',
image: require('../assets/bird2.png'),
type: 'Parrot',
age: '4 years old',
},
],
},
{
pet: 'bunnies',
pets: [
{
id: '1',
name: 'Boots',
image: require('../assets/bunny1.png'),
type: 'Angora',
age: '1 years old',
},
{
id: '2',
name: 'Pookie',
image: require('../assets/bunny2.png'),
type: 'Angora',
age: '1 years old',
},
],
},
];
export default pets;
src\const\colors.js
const COLORS = {
primary: '#306060',
secondary: '#88b3b5',
white: '#FFF',
dark: '#616161',
light: '#f5f5f5',
grey: '#a8a8a8',
background: '#d0d8dc',
orange: '#f5a623',
green: '#00B761',
};
export default COLORS;
git clone https://smilehsu@bitbucket.org/smilehsu/yt_example_petui0210.git