diff --git a/app.json b/app.json index 2dea4a3..3ff74e3 100644 --- a/app.json +++ b/app.json @@ -9,7 +9,7 @@ "policy": "sdkVersion" }, "orientation": "portrait", - "icon": "./assets/images/KITAPP.png", + "icon": "./assets/images/kitlog.png", "userInterfaceStyle": "automatic", "newArchEnabled": true, "ios": { @@ -24,20 +24,20 @@ } }, "splash": { - "image": "./assets/images/KITAPP.png", - "resizeMode": "cover", - "backgroundColor": "#4B6E7C" + "image": "./assets/images/kitlog.png", + "resizeMode": "contain", + "backgroundColor": "#b7caaf" }, "android": { "adaptiveIcon": { - "foregroundImage": "./assets/images/KITAPP.png", - "backgroundColor": "#4B6E7C" + "foregroundImage": "./assets/images/kitlog.png", + "backgroundColor": "#b7caaf" } }, "web": { "bundler": "metro", "output": "static", - "favicon": "./assets/images/KITAPP.png" + "favicon": "./assets/images/kitlog.png" }, "extra": { "eas": { diff --git a/assets/images/KITAPP.png b/assets/images/KITAPP.png deleted file mode 100644 index b2b3f25..0000000 Binary files a/assets/images/KITAPP.png and /dev/null differ diff --git a/assets/images/kitlog.png b/assets/images/kitlog.png new file mode 100644 index 0000000..ee67c72 Binary files /dev/null and b/assets/images/kitlog.png differ diff --git a/assets/images/noCover.jpg b/assets/images/noCover.jpg new file mode 100644 index 0000000..d900c7e Binary files /dev/null and b/assets/images/noCover.jpg differ diff --git a/assets/images/splash.png b/assets/images/splash.png deleted file mode 100644 index eadd53d..0000000 Binary files a/assets/images/splash.png and /dev/null differ diff --git a/assets/images/unknownBook.jpg b/assets/images/unknownBook.jpg deleted file mode 100644 index 126c084..0000000 Binary files a/assets/images/unknownBook.jpg and /dev/null differ diff --git a/src/components/CurrentPageSection.tsx b/src/components/CurrentPageSection.tsx new file mode 100644 index 0000000..3be5411 --- /dev/null +++ b/src/components/CurrentPageSection.tsx @@ -0,0 +1,113 @@ +import React from "react"; +import { + View, + Text, + TouchableOpacity, + TextInput, + StyleSheet, +} from "react-native"; +import { colors } from "../constants/colors"; +import { sizes } from "../constants/sizes"; +import { useTranslation } from "react-i18next"; +import AsyncStorage from "@react-native-async-storage/async-storage"; + +interface CurrentPageSectionProps { + book: any; + setParsedBook: React.Dispatch>; +} + +export const CurrentPageSection: React.FC = ({ + book, + setParsedBook, +}) => { + const { t } = useTranslation(); + + const handleCurrentPageChange = async (newCurPage: number) => { + setParsedBook((prevBook: any) => ({ + ...prevBook, + currentPage: newCurPage, + })); + try { + const existingBooks = await AsyncStorage.getItem("books"); + const books = existingBooks ? JSON.parse(existingBooks) : []; + const updatedBooks = books.map((b: any) => { + return { + ...b, + currentPage: b.title === book.title ? newCurPage : b.currentPage, + }; + }); + await AsyncStorage.setItem("books", JSON.stringify(updatedBooks)); + } catch (error) { + console.error("Error updating favorite page", error); + } + }; + + return ( + <> + {t("current_page")} + + { + const newCurPage = book.currentPage - 1; + handleCurrentPageChange(newCurPage); + }} + > + - + + { + const newCurPage = parseInt(text, 10) || 0; + handleCurrentPageChange(newCurPage); + }} + /> + { + const newCurPage = book.currentPage + 1; + handleCurrentPageChange(newCurPage); + }} + > + + + + + + ); +}; + +const styles = StyleSheet.create({ + label: { + fontWeight: "bold", + color: colors.textPrimary, + fontSize: sizes.fontSizeSmall, + }, + incrementContainer: { + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + marginTop: 10, + }, + incrementButton: { + padding: 10, + backgroundColor: colors.primary, + borderRadius: sizes.borderRadius, + }, + incrementButtonText: { + color: colors.white, + fontSize: sizes.fontSizeMedium, + }, + incrementInput: { + width: 50, + height: 40, + textAlign: "center", + borderColor: colors.secondary, + borderWidth: 1, + marginHorizontal: 10, + borderRadius: sizes.borderRadius, + color: colors.textPrimary, + fontSize: sizes.fontSizeMedium, + }, +}); diff --git a/src/components/CurrentlyReadingBook.tsx b/src/components/CurrentlyReadingBook.tsx index 5256d4d..54e2828 100644 --- a/src/components/CurrentlyReadingBook.tsx +++ b/src/components/CurrentlyReadingBook.tsx @@ -7,6 +7,7 @@ import { useNavigation } from "@react-navigation/native"; import { StackNavigationProp } from "@react-navigation/stack"; import { useTranslation } from "react-i18next"; import { ProgressBar } from "react-native-paper"; +import { getSecureImageUrl } from "../util/getSecureImageUrl"; type RootStackParamList = { BookPreview: { @@ -18,7 +19,6 @@ type RootStackParamList = { publication: string; review: string; rating: number; - saveDate: Date; status: string; favPage?: number; favPageImage?: string; @@ -33,36 +33,23 @@ type BookScreenNavigationProp = StackNavigationProp< >; type CurrentlyReadingBookProps = { - title: string; - author: string; - image: string; - pages: string; - publication: string; - review: string; - rating: number; - status: string; - favPage?: number; - favPageImage?: string; - currentPage?: number; -}; - -const getSecureImageUrl = (url: string | undefined) => { - if (!url) return "https://via.placeholder.com/128x192?text=No+Cover"; - return url.replace("http://", "https://").replace("&edge=curl", ""); + book: any; }; export const CurrentlyReadingBook = ({ - title, - author, - image, - pages, - publication, - review, - rating, - status, - favPage, - favPageImage, - currentPage, + book: { + title, + author, + image, + pages, + publication, + review, + rating, + status, + favPage, + favPageImage, + currentPage, + }, }: CurrentlyReadingBookProps) => { const navigation = useNavigation(); const { t } = useTranslation(); @@ -76,7 +63,6 @@ export const CurrentlyReadingBook = ({ publication, review, rating, - saveDate: new Date(), status, favPage, favPageImage, @@ -92,7 +78,7 @@ export const CurrentlyReadingBook = ({ source={ image ? { uri: getSecureImageUrl(image) } - : require("../../assets/images/unknownBook.jpg") + : require("../../assets/images/noCover.jpg") } style={styles.image} /> @@ -107,12 +93,18 @@ export const CurrentlyReadingBook = ({ - {t("current_page")}: {currentPage} + {t("current_page")}: {currentPage?.toString() || "0"} diff --git a/src/components/CustomDropDownPicker.tsx b/src/components/CustomDropDownPicker.tsx new file mode 100644 index 0000000..0a1287e --- /dev/null +++ b/src/components/CustomDropDownPicker.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import DropDownPicker from "react-native-dropdown-picker"; +import { colors } from "../constants/colors"; +import { useTranslation } from "react-i18next"; +import { View } from "react-native"; + +interface CustomDropDownPickerProps { + open: boolean; + value: string; + setOpen: React.Dispatch>; + setValue: React.Dispatch>; +} + +export const CustomDropDownPicker = ({ + open, + value, + setOpen, + setValue, +}: CustomDropDownPickerProps) => { + const { t } = useTranslation(); + + const items = [ + { label: t("read"), value: "read" }, + { label: t("to_read"), value: "to_read" }, + { label: t("currently_reading"), value: "currently_reading" }, + ]; + const getStatusLabel = (statusKey: string) => { + switch (statusKey) { + case "read": + return t("read"); + case "to_read": + return t("to_read"); + case "currently_reading": + return t("currently_reading"); + } + }; + return ( + + {}} + style={styles.statusDropdown} + dropDownContainerStyle={{ + backgroundColor: colors.background, + borderColor: colors.secondary, + }} + selectedItemContainerStyle={{ + backgroundColor: colors.backgroundSecondary, + }} + placeholder={getStatusLabel(value)} + /> + + ); +}; +const styles = { + statusDropdown: { + backgroundColor: colors.background, + borderRadius: 5, + borderWidth: 1, + borderColor: colors.secondary, + }, +}; diff --git a/src/components/FavImageSection.tsx b/src/components/FavImageSection.tsx new file mode 100644 index 0000000..3753ab3 --- /dev/null +++ b/src/components/FavImageSection.tsx @@ -0,0 +1,187 @@ +import { Ionicons } from "@expo/vector-icons"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { View, TouchableOpacity, Modal, FlexAlignType } from "react-native"; +import * as ImagePicker from "expo-image-picker"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { Text, Image } from "react-native"; +import { colors } from "../constants/colors"; +import { sizes } from "../constants/sizes"; +import { getSecureImageUrl } from "../util/getSecureImageUrl"; + +export const FavImageSection = ({ book }: { book: any }) => { + const { t } = useTranslation(); + + const [isModalVisible, setIsModalVisible] = useState(false); + const [favPageImages, setFavPageImages] = useState( + book.favPageImage ? [book.favPageImage] : [] + ); + const [selectedImageIndex, setSelectedImageIndex] = useState( + null + ); + + useEffect(() => { + const loadFavPageImages = async () => { + try { + const storedFavPageImages = await AsyncStorage.getItem( + `favPageImages_${book.title}` + ); + if (storedFavPageImages) { + setFavPageImages(JSON.parse(storedFavPageImages)); + } + } catch (error) { + console.error("Error loading favorite page images", error); + } + }; + + loadFavPageImages(); + }, [book.title]); + + const pickFavPageImage = async () => { + try { + const permissionResult = + await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (permissionResult.status !== "granted") { + alert(t("permission_required")); + return; + } + let result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: true, + aspect: [2, 3], + quality: 0.5, + }); + if (!result.canceled) { + const selectedAsset = result.assets[0]; + if (selectedAsset?.uri) { + const updatedFavPageImages = [...favPageImages, selectedAsset.uri]; + setFavPageImages(updatedFavPageImages); + await AsyncStorage.setItem( + `favPageImages_${book.title}`, + JSON.stringify(updatedFavPageImages) + ); + } + } + } catch (error) { + console.error("Error picking image:", error); + alert(t("error_picking_image")); + } + }; + + return ( + <> + + {t("favPage")} + + + + {t("add")} + + + + + {favPageImages.map((imageUri, index) => ( + { + setSelectedImageIndex(index); + setIsModalVisible(true); + }} + style={styles.favPageImageWrapper} + > + + + ))} + + setIsModalVisible(false)} + > + + setIsModalVisible(false)} + > + {selectedImageIndex !== null && ( + + )} + + + + + ); +}; +const styles = { + label: { + fontWeight: "bold", + color: colors.textPrimary, + fontSize: sizes.fontSizeSmall, + marginRight: 140, + }, + favPageImage: { + width: "100%", + height: 200, + borderRadius: sizes.borderRadius, + backgroundColor: colors.background, + marginTop: 10, + }, + favPageImageContainer: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-evenly", + marginTop: 10, + }, + addButton: { + padding: 8, + flexDirection: "row", + alignItems: "center", + }, + addButtonContainer: { + backgroundColor: colors.primary, + borderRadius: sizes.borderRadius, + paddingHorizontal: 12, + }, + addButtonText: { + color: "white", + marginLeft: 4, + fontSize: sizes.fontSizeSmall, + fontWeight: "bold", + }, + favPageImagesGrid: { + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "space-between", + }, + favPageImageWrapper: { + width: "48%", + marginBottom: 10, + }, + modalContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + backgroundColor: colors.blur, + }, + modalBackground: { + width: "100%", + height: "100%", + }, + modalImage: { + width: "90%", + height: "90%", + resizeMode: "contain", + alignSelf: "center", + }, +}; diff --git a/src/components/addButton.tsx b/src/components/addButton.tsx index cc4c855..b24477c 100644 --- a/src/components/addButton.tsx +++ b/src/components/addButton.tsx @@ -4,7 +4,7 @@ import { Ionicons } from "@expo/vector-icons"; import { colors } from "../constants/colors"; import { sizes } from "../constants/sizes"; -export default function AddButton({ onPress }: { onPress: () => void }) { +export const AddButton: React.FC<{ onPress: () => void }> = ({ onPress }) => { return ( void }) { /> ); -} +}; const styles = StyleSheet.create({ floatingButton: { diff --git a/src/components/book.tsx b/src/components/book.tsx index 68c06f8..92ae5f5 100644 --- a/src/components/book.tsx +++ b/src/components/book.tsx @@ -5,7 +5,7 @@ import { StackNavigationProp } from "@react-navigation/stack"; import { colors } from "../constants/colors"; import { sizes } from "../constants/sizes"; import { Ionicons } from "@expo/vector-icons"; -import { useTranslation } from "react-i18next"; +import { getSecureImageUrl } from "../util/getSecureImageUrl"; type RootStackParamList = { BookPreview: { @@ -17,7 +17,6 @@ type RootStackParamList = { publication: string; review: string; rating: number; - saveDate: Date; status: string; favPage?: number; favPageImage?: string; @@ -31,6 +30,10 @@ type BookScreenNavigationProp = StackNavigationProp< "BookPreview" >; +type BookProps = { + book: any; +}; + const getStatusIcon = (status: string) => { switch (status) { case "read": @@ -45,52 +48,34 @@ const getStatusIcon = (status: string) => { }; export const Book = ({ - title, - author, - image, - pages, - publication, - review, - rating, - status, - favPage, - favPageImage, - currentPage, - style, -}: { - title: string; - author: string; - image: string; - pages: string; - publication: string; - review: string; - rating: number; - status: string; - favPage?: number; - favPageImage?: string; - currentPage?: number; - style?: object; -}) => { + book: { + title, + author, + image, + pages, + publication, + review, + rating, + status, + favPage, + favPageImage, + currentPage, + }, +}: BookProps) => { const navigation = useNavigation(); - const { t } = useTranslation(); - const statusIcon = getStatusIcon(status, t); - - const getSecureImageUrl = (url: string | undefined) => { - if (!url) return "https://via.placeholder.com/128x192?text=No+Cover"; - return url.replace("http://", "https://").replace("&edge=curl", ""); - }; - + const statusIcon = getStatusIcon(status); const handlePress = () => { navigation.navigate("BookPreview", { book: { title, author, - image: getSecureImageUrl(image), + image: getSecureImageUrl(image) + ? getSecureImageUrl(image) + : require("../../assets/images/noCover.jpg"), pages, publication, review, rating, - saveDate: new Date(), status, favPage, favPageImage, @@ -100,7 +85,7 @@ export const Book = ({ }; return ( - + @@ -108,8 +93,12 @@ export const Book = ({ diff --git a/src/components/filterModal.tsx b/src/components/filterModal.tsx index 00e3d22..a320b43 100644 --- a/src/components/filterModal.tsx +++ b/src/components/filterModal.tsx @@ -6,8 +6,6 @@ import { TextInput, TouchableOpacity, StyleSheet, - Platform, - Button, } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import { colors } from "../constants/colors"; @@ -20,8 +18,8 @@ import Animated, { runOnJS, } from "react-native-reanimated"; import { StarRating } from "./StarRating"; -import { Picker } from "@react-native-picker/picker"; import { useTranslation } from "react-i18next"; +import { CustomDropDownPicker } from "./CustomDropDownPicker"; interface FilterModalProps { visible: boolean; @@ -38,7 +36,7 @@ interface FilterModalProps { }; } -const FilterModal = ({ +export const FilterModal = ({ visible, onClose, onApplyFilters, @@ -51,7 +49,8 @@ const FilterModal = ({ const [isClosing, setIsClosing] = useState(false); const translateY = useSharedValue(1000); const backgroundOpacity = useSharedValue(0); - const [isPickerVisible, setPickerVisible] = useState(false); + const [bookStatus, setBookStatus] = useState(""); + const [open, setOpen] = useState(false); useEffect(() => { if (visible) { @@ -72,6 +71,10 @@ const FilterModal = ({ } }, [visible]); + useEffect(() => { + setStatus(bookStatus); + }, [bookStatus]); + const closeModal = () => { setIsClosing(true); translateY.value = withSpring( @@ -182,19 +185,13 @@ const FilterModal = ({ {t("status")} - setPickerVisible(true)} - > - - {status || t("selectStatus")} - - + setStatus(value)} + /> - - setPickerVisible(false)} - > - - -