2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char *argv[])
6 | {
7 | @autoreleasepool {
8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/fastlane/metadata/Fastfile:
--------------------------------------------------------------------------------
1 | desc "Submit a new Beta Build to fdroid"
2 | lane :beta do
3 |
4 | # tests must pass
5 | sh "npm test"
6 |
7 | gradle(task: "assembleRelease")
8 |
9 | # Upload to beta
10 | supply(track: 'beta')
11 |
12 | end
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | //Reanimated plugin has to be listed last.
2 | module.exports = {
3 | presets: ['module:metro-react-native-babel-preset'],
4 | plugins: ["nativewind/babel",'react-native-reanimated/plugin'],
5 | env: {
6 | production: {
7 | plugins: ['react-native-paper/babel'],
8 | },
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Metro configuration for React Native
3 | * https://github.com/facebook/react-native
4 | *
5 | * @format
6 | */
7 |
8 | module.exports = {
9 | transformer: {
10 | getTransformOptions: async () => ({
11 | transform: {
12 | experimentalImportSupport: false,
13 | inlineRequires: true,
14 | },
15 | }),
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/utils/validators.ts:
--------------------------------------------------------------------------------
1 | export const handleNumberChange = (
2 | func: any,
3 | text: string,
4 | min: number,
5 | max: number,
6 | ) => {
7 | // Use regular expressions to allow only numbers and optionally a single decimal point
8 | const regex = /^[0-9]*\.?[0-9]*$/;
9 | if (regex.test(text) && Number(text) >= min && Number(text) <= max) {
10 | func();
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/layout/launch_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/ios/link-assets-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "migIndex": 1,
3 | "data": [
4 | {
5 | "path": "assets/fonts/Montserrat-Bold.otf",
6 | "sha1": "a88aa713b7cdada0016869ed3548aa9c4e4f18b5"
7 | },
8 | {
9 | "path": "assets/fonts/Montserrat-Regular.otf",
10 | "sha1": "a3f5403e26df9e0dfcabd5c17bdf29662fbe96c2"
11 | },
12 | {
13 | "path": "assets/fonts/TSMorabaat-Regular.otf",
14 | "sha1": "ec4a2fa82e0b19b9409cb67737c9b9f99d0be754"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/android/link-assets-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "migIndex": 1,
3 | "data": [
4 | {
5 | "path": "assets/fonts/Montserrat-Bold.otf",
6 | "sha1": "a88aa713b7cdada0016869ed3548aa9c4e4f18b5"
7 | },
8 | {
9 | "path": "assets/fonts/Montserrat-Regular.otf",
10 | "sha1": "a3f5403e26df9e0dfcabd5c17bdf29662fbe96c2"
11 | },
12 | {
13 | "path": "assets/fonts/TSMorabaat-Regular.otf",
14 | "sha1": "ec4a2fa82e0b19b9409cb67737c9b9f99d0be754"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/ios/.xcode.env:
--------------------------------------------------------------------------------
1 | # This `.xcode.env` file is versioned and is used to source the environment
2 | # used when running script phases inside Xcode.
3 | # To customize your local environment, you can create an `.xcode.env.local`
4 | # file that is not versioned.
5 |
6 | # NODE_BINARY variable contains the PATH to the node executable.
7 | #
8 | # Customize the NODE_BINARY variable here.
9 | # For example, to use nvm with brew, add the following line
10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use
11 | export NODE_BINARY=$(command -v node)
12 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | // tailwind.config.js
3 |
4 | module.exports = {
5 | content: ["./App.{js,jsx,ts,tsx}","./screens/**/*.{js,jsx,ts,tsx}","./components/**/*.{js,jsx,ts,tsx}","./routers/**/*.{js,jsx,ts,tsx}","./screens/stackNav/**/*.{js,jsx,ts,tsx}"],
6 | theme: {
7 | extend: {},
8 | colors:{"mainCyan":"#77AEBB","mainGreen":"#AEBB77","mainPink":"#BB77AE","goldenrod":"#DAA520","white":"#ffffff","gray":"#C9C9C9","black":"#000000","red":"#FF0000","slate-300":"#303639","slate-500":"#505d70"}
9 | },
10 | plugins: [],
11 | }
--------------------------------------------------------------------------------
/hooks/useWidthScreen.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { Dimensions } from "react-native";
3 |
4 | export default function useWidthScreen() {
5 | const [screenWidth, setScreenWidth] = useState(
6 | Dimensions.get("window").width,
7 | );
8 | useEffect(() => {
9 | const dimensionListener = Dimensions.addEventListener(
10 | "change",
11 | (dimensions) => {
12 | setScreenWidth(dimensions.window.width);
13 | },
14 | );
15 | return () => dimensionListener.remove();
16 | }, []);
17 | return screenWidth;
18 | }
19 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import {AppRegistry,Platform} from 'react-native';
6 | import App from './App';
7 | import {name as appName} from './app.json';
8 | import PushNotification from "react-native-push-notification";
9 |
10 | PushNotification.configure({
11 | // (required) Called when a remote is received or opened, or local notification is opened
12 | onNotification: function (notification) {
13 | // console.log("NOTIFICATION:", notification);
14 | },
15 | requestPermissions: Platform.OS === 'ios'
16 | })
17 | AppRegistry.registerComponent(appName, () => App);
18 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'Closet-Archive'
2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
3 | include ':app'
4 | includeBuild('../node_modules/react-native-gradle-plugin')
5 | include ':react-native-splash-screen'
6 | project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
7 | apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
8 | useExpoModules()
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/components/ThemeView.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentProps, ReactElement } from "react";
2 | import { useColorScheme, View } from "react-native";
3 | import { colors } from "../utils/colors";
4 |
5 | export const ThemeView = ({
6 | classNameStyle,
7 | children,
8 | }: {
9 | children: ReactElement;
10 | classNameStyle?: ComponentProps<"div">["className"];
11 | }) => {
12 | //used for background usually
13 | const isDarkMode = useColorScheme() === "dark";
14 | return (
15 |
19 | {children}
20 |
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/android/app/src/release/java/com/myclosetx/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Meta Platforms, Inc. and affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.myclosetx;
8 |
9 | import android.content.Context;
10 | import com.facebook.react.ReactInstanceManager;
11 |
12 | /**
13 | * Class responsible of loading Flipper inside your React Native application. This is the release
14 | * flavor of it so it's empty as we don't want to load Flipper.
15 | */
16 | public class ReactNativeFlipper {
17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
18 | // Do nothing as we don't want to initialize Flipper on Release.
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | googlePlayServicesVersion = "+" // default: "+"
6 | firebaseMessagingVersion = "21.1.0" // default: "21.1.0"
7 | buildToolsVersion = "33.0.0"
8 | minSdkVersion = 21
9 | compileSdkVersion = 33
10 | targetSdkVersion = 33
11 |
12 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
13 | ndkVersion = "23.1.7779620"
14 | }
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | dependencies {
20 | classpath("com.android.tools.build:gradle:7.4.1")
21 | classpath("com.facebook.react:react-native-gradle-plugin")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/components/ThemeText.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentProps } from "react";
2 | import { useColorScheme, Text } from "react-native";
3 | import { colors } from "../utils/colors";
4 |
5 | export const ThemeText = ({
6 | classNameStyle,
7 | customStyle,
8 | darkColor = colors.white,
9 | lightColor = colors.gray,
10 | children,
11 | }: {
12 | children: string;
13 | classNameStyle?: ComponentProps<"p">["className"];
14 | customStyle?: {};
15 | darkColor?: string;
16 | lightColor?: string;
17 | }) => {
18 | //used for background usually
19 | const isDarkMode = useColorScheme() === "dark";
20 | return (
21 |
25 | {children}
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/ios/closetArchiveTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/components/BackButton.tsx:
--------------------------------------------------------------------------------
1 | import { useNavigation } from "@react-navigation/native";
2 | import { Keyboard, TouchableOpacity, View, useColorScheme } from "react-native";
3 | import Icon from "react-native-vector-icons/Ionicons";
4 | import { colors } from "../utils/colors";
5 |
6 | export const BackButton = () => {
7 | const navigation = useNavigation();
8 | const isDarkMode = useColorScheme() === "dark";
9 | return (
10 |
11 | {
14 | Keyboard.dismiss();
15 | navigation.goBack();
16 | }}
17 | >
18 |
23 |
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/utils/exportStoreToJson.ts:
--------------------------------------------------------------------------------
1 | import { RootState, store } from "../redux/store"; // Replace with the correct path to your rootReducer file
2 | import * as ScopedStorage from "react-native-scoped-storage";
3 |
4 | export const exportStoreToJson = async () => {
5 | if (!store) {
6 | console.log("Store not initialized");
7 | return;
8 | }
9 |
10 | try {
11 | const state: RootState = store.getState();
12 | const serializedState = JSON.stringify(state);
13 |
14 | // Create a new file named store.json in the Documents directory
15 | const storePath = await ScopedStorage.createDocument(
16 | "store.json",
17 | "application/json",
18 | serializedState,
19 | "utf8",
20 | );
21 | if (storePath) {
22 | console.log("Store exported successfully.");
23 | return true;
24 | } else {
25 | return false;
26 | }
27 | } catch (error) {
28 | console.error("Error exporting store:", error);
29 | return false;
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/ios/closetArchive/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "scale" : "1x",
46 | "size" : "1024x1024"
47 | }
48 | ],
49 | "info" : {
50 | "author" : "xcode",
51 | "version" : 1
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/License:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Bahaa Tuffaha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/redux/store.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers, configureStore } from "@reduxjs/toolkit";
2 | import categoriesSlice from "./categoriesSlice";
3 | import itemsSlice from "./itemsSlice";
4 | import FilesystemStorage from "redux-persist-filesystem-storage";
5 | import {
6 | persistStore,
7 | persistReducer,
8 | FLUSH,
9 | REHYDRATE,
10 | PAUSE,
11 | PERSIST,
12 | PURGE,
13 | REGISTER,
14 | } from "redux-persist";
15 | import settingsSlice from "./settingsSlice";
16 |
17 | const persistConfig = {
18 | key: "root",
19 | storage: FilesystemStorage,
20 | };
21 | const rootReducer = combineReducers({
22 | CategoryList: categoriesSlice,
23 | itemsList: itemsSlice,
24 | settings: settingsSlice,
25 | });
26 | export type RootState = ReturnType;
27 | const persistedReducer = persistReducer(persistConfig, rootReducer);
28 |
29 | export const store = configureStore({
30 | reducer: persistedReducer,
31 | middleware: (getDefaultMiddleware) =>
32 | getDefaultMiddleware({
33 | serializableCheck: {
34 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
35 | },
36 | }),
37 | });
38 |
39 | export const persistor = persistStore(store);
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | ios/.xcode.env.local
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 | *.hprof
33 | .cxx/
34 | *.keystore
35 | !debug.keystore
36 |
37 | # node.js
38 | #
39 | node_modules/
40 | npm-debug.log
41 | yarn-error.log
42 |
43 | # fastlane
44 | #
45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
46 | # screenshots whenever they are needed.
47 | # For more information about the recommended setup visit:
48 | # https://docs.fastlane.tools/best-practices/source-control/
49 |
50 | **/fastlane/report.xml
51 | **/fastlane/Preview.html
52 | **/fastlane/screenshots
53 | **/fastlane/test_output
54 |
55 | # Bundle artifact
56 | *.jsbundle
57 |
58 | # Ruby / CocoaPods
59 | /ios/Pods/
60 | /vendor/bundle/
61 |
62 | # Temporary files created by Metro to check the health of the file watcher
63 | .metro-health-check*
64 |
--------------------------------------------------------------------------------
/ios/closetArchive/AppDelegate.mm:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 |
5 | @implementation AppDelegate
6 |
7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
8 | {
9 | self.moduleName = @"Closet-Archive";
10 | // You can add your custom initial props in the dictionary below.
11 | // They will be passed down to the ViewController used by React Native.
12 | self.initialProps = @{};
13 |
14 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
15 | }
16 |
17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
18 | {
19 | #if DEBUG
20 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
21 | #else
22 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
23 | #endif
24 | }
25 |
26 | /// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
27 | ///
28 | /// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
29 | /// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
30 | /// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`.
31 | - (BOOL)concurrentRootEnabled
32 | {
33 | return true;
34 | }
35 |
36 | @end
37 |
--------------------------------------------------------------------------------
/components/SpriteAnimator.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react";
2 | import { View, Image } from "react-native";
3 |
4 | const SpriteAnimator = ({ sheet, columns, size, fps = 16, play = true }) => {
5 | const imgRef = useRef(null);
6 | const [imgWidth, setImgWidth] = useState(0);
7 | const [spriteIndex, setSpriteIndex] = useState(0);
8 |
9 | useEffect(() => {
10 | if (imgRef.current && imgWidth == 0) {
11 | setImgWidth((_) => size * columns);
12 | }
13 | }, [imgRef, imgWidth]);
14 |
15 | useEffect(() => {
16 | if (play) {
17 | if (spriteIndex < columns - 1) {
18 | setTimeout(() => {
19 | setSpriteIndex((prev) => prev + 1);
20 | }, 1000 / fps);
21 | } else {
22 | setTimeout(() => {
23 | setSpriteIndex(0);
24 | }, 1000 / fps);
25 | }
26 | } else if (!play && spriteIndex > 0) {
27 | setSpriteIndex(0);
28 | }
29 | }, [spriteIndex]);
30 |
31 | return (
32 |
35 |
45 |
46 | );
47 | };
48 |
49 | export default SpriteAnimator;
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | I18nManager,
3 | SafeAreaView,
4 | StatusBar,
5 | useColorScheme,
6 | } from "react-native";
7 | import Navigator from "./routers/stack";
8 | import { PaperProvider } from "react-native-paper";
9 | import SplashScreen from "react-native-splash-screen";
10 | import { useEffect } from "react";
11 | import { GestureHandlerRootView } from "react-native-gesture-handler";
12 | import { Provider as ReduxProvider } from "react-redux";
13 | import { store, persistor } from "./redux/store";
14 | import { PersistGate } from "redux-persist/integration/react";
15 |
16 | import { colors } from "./utils/colors";
17 |
18 | function App(): JSX.Element {
19 | const isDarkMode = useColorScheme() === "dark";
20 |
21 | const backgroundStyle = {
22 | backgroundColor: isDarkMode ? colors.darkblue : colors.white,
23 | };
24 |
25 | useEffect(() => {
26 | I18nManager.allowRTL(false);
27 | I18nManager.forceRTL(false);
28 | SplashScreen.hide();
29 | }, []);
30 | return (
31 |
32 |
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 |
49 | export default App;
50 |
--------------------------------------------------------------------------------
/components/CustomInput.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, TextInput, View, useColorScheme } from "react-native";
2 | import { TextInput as TextInput2 } from "react-native-paper";
3 | import { Props } from "react-native-paper/lib/typescript/src/components/TextInput/TextInput";
4 | import { colors } from "../utils/colors";
5 |
6 | export interface CustomInputProps extends Props {
7 | left?: React.ReactElement;
8 | right?: React.ReactElement;
9 | textArea?: boolean;
10 | }
11 | export const CustomInput = ({
12 | left,
13 | right,
14 | textArea = false,
15 | ...props
16 | }: CustomInputProps) => (
17 | (
28 |
29 | {left && {left}}
30 |
34 | {right && {right}}
35 |
36 | )}
37 | />
38 | );
39 |
40 | const styles = StyleSheet.create({
41 | customLeft: {
42 | position: "absolute",
43 | left: 0,
44 | marginVertical: 15,
45 | paddingHorizontal: 10,
46 | },
47 | customRight: {
48 | position: "absolute",
49 | right: 0,
50 | marginVertical: 15,
51 | paddingHorizontal: 10,
52 | },
53 | });
54 |
--------------------------------------------------------------------------------
/components/ImageViewer.tsx:
--------------------------------------------------------------------------------
1 | import { Dispatch, SetStateAction, useState } from "react";
2 | import { TouchableOpacity } from "react-native-gesture-handler";
3 | import addImage from "../assets/images/addImage.png";
4 | import { Image } from "react-native";
5 | import CustomModal from "./CustomModal";
6 | import useWidthScreen from "../hooks/useWidthScreen";
7 |
8 | export const ImageViewer = ({
9 | imageUrl,
10 | setImageModalVisible,
11 | }: {
12 | imageUrl: string;
13 | setImageModalVisible: Dispatch>;
14 | }) => {
15 | const [showImageFull, setShowImageFull] = useState(false);
16 | const screenWidth = useWidthScreen();
17 | return (
18 | <>
19 |
25 |
33 |
34 | imageUrl != "" && setShowImageFull(true)}
36 | onPress={() => {
37 | setImageModalVisible(true);
38 | }}
39 | >
40 |
48 |
49 | >
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "lib": ["esnext"],
6 | "allowJs": true,
7 | "jsx": "react-native",
8 | "noEmit": true,
9 | "isolatedModules": false,
10 | "strict": true,
11 | "moduleResolution": "node",
12 | "allowSyntheticDefaultImports": true,
13 | "esModuleInterop": true,
14 | // ? Custom ones
15 | "skipLibCheck": true,
16 | "resolveJsonModule": true,
17 | "noImplicitAny": true,
18 | "strictNullChecks": true,
19 | "strictFunctionTypes": true,
20 | "strictPropertyInitialization": true,
21 | "noImplicitThis": true,
22 | "alwaysStrict": true,
23 | "noUnusedLocals": true,
24 | "noUnusedParameters": true,
25 | "noImplicitReturns": true,
26 | "noFallthroughCasesInSwitch": true,
27 | "forceConsistentCasingInFileNames": true,
28 | // ? Babel Plugin Module Resolver
29 | "baseUrl": "./src",
30 | "paths": {
31 | "@shared-components/*": ["./shared/components/*"],
32 | "@shared-constants": ["./shared/constants"],
33 | "@font-size": ["./shared/theme/font-size"],
34 | "@api": ["./services/api/index"],
35 | "@fonts": ["./shared/theme/fonts"],
36 | "@colors": ["./shared/theme/colors"],
37 | "@theme/*": ["./shared/theme/*"],
38 | "@services/*": ["./services/*"],
39 | "@screens/*": ["./screens/*"],
40 | "@utils": ["./utils/"],
41 | "@assets": ["./assets/"],
42 | "@local-storage": ["./services/local-storage"]
43 | }
44 | },
45 | "extends": "@tsconfig/react-native/tsconfig.json",
46 | "exclude": [
47 | "node_modules",
48 | "babel.config.js",
49 | "metro.config.js",
50 | "jest.config.js"
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/hooks/useHeatmap.ts:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import { RootState } from "../redux/store";
3 | import { useMemo } from "react";
4 |
5 | export default function useHeatmap(itemLogs: string[]) {
6 | const storedEvents = useSelector((state: RootState) => state.itemsList.logs);
7 | const currentDate = new Date();
8 |
9 | const oneMonthAgo = new Date();
10 | oneMonthAgo.setMonth(currentDate.getMonth() - 1);
11 |
12 | const oneWeekAgo = new Date();
13 | oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
14 |
15 | const heatmapColor = useMemo(() => {
16 | const filteredEventsLastMonth = storedEvents.filter((x) => {
17 | const eventDate = new Date(JSON.parse(x.eventDate));
18 | return (
19 | eventDate >= oneMonthAgo &&
20 | eventDate <= currentDate &&
21 | eventDate < oneWeekAgo &&
22 | itemLogs?.includes(x.eventId)
23 | );
24 | });
25 | const filteredEventsLastWeek = storedEvents.filter((x) => {
26 | const eventDate = new Date(JSON.parse(x.eventDate));
27 | return (
28 | eventDate >= oneWeekAgo &&
29 | eventDate <= currentDate &&
30 | itemLogs?.includes(x.eventId)
31 | );
32 | });
33 |
34 | // const filteredEventsMoreThanAMonthAgo = storedEvents.filter((x) => {
35 | // const eventDate = new Date(JSON.parse(x.eventDate));
36 | // return eventDate < oneMonthAgo;
37 | // });
38 |
39 | if (filteredEventsLastWeek.length > 0) {
40 | return "rgba(119, 174, 187, 0.5)";
41 | } else if (filteredEventsLastMonth.length > 0) {
42 | return "rgba(247, 198, 0, 0.5)";
43 | } else {
44 | return "rgba(211, 0, 71, 0.5)";
45 | }
46 | }, [itemLogs, storedEvents]);
47 | return heatmapColor;
48 | }
49 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/myclosetx/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.myclosetx;
2 | import expo.modules.ReactActivityDelegateWrapper;
3 |
4 | import com.facebook.react.ReactActivity;
5 | import com.facebook.react.ReactActivityDelegate;
6 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
7 | import com.facebook.react.defaults.DefaultReactActivityDelegate;
8 | import android.os.Bundle;
9 | import org.devio.rn.splashscreen.SplashScreen;
10 |
11 | public class MainActivity extends ReactActivity {
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | SplashScreen.show(this); // here
15 | super.onCreate(savedInstanceState);
16 | }
17 | /**
18 | * Returns the name of the main component registered from JavaScript. This is used to schedule
19 | * rendering of the component.
20 | */
21 | @Override
22 | protected String getMainComponentName() {
23 | return "Closet-Archive";
24 | }
25 |
26 | /**
27 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
28 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
29 | * (aka React 18) with two boolean flags.
30 | */
31 | @Override
32 | protected ReactActivityDelegate createReactActivityDelegate() {
33 | return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new DefaultReactActivityDelegate(
34 | this,
35 | getMainComponentName(),
36 | // If you opted-in for the New Architecture, we enable the Fabric Renderer.
37 | DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
38 | // If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
39 | DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
40 | ));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/components/CustomModal.tsx:
--------------------------------------------------------------------------------
1 | import { Dispatch, ReactElement, SetStateAction } from "react";
2 | import { useColorScheme } from "react-native";
3 | import { colors } from "../utils/colors";
4 | import { Button, Dialog, Portal } from "react-native-paper";
5 | import { localization } from "../utils/localization";
6 | import { useSelector } from "react-redux";
7 | import { RootState } from "../redux/store";
8 | const CustomModal = ({
9 | setVisible,
10 | visible,
11 | label,
12 | minHeight = 200,
13 | maxHeight,
14 | showClose = true,
15 | children,
16 | }: {
17 | setVisible: Dispatch>;
18 | visible: boolean;
19 | label?: string;
20 | minHeight?: number;
21 | maxHeight?: number;
22 | showClose?: boolean;
23 | children: ReactElement;
24 | }) => {
25 | const isDarkMode = useColorScheme() === "dark";
26 | const storedSettings = useSelector((state: RootState) => state.settings);
27 | return (
28 |
29 |
55 |
56 | );
57 | };
58 | export default CustomModal;
59 |
--------------------------------------------------------------------------------
/ios/closetArchive/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | Closet-Archive
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSExceptionDomains
30 |
31 | localhost
32 |
33 | NSExceptionAllowsInsecureHTTPLoads
34 |
35 |
36 |
37 |
38 | NSLocationWhenInUseUsageDescription
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UIViewControllerBasedStatusBarAppearance
53 |
54 | UIAppFonts
55 |
56 | TSMorabaat-Regular.otf
57 | Montserrat-Bold.otf
58 | Montserrat-Regular.otf
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 | # Automatically convert third-party libraries to use AndroidX
25 | android.enableJetifier=true
26 |
27 | # Version of flipper SDK to use with React Native
28 | FLIPPER_VERSION=0.125.0
29 |
30 | # Use this property to specify which architecture you want to build.
31 | # You can also override it from the CLI using
32 | # ./gradlew -PreactNativeArchitectures=x86_64
33 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
34 |
35 | # Use this property to enable support to the new architecture.
36 | # This will allow you to use TurboModules and the Fabric render in
37 | # your application. You should enable this flag either if you want
38 | # to write custom TurboModules/Fabric components OR use libraries that
39 | # are providing them.
40 | newArchEnabled=false
41 |
42 | # Use this property to enable or disable the Hermes JS engine.
43 | # If set to false, you will be using JSC instead.
44 | hermesEnabled=true
45 |
--------------------------------------------------------------------------------