Skip to content

Commit

Permalink
Implement the image uploading and displaying logic (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shysh-Oleksandr authored Nov 25, 2023
1 parent 08f0bb2 commit 62e6b4a
Show file tree
Hide file tree
Showing 21 changed files with 1,797 additions and 107 deletions.
11 changes: 5 additions & 6 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"slug": "bulletjournal",
"owner": "oleksandrshysh",
"originalFullName": "@frankmelward/bulletjournal",
"version": "1.0.0",
"version": "1.1.0",
"orientation": "portrait",
"icon": "./assets/images/mainIcon.png",
"scheme": "com.frankmelward.bulletjournal",
Expand All @@ -14,9 +14,7 @@
"resizeMode": "cover",
"backgroundColor": "#0891b2"
},
"assetBundlePatterns": [
"**/*"
],
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.frankmelward.bulletjournal"
Expand All @@ -27,14 +25,15 @@
"backgroundImage": "./assets/images/iconBg.png",
"backgroundColor": "#354352"
},
"versionCode": 5,
"versionCode": 7,
"googleServicesFile": "./google-services.json",
"package": "com.frankmelward.bulletjournal"
},
"plugins": [
"expo-router",
"@react-native-firebase/app",
"@react-native-firebase/crashlytics"
"@react-native-firebase/crashlytics",
"expo-image-picker"
],
"experiments": {
"typedRoutes": true
Expand Down
6 changes: 5 additions & 1 deletion eas.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
"env": {
"EXPO_PUBLIC_API_URL": "https://bullet-journal-3q4s.onrender.com",
"EXPO_PUBLIC_FIREBASE_API_KEY": "AIzaSyCN0HVzj1sjMzfFMG1DyYobvv5yjz7XsyM",
"EXPO_PUBLIC_ANDROID_CLIENT_ID": "1000487121408-02g289hkmvf089bs0gfeel9nvluaovmr.apps.googleusercontent.com"
"EXPO_PUBLIC_ANDROID_CLIENT_ID": "1000487121408-02g289hkmvf089bs0gfeel9nvluaovmr.apps.googleusercontent.com",
"EXPO_PUBLIC_BUCKET": "bullet-journal",
"EXPO_PUBLIC_AWS_ACCESS_KEY_ID": "AKIARKXI5PJQSVROUS3V",
"EXPO_PUBLIC_AWS_SECRET_ACCESS_KEY": "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",
"EXPO_PUBLIC_AWS_IMAGE_URL": "https://bullet-journal.s3.eu-north-1.amazonaws.com/"
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import "react-native-get-random-values";
import "react-native-url-polyfill/auto";

import { registerRootComponent } from "expo";

import App from "./src/App";
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"ios": "expo run:ios",
"build": "eas build --platform android",
"prepare": "husky install",
"clean:android": "expo prebuild --clean -p android",
"lint": "yarn lint:js && yarn lint:types-cli",
"lint:js": "eslint --ext .ts --ext .tsx .",
"lint:types-cli": "yarn tsc -p tsconfig.json",
Expand All @@ -27,6 +28,7 @@
"node": ">=16"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.454.0",
"@expo/config-plugins": "^7.2.5",
"@expo/vector-icons": "^13.0.0",
"@react-native-async-storage/async-storage": "1.18.2",
Expand All @@ -48,6 +50,7 @@
"expo-crypto": "~12.4.1",
"expo-dev-client": "~2.4.11",
"expo-font": "~11.4.0",
"expo-image-picker": "~14.3.2",
"expo-linking": "~5.0.2",
"expo-navigation-bar": "~2.3.0",
"expo-router": "^2.0.0",
Expand All @@ -64,7 +67,9 @@
"react-dom": "18.2.0",
"react-native": "0.72.5",
"react-native-dotenv": "^3.4.9",
"react-native-fast-image": "^8.6.3",
"react-native-gesture-handler": "~2.12.0",
"react-native-get-random-values": "^1.10.0",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-modal-datetime-picker": "^17.1.0",
"react-native-pell-rich-editor": "^1.9.0",
Expand All @@ -74,6 +79,7 @@
"react-native-shadow-2": "^7.0.8",
"react-native-svg": "13.9.0",
"react-native-svg-transformer": "^1.1.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-web": "~0.19.6",
"react-native-webview": "13.2.2",
"react-native-wheel-color-picker": "^1.2.0",
Expand All @@ -82,7 +88,8 @@
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"styled-components": "^5.3.6",
"tinycolor2": "^1.6.0"
"tinycolor2": "^1.6.0",
"uuid": "^9.0.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand All @@ -94,6 +101,7 @@
"@types/redux-logger": "^3.0.10",
"@types/styled-components-react-native": "5.1.3",
"@types/tinycolor2": "^1.4.4",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.7.3",
"@typescript-eslint/parser": "^6.7.3",
"babel-plugin-module-resolver": "^5.0.0",
Expand Down
7 changes: 5 additions & 2 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Props = {
minHeight?: number;
numberOfLines?: number;
paddingHorizontal?: number;
paddingVertical?: number;
onIconPress?: () => void;
onChange: (text: string) => void;
onSubmitEditing?: (
Expand Down Expand Up @@ -62,6 +63,7 @@ const Input = ({
maxLength = 50,
minHeight = 40,
paddingHorizontal = 20,
paddingVertical = 8,
onIconPress,
onChange,
onSubmitEditing,
Expand Down Expand Up @@ -104,6 +106,7 @@ const Input = ({
fontWeight={fontWeight}
fontSize={fontSize}
paddingHorizontal={paddingHorizontal}
paddingVertical={paddingVertical}
maxWidth={maxWidth}
keyboardType={keyboardType}
maxLength={maxLength}
Expand Down Expand Up @@ -154,16 +157,16 @@ const CustomInput = styled.TextInput<{
fontWeight?: keyof typeof theme.fonts;
fontSize?: keyof typeof theme.fontSizes;
paddingHorizontal: number;
paddingVertical: number;
maxWidth?: number;
labelColor: string;
}>`
width: 100%;
color: ${theme.colors.darkBlueText};
color: ${({ labelColor }) => labelColor};
font-size: ${({ fontSize }) => getFontSize(fontSize)}px;
font-family: ${({ fontWeight }) => getFont(fontWeight)};
text-align: ${({ isCentered }) => (isCentered ? "center" : "left")};
padding-vertical: 8px;
padding-vertical: ${({ paddingVertical }) => paddingVertical}px;
padding-horizontal: ${({ maxWidth, paddingHorizontal }) =>
maxWidth && maxWidth < 60 ? 0 : paddingHorizontal}px;
`;
Expand Down
6 changes: 5 additions & 1 deletion src/config/AppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ const firebaseConfig = {
};

export default {
apiUrl: process.env.EXPO_PUBLIC_API_URL,
apiUrl: process.env.EXPO_PUBLIC_API_URL, // Local ip from `ipconfig getifaddr en0`: http://192.168.0.103:8001
androidClientId: process.env.EXPO_PUBLIC_ANDROID_CLIENT_ID,
s3Bucket: process.env.EXPO_PUBLIC_BUCKET,
s3BucketImageUrl: process.env.EXPO_PUBLIC_AWS_IMAGE_URL,
awsAccessKeyId: process.env.EXPO_PUBLIC_AWS_ACCESS_KEY_ID,
awsSecretAccessKey: process.env.EXPO_PUBLIC_AWS_SECRET_ACCESS_KEY,
firebaseConfig,
} as const;
4 changes: 4 additions & 0 deletions src/modules/app/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export enum CustomUserEvents {
CREATE_LABEL = "create_label",
UPDATE_LABEL = "update_label",
DELETE_LABEL = "delete_label",
PICK_IMAGE_FROM_GALLERY = "pick_image_from_gallery",
MAKE_A_PHOTO = "make_a_photo",
}

export type UserEventsStackParamList = {
Expand All @@ -22,4 +24,6 @@ export type UserEventsStackParamList = {
[CustomUserEvents.CREATE_LABEL]: undefined;
[CustomUserEvents.UPDATE_LABEL]: { labelId: string };
[CustomUserEvents.DELETE_LABEL]: { labelId: string };
[CustomUserEvents.PICK_IMAGE_FROM_GALLERY]: undefined;
[CustomUserEvents.MAKE_A_PHOTO]: undefined;
};
26 changes: 25 additions & 1 deletion src/modules/notes/NotesApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import { emptyAxiosApi } from "store/api/emptyAxiosApi";
import { Method, TAG } from "store/models";

import {
CreateImagesRequest,
CreateImagesResponse,
CreateLabelRequest,
CreateLabelResponse,
CreateNoteRequest,
CreateNoteResponse,
DeleteImagesRequest,
FetchLabelsResponse,
FetchNotesResponse,
UpdateLabelRequest,
Expand Down Expand Up @@ -33,7 +37,7 @@ export const notesApi = emptyAxiosApi.injectEndpoints({
},
invalidatesTags: [TAG.NOTES],
}),
createNote: build.mutation<void, CreateNoteRequest>({
createNote: build.mutation<CreateNoteResponse, CreateNoteRequest>({
query(payload) {
return {
url: `/notes/create`,
Expand All @@ -52,6 +56,7 @@ export const notesApi = emptyAxiosApi.injectEndpoints({
},
invalidatesTags: [TAG.NOTES],
}),

fetchLabels: build.query<FetchLabelsResponse, string>({
query(userId) {
return {
Expand Down Expand Up @@ -90,6 +95,25 @@ export const notesApi = emptyAxiosApi.injectEndpoints({
},
invalidatesTags: [TAG.LABEL],
}),

createImages: build.mutation<CreateImagesResponse, CreateImagesRequest>({
query(payload) {
return {
url: `/images/create`,
method: Method.POST,
body: payload,
};
},
}),
deleteImages: build.mutation<void, DeleteImagesRequest>({
query(payload) {
return {
url: `/images`,
method: Method.DELETE,
body: payload,
};
},
}),
};
},

Expand Down
3 changes: 2 additions & 1 deletion src/modules/notes/components/noteForm/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ const Section = styled.View<{ isFormItem: boolean }>`
isFormItem &&
`
padding-top: 8px;
width: 40px;
min-width: 40px;
flex: 0.15;
align-items: center;
`}
`;
Expand Down
107 changes: 107 additions & 0 deletions src/modules/notes/components/noteForm/ImagePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import * as ExpoImagePicker from "expo-image-picker";
import React from "react";
import { Alert } from "react-native";
import theme from "theme";

import { Ionicons } from "@expo/vector-icons";
import { BUTTON_HIT_SLOP } from "components/HeaderBar";
import logging from "config/logging";
import { CustomUserEvents } from "modules/app/types";
import { getUserId } from "modules/auth/AuthSlice";
import { Image } from "modules/notes/types";
import { useAppSelector } from "store/helpers/storeHooks";
import styled from "styled-components/native";
import { addCrashlyticsLog } from "utils/addCrashlyticsLog";
import { logUserEvent } from "utils/logUserEvent";

import FormLabel from "./FormLabel";

const alertError = () => {
Alert.alert("Something went wrong", "Please try again");
};

type Props = {
noteId?: string;
setCurrentImages: React.Dispatch<React.SetStateAction<Image[]>>;
};

const ImagePicker = ({ noteId, setCurrentImages }: Props): JSX.Element => {
const userId = useAppSelector(getUserId);

const onPress = async (isCamera = false) => {
logUserEvent(
isCamera
? CustomUserEvents.MAKE_A_PHOTO
: CustomUserEvents.PICK_IMAGE_FROM_GALLERY,
);
addCrashlyticsLog(`User tries to ${isCamera ? "make" : "pick"} an image`);

const pickerResult = await (isCamera
? ExpoImagePicker.launchCameraAsync()
: ExpoImagePicker.launchImageLibraryAsync({
mediaTypes: ExpoImagePicker.MediaTypeOptions.Images,
orderedSelection: true,
selectionLimit: 20,
allowsMultipleSelection: true,
quality: 1,
}));

try {
if (pickerResult.canceled || !userId) return;

const newImagesUri = pickerResult.assets.map((image) => ({
_id: image.uri,
author: userId,
url: image.uri,
noteId: noteId || undefined,
}));

setCurrentImages((prev) => [...prev, ...newImagesUri]);
} catch (e) {
logging.error(e);
addCrashlyticsLog(e as string);
alertError();
}
};

return (
<Section>
<ButtonsContainer>
<ButtonContainer
onPress={() => onPress(false)}
mr={12}
hitSlop={BUTTON_HIT_SLOP}
>
<Ionicons name="image" size={26} color={theme.colors.cyan500} />
</ButtonContainer>
<ButtonContainer
onPress={() => onPress(true)}
hitSlop={BUTTON_HIT_SLOP}
>
<Ionicons name="camera" size={26} color={theme.colors.cyan500} />
</ButtonContainer>
</ButtonsContainer>

<FormLabel label="Images" />
</Section>
);
};

const Section = styled.View`
flex-direction: row;
flex: 1;
align-center: center;
justify-content: center;
margin: 4px 0px 0;
`;

const ButtonsContainer = styled.View`
flex-direction: row;
align-items: center;
`;

const ButtonContainer = styled.TouchableOpacity<{ mr?: number }>`
margin-right: ${({ mr }) => mr ?? 0}px;
`;

export default React.memo(ImagePicker);
Loading

0 comments on commit 62e6b4a

Please sign in to comment.