diff --git a/android/app/build.gradle b/android/app/build.gradle index e9c558e..50a97f3 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -99,8 +99,8 @@ android { applicationId "com.myclosetx" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 3 - versionName "1.0.4" + versionCode 4 + versionName "1.0.5" } splits { diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 4b185bc..6d5b6b0 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> + + @@ -7,6 +7,11 @@ + + + + + { -// Alert.alert("Color Selected", `You selected color: ${color}`); -// }; +import { localization } from "../utils/localization"; +import { useSelector } from "react-redux"; +import { RootState } from "../redux/store"; const ColorFilter = ({ colors, @@ -21,8 +14,6 @@ const ColorFilter = ({ colors: string[]; setColors: Dispatch>; }) => { - // const isDarkMode = useColorScheme() === "dark"; - const basicColors: { [key: string]: string } = { yellow: "#FEFD00", yellow_Orange: "#F8A900", @@ -41,11 +32,12 @@ const ColorFilter = ({ gray: "#808080", unknown: "", }; + const storedSettings = useSelector((state: RootState) => state.settings); return ( - Filter By Color + {localization.FilterByColor[storedSettings.language]} {Object.keys(basicColors).map((name) => ( diff --git a/package.json b/package.json index 3a7f994..2397e9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "closet-archive", - "version": "1.0.4", + "version": "1.0.5", "private": true, "scripts": { "android": "react-native run-android", @@ -55,7 +55,12 @@ "expo-application", "expo-constants", "expo-font", - "expo-keep-awake" + "expo-splash-screen", + "expo-notifications", + "expo-updates", + "expo-location", + "expo-web-browser", + "expo-av" ] } }, diff --git a/redux/settingsSlice.ts b/redux/settingsSlice.ts index bcce8ef..fe01239 100644 --- a/redux/settingsSlice.ts +++ b/redux/settingsSlice.ts @@ -5,6 +5,7 @@ export type SettingsType = { name: string; laundryNumber: number; enableLaundry: boolean; + enableReminder: boolean; appVer: string; }; const initialState: SettingsType = { @@ -12,6 +13,7 @@ const initialState: SettingsType = { name: "User", laundryNumber: 5, enableLaundry: true, + enableReminder: true, appVer: "1.0.1", }; const settingsSlice = createSlice({ @@ -30,10 +32,15 @@ const settingsSlice = createSlice({ setEnableLaundry: (state, action) => { state.enableLaundry = action.payload.enableLaundry; }, + setReminder: (state, action) => { + state.enableReminder = action.payload.enableReminder; + }, importSettings: (state, action) => { state.language = action.payload.language; state.laundryNumber = action.payload.laundryNumber; state.name = action.payload.name; + state.enableLaundry = action.payload.enableLaundry; + state.enableReminder = action.payload.enableReminder; }, setAppVer: (state, action) => { state.appVer = action.payload.appVer; @@ -45,6 +52,7 @@ export const { userNameSetter, laundryNumberSetter, setEnableLaundry, + setReminder, importSettings, setAppVer, } = settingsSlice.actions; diff --git a/routers/stack.tsx b/routers/stack.tsx index ab649ff..fcc856e 100644 --- a/routers/stack.tsx +++ b/routers/stack.tsx @@ -9,6 +9,8 @@ import { EventLogForm } from "../screens/stackNav/EventLogForm"; import { CollectionForm } from "../screens/stackNav/CollectionForm"; import { Settings } from "../screens/stackNav/Settings"; import { ClosetInfo } from "../screens/stackNav/ClosetInfo"; +import { ModeScreen } from "../screens/stackNav/ModeScreen"; +import { BulkModeForm } from "../screens/stackNav/BulkModeForm"; export default function Navigator() { const Stack = createNativeStackNavigator(); return ( @@ -16,7 +18,7 @@ export default function Navigator() { + + { + const { selectedCategory } = route.params; + const [prefix, setPrefix] = useState(""); + const [imageNum, setImageNum] = useState(2); + const [openType, setOpenType] = useState(false); + const [openCollection, setOpenCollection] = useState(false); + const colorScheme = String(useColorScheme()?.toUpperCase()) as ThemeNameType; + const [collection, setCollection] = useState([]); + const [type, setType] = useState(""); + const dispatch = useDispatch(); + const [errorsList, setErrorsList] = useState([]); + // const [images, setImages] = useState<[]>([]); + + const storedSettings = useSelector((state: RootState) => state.settings); + const collectionState = useSelector( + (state: RootState) => state.itemsList.collectionTags, + ); + + const handleImagePicker = async (type: number, createItem: any) => { + if (type === 0) { + try { + const response = await launchImageLibrary({ + mediaType: "photo", + selectionLimit: imageNum, + }); + + if (response.didCancel) { + console.log("Image picker cancelled"); + return; + } else if (response.errorCode) { + console.log("Image picker error: ", response.errorMessage); + return; + } + + const newImages = []; + for (let i = 0; i < response.assets?.length; i++) { + newImages.push(response.assets[i]?.uri); + } + createItem(newImages); // Update the state with the new array + } catch (error) { + console.error("Image picker error:", error); + } + } else { + const newImages = []; + for (let i = 0; i < imageNum; i++) { + try { + await launchCamera( + { + mediaType: "photo", + }, + (response) => { + if (response.didCancel) { + console.log("Image picker cancelled"); + } else if (response.errorCode) { + console.log("Image picker error: ", response.errorMessage); + return; + } else { + newImages.push(response.assets[0]?.uri); + } + }, + ); + } catch (error) { + console.log("permission error", error); + } + } + createItem(newImages); + } + }; + function addItemHandler(mode: number) { + const errors = []; + + if (prefix.length <= 0) { + errors.push("Please enter item prefix"); + } + if (prefix.length > 20) { + errors.push("Please enter a name within 20 characters"); + } + + if (errors.length > 0) { + setErrorsList(errors); + return; + } + setErrorsList([]); + + handleImagePicker(mode, createItem); + + function createItem(images: any[]) { + for (let i = 0; i < images.length; i++) { + ImageResizer.createResizedImage(images[i], 500, 500, "JPEG", 40, 0) + .then(async (response) => { + const imgBase64 = await FileSystem.readAsStringAsync(response.uri, { + encoding: "base64", + }); + console.log("imageSize", response.size); + dispatch( + addItem({ + name: prefix + ` ${i + 1}`, + collection: collection, + category: selectedCategory, + type: type, + fit: "", + season: "", + size: "", + sizeUnit: "", + quantity: 1, + purchaseDate: JSON.stringify(new Date()), + image: imgBase64, + automaticColor: false, + primaryColor: "", + secondaryColor: "", + tertiaryColor: "", + }), + ); + }) + .catch((err) => { + console.log("image comparison error: ", err); + }); + } + } + + navigation.popToTop("Category"); + + navigation.dispatch(CommonActions.goBack()); + } + return ( + + <> + + + + {localization.BulkMode[storedSettings.language]} + + + + setPrefix(text.nativeEvent.text)} + /> + {selectedCategory <= 3 && ( + + + + )} + + + + + handleNumberChange( + () => { + setImageNum(Number(text.nativeEvent.text)); + }, + text.nativeEvent.text, + 0, + 30, + ) + } + keyboardType="numeric" + /> + + + + {localization.bulkInfo[storedSettings.language]} + + + {errorsList.length > 0 && ( + + {errorsList.map((error, index) => { + return ( + + {error} + + ); + })} + + )} + + + + + + ); +}; diff --git a/screens/stackNav/Category.tsx b/screens/stackNav/Category.tsx index df1f429..c6b9187 100644 --- a/screens/stackNav/Category.tsx +++ b/screens/stackNav/Category.tsx @@ -100,7 +100,8 @@ export function Category() { > - navigation.navigate(item.screen, { + navigation.navigate("ModeScreen", { + screenName: item.screen, selectedCategory: item.index, }) } diff --git a/screens/stackNav/Home.tsx b/screens/stackNav/Home.tsx index b04c5b0..e483ae4 100644 --- a/screens/stackNav/Home.tsx +++ b/screens/stackNav/Home.tsx @@ -85,10 +85,7 @@ export function Home() { (state: RootState) => state.settings.language, ); useEffect(() => { - // PushNotification.deleteChannel("channel-id-1"); - GetAllPermissions(); - createChannels(selectedLang, storedSettings.name); }, []); useEffect(() => { @@ -96,7 +93,13 @@ export function Home() { if (storedSettings.enableLaundry != true || numberOfLaundry == 0) { PushNotification.cancelLocalNotification("2"); } - }, [storedSettings.enableLaundry, numberOfLaundry]); + }, [storedSettings.enableLaundry, , numberOfLaundry]); + useEffect(() => { + createChannels(selectedLang, storedSettings.name); + if (storedSettings.enableReminder != true) { + PushNotification.cancelLocalNotification("1"); + } + }, [storedSettings.enableReminder]); return ( diff --git a/screens/stackNav/ModeScreen.tsx b/screens/stackNav/ModeScreen.tsx new file mode 100644 index 0000000..8aa47e9 --- /dev/null +++ b/screens/stackNav/ModeScreen.tsx @@ -0,0 +1,65 @@ +import { Image, TouchableOpacity } from "react-native"; +import { ThemeView } from "../../components/ThemeView"; +import { ThemeText } from "../../components/ThemeText"; +import { BackButton } from "../../components/BackButton"; +import bulkBoxes from "../../assets/images/bulk.png"; +import singleBox from "../../assets/images/single.png"; +import { localization } from "../../utils/localization"; +import { useSelector } from "react-redux"; +import { RootState } from "../../redux/store"; + +export const ModeScreen = ({ + navigation, + route, +}: { + navigation: any; + route: any; +}) => { + const { selectedCategory, screenName } = route.params; + const storedSettings = useSelector((state: RootState) => state.settings); + return ( + + <> + + + navigation.navigate(screenName, { + selectedCategory: selectedCategory, + }) + } + > + + {localization.addSingleItem[storedSettings.language]} + + + + + navigation.navigate("BulkModeForm", { + selectedCategory: selectedCategory, + }) + } + > + + {localization.addMultiItems[storedSettings.language]} + + + + + + ); +}; diff --git a/screens/stackNav/Settings.tsx b/screens/stackNav/Settings.tsx index 0b694a4..af61503 100644 --- a/screens/stackNav/Settings.tsx +++ b/screens/stackNav/Settings.tsx @@ -12,6 +12,7 @@ import { changeLanguage, laundryNumberSetter, setEnableLaundry, + setReminder, userNameSetter, } from "../../redux/settingsSlice"; import { Button, Checkbox, Snackbar } from "react-native-paper"; @@ -21,19 +22,7 @@ import Icon from "react-native-vector-icons/FontAwesome"; import { colors } from "../../utils/colors"; import { laundryRefresher } from "../../redux/itemsSlice"; import { localization } from "../../utils/localization"; - -export const handleNumberChange = ( - func: any, - text: string, - min: number, - max: number, -) => { - // Use regular expressions to allow only numbers and optionally a single decimal point - const regex = /^[0-9]*\.?[0-9]*$/; - if (regex.test(text) && Number(text) >= min && Number(text) <= max) { - func(); - } -}; +import { handleNumberChange } from "../../utils/validators"; export const Settings = () => { const [openLang, setOpenLang] = useState(false); @@ -49,8 +38,12 @@ export const Settings = () => { const onToggleSnackBar = () => setVisible(!visible); const onDismissSnackBar = () => setVisible(false); const [exportOrImport, setExportOrImport] = useState(0); - const [checked, setChecked] = useState(storedSettings.enableLaundry); - + const [checkedLau, setCheckedLau] = useState(storedSettings.enableLaundry); + const [checkedRem, setCheckedRem] = useState(storedSettings.enableReminder); + const setCheckboxes = (enableLaundry: boolean, enableReminder: boolean) => { + setCheckedLau(enableLaundry); + setCheckedRem(enableReminder); + }; return ( <> @@ -149,7 +142,7 @@ export const Settings = () => {