From a01da16eeb34edad2e4e261abd35281d0452e68d Mon Sep 17 00:00:00 2001 From: Hassan Khan Date: Mon, 14 Oct 2024 02:14:44 +0100 Subject: [PATCH] feat(apps/mobile): track when an image is being uploaded --- apps/mobile/src/app/index.tsx | 3 + .../src/features/CatCard/CatCardSkeleton.tsx | 94 +++++++++++++++++++ .../store/selectors/getIsImageUploading.ts | 4 + .../src/store/slices/ImageActivitySlice.ts | 33 +++++++ apps/mobile/src/store/store.ts | 3 + 5 files changed, 137 insertions(+) create mode 100644 apps/mobile/src/features/CatCard/CatCardSkeleton.tsx create mode 100644 apps/mobile/src/store/selectors/getIsImageUploading.ts create mode 100644 apps/mobile/src/store/slices/ImageActivitySlice.ts diff --git a/apps/mobile/src/app/index.tsx b/apps/mobile/src/app/index.tsx index c9f8d30..c8baf1c 100644 --- a/apps/mobile/src/app/index.tsx +++ b/apps/mobile/src/app/index.tsx @@ -7,6 +7,8 @@ import { UploadButton } from '../components/UploadButton'; import { ImageList } from '../features/HomePage/ImageList'; import { NoImagesFound } from '../features/HomePage/NoImagesFound'; import { UploadImageSheet } from '../features/UploadImageModal/UploadImageSheet'; +import { useAppSelector } from '../store/overrides'; +import { getIsImageUploading } from '../store/selectors/getIsImageUploading'; import { useGetMyFavouritesQuery, useGetMyImagesQuery, @@ -24,6 +26,7 @@ const Home = () => { const isLoading = isImagesLoading || isFavouritesLoading || isVotesLoading; const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false); + const isImageUploading = useAppSelector(getIsImageUploading); const handleUploadButtonPress = useCallback(() => { setIsBottomSheetOpen(true); diff --git a/apps/mobile/src/features/CatCard/CatCardSkeleton.tsx b/apps/mobile/src/features/CatCard/CatCardSkeleton.tsx new file mode 100644 index 0000000..9818940 --- /dev/null +++ b/apps/mobile/src/features/CatCard/CatCardSkeleton.tsx @@ -0,0 +1,94 @@ +import { useEffect } from 'react'; +import { View } from 'react-native'; +import { createStyleSheet, useStyles } from 'react-native-unistyles'; +import FontAwesome from '@expo/vector-icons/FontAwesome'; +import Animated, { + useAnimatedStyle, + useDerivedValue, + useSharedValue, + withRepeat, + withSequence, + withTiming, +} from 'react-native-reanimated'; + +export const CatCardSkeleton = () => { + const { styles } = useStyles(stylesheet); + + const val1 = useSharedValue(0.6); + + useEffect(() => { + 'worklet'; + val1.value = withRepeat( + withSequence( + withTiming(1, { duration: 1000 }), + withTiming(0.6, { duration: 1000 }) + ), + 0 + ); + }, []); + + const val2 = useDerivedValue(() => { + return 0.6 + 1 - val1.value; + }); + + const dotStyle = useAnimatedStyle(() => ({ + opacity: val1.value, + })); + + const dotStyle2 = useAnimatedStyle(() => ({ + opacity: val2.value, + })); + + return ( + + + + + + + + + + + + ); +}; + +const stylesheet = createStyleSheet((theme) => ({ + root: { + borderColor: theme.colors.background.$6, + borderRadius: theme.radii.$3, + borderWidth: theme.borderWidths.$1, + width: theme.space.full, + marginVertical: 15, + }, + base: { + borderBottomLeftRadius: theme.radii.$3, + borderBottomRightRadius: theme.radii.$3, + width: theme.space.full, + height: 80, + backgroundColor: '#fff', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-evenly', + }, + image: { + borderTopLeftRadius: theme.radii.$3, + borderTopRightRadius: theme.radii.$3, + width: theme.space.full, + height: 180, + backgroundColor: '#c9c8c8', + justifyContent: 'flex-start', + alignItems: 'flex-end', + }, +})); diff --git a/apps/mobile/src/store/selectors/getIsImageUploading.ts b/apps/mobile/src/store/selectors/getIsImageUploading.ts new file mode 100644 index 0000000..05837f1 --- /dev/null +++ b/apps/mobile/src/store/selectors/getIsImageUploading.ts @@ -0,0 +1,4 @@ +import { RootState } from '../store'; + +export const getIsImageUploading = (state: RootState) => + state.imageActivity.isImageUploading; diff --git a/apps/mobile/src/store/slices/ImageActivitySlice.ts b/apps/mobile/src/store/slices/ImageActivitySlice.ts new file mode 100644 index 0000000..415713d --- /dev/null +++ b/apps/mobile/src/store/slices/ImageActivitySlice.ts @@ -0,0 +1,33 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { CatApi } from '../services/CatApi'; + +const initialState = { + isImageUploading: false, +}; + +export const ImageActivitySlice = createSlice({ + name: 'imageActivity', + initialState, + reducers: {}, + extraReducers: (builder) => { + builder + .addMatcher( + CatApi.endpoints.uploadImage.matchPending, + (state, { payload }) => { + state.isImageUploading = true; + } + ) + .addMatcher( + CatApi.endpoints.uploadImage.matchFulfilled, + (state, { payload }) => { + state.isImageUploading = false; + } + ) + .addMatcher( + CatApi.endpoints.uploadImage.matchRejected, + (state, { payload }) => { + state.isImageUploading = false; + } + ); + }, +}); diff --git a/apps/mobile/src/store/store.ts b/apps/mobile/src/store/store.ts index 0a62bc4..fd313c3 100644 --- a/apps/mobile/src/store/store.ts +++ b/apps/mobile/src/store/store.ts @@ -4,6 +4,7 @@ import devToolsEnhancer from 'redux-devtools-expo-dev-plugin'; import { ToastMiddleware } from './middleware/ToastMiddleware'; import { CatApi } from './services/CatApi'; +import { ImageActivitySlice } from './slices/ImageActivitySlice'; export const store = configureStore({ enhancers: (getDefaultEnhancers) => @@ -19,6 +20,8 @@ export const store = configureStore({ reducer: { // RTK-Query reducers [CatApi.reducerPath]: CatApi.reducer, + // RTK slices + [ImageActivitySlice.name]: ImageActivitySlice.reducer, }, });