Преглед изворни кода

Merge branch 'range-calendar' of SashaGoncharov19/nomadmania-app into master

Viktoriia пре 1 година
родитељ
комит
5a6973053c

+ 10 - 0
assets/icons/left-arrow.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_4051_13199)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2081 2.32325C9.8343 2.02424 9.28891 2.08484 8.9899 2.4586L4.9899 7.4586C4.73668 7.77512 4.73668 8.22488 4.9899 8.5414L8.9899 13.5414C9.28891 13.9152 9.8343 13.9758 10.2081 13.6768C10.5818 13.3777 10.6424 12.8324 10.3434 12.4586L6.77653 8L10.3434 3.5414C10.6424 3.16764 10.5818 2.62225 10.2081 2.32325Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_4051_13199">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 10 - 0
assets/icons/right-arrow.svg

@@ -0,0 +1,10 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_4051_13205)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5.79194 2.32325C6.1657 2.02424 6.71109 2.08484 7.0101 2.4586L11.0101 7.4586C11.2633 7.77512 11.2633 8.22488 11.0101 8.5414L7.0101 13.5414C6.71109 13.9152 6.1657 13.9758 5.79194 13.6768C5.41818 13.3777 5.35758 12.8324 5.65659 12.4586L9.22347 8L5.65659 3.5414C5.35758 3.16764 5.41818 2.62225 5.79194 2.32325Z" fill="#0F3F4F"/>
+</g>
+<defs>
+<clipPath id="clip0_4051_13205">
+<rect width="16" height="16" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 3 - 0
assets/icons/trash.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M15.0833 3.44452H13.6944H11.6111V1.36118C11.6111 0.977848 11.3007 0.666748 10.9166 0.666748H5.3611C4.97709 0.666748 4.66667 0.977848 4.66667 1.36118V3.44452H2.58333H1.19443C0.5 3.44452 0.5 4.03341 0.5 4.41675C0.5 4.80008 0.5 5.25008 1.1944 5.25008H1.88884L1.88887 15.2501C1.88887 16.3987 2.8236 17.3334 3.9722 17.3334H12.3055C13.4541 17.3334 14.3889 16.3987 14.3889 15.2501L14.3888 5.25008H15.0833C15.7777 5.25008 15.7777 4.80011 15.7777 4.41678C15.7777 4.03345 15.7777 3.44452 15.0833 3.44452ZM6.6865 2.61301H9.44616V3.44452H6.6865V2.61301ZM12.511 14.6337C12.511 15.0163 12.1999 15.3281 11.8166 15.3281H4.52777C4.14443 15.3281 3.83333 15.0163 3.83333 14.6337L3.8333 5.25008H5.36107H10.9166H12.511V14.6337Z" fill="white"/>
+</svg>

Разлика између датотеке није приказан због своје велике величине
+ 227 - 209
package-lock.json


+ 8 - 2
package.json

@@ -6,11 +6,12 @@
     "start": "expo start",
     "android": "expo start --android",
     "ios": "expo start --ios",
-    "web": "expo start --web"
+    "web": "expo start --web",
+    "postinstall": "patch-package"
   },
   "dependencies": {
     "@react-native-async-storage/async-storage": "1.18.2",
-    "@react-native-community/datetimepicker": "^7.6.2",
+    "@react-native-community/datetimepicker": "7.2.0",
     "@react-navigation/bottom-tabs": "^6.5.11",
     "@react-navigation/native": "^6.1.9",
     "@react-navigation/native-stack": "^6.9.17",
@@ -23,8 +24,12 @@
     "expo-image-picker": "~14.3.2",
     "expo-splash-screen": "~0.20.5",
     "expo-status-bar": "~1.6.0",
+    "moment": "^2.29.4",
+    "patch-package": "^8.0.0",
+    "postinstall-postinstall": "^2.1.0",
     "react": "18.2.0",
     "react-native": "0.72.6",
+    "react-native-calendar-picker": "^7.1.4",
     "react-native-safe-area-context": "4.6.3",
     "react-native-screens": "~3.22.0",
     "react-native-svg": "13.9.0"
@@ -32,6 +37,7 @@
   "devDependencies": {
     "@babel/core": "^7.20.0",
     "@types/react": "~18.2.14",
+    "@types/react-native-calendar-picker": "^7.0.6",
     "prettier": "^3.1.0",
     "react-native-svg-transformer": "^1.1.0",
     "typescript": "^5.1.3"

+ 57 - 0
patches/react-native-calendar-picker+7.1.4.patch

@@ -0,0 +1,57 @@
+diff --git a/node_modules/react-native-calendar-picker/CalendarPicker/Day.js b/node_modules/react-native-calendar-picker/CalendarPicker/Day.js
+index f79b618..e163c31 100644
+--- a/node_modules/react-native-calendar-picker/CalendarPicker/Day.js
++++ b/node_modules/react-native-calendar-picker/CalendarPicker/Day.js
+@@ -197,15 +197,43 @@ export default function Day(props) {
+       );
+     } else {
+       return (
+-        <View style={[styles.dayWrapper, custom.containerStyle]}>
+-          <TouchableOpacity
+-            disabled={!enableDateChange}
+-            style={[custom.style, computedSelectedDayStyle, selectedDayStyle ]}
+-            onPress={() => onPressDay({year, month, day}) }>
+-            <Text style={[styles.dayLabel, textStyle, custom.textStyle, selectedDayTextStyle]}>
+-              { day }
+-            </Text>
+-          </TouchableOpacity>
++        <View style={[styles.dayWrapper, custom.containerStyle, {position: 'relative'}]}>
++          {!(selectedStartDate && !selectedEndDate && isThisDaySameAsSelectedStart) && (
++            <TouchableOpacity
++              disabled={!enableDateChange}
++              style={[custom.style, computedSelectedDayStyle, selectedDayStyle]}
++              onPress={() => onPressDay({year, month, day}) }>
++              <Text style={[
++                styles.dayLabel,
++                textStyle,
++                custom.textStyle,
++                selectedDayTextStyle,
++                { color: '#0F3F4F' },
++                (thisDay.isSame(today, 'day') && (isThisDaySameAsSelectedStart || isThisDaySameAsSelectedEnd)) && { borderWidth: 0 }
++              ]}>
++                { day }
++              </Text>
++            </TouchableOpacity>
++          )}
++
++          {(thisDay.isSame(selectedStartDate, 'day') || thisDay.isSame(selectedEndDate, 'day')) && (
++            <TouchableOpacity
++              disabled={!enableDateChange}
++              style={[{position: 'absolute'}]}
++              onPress={() => onPressDay({year, month, day}) }>
++              <Text style={[styles.dayLabel, textStyle, custom.textStyle, selectedDayTextStyle, {
++                backgroundColor: '#0F3F4F',
++                borderRadius: 17,
++                height: 34,
++                width: 34,
++                textAlign: 'center',
++                verticalAlign: 'middle',
++                overflow: 'hidden',
++              }]}>
++                { day }
++              </Text>
++            </TouchableOpacity>
++          )}
+         </View>
+       );
+     }

+ 94 - 0
src/components/AddPhoto/index.tsx

@@ -0,0 +1,94 @@
+import React, { useState } from 'react';
+import {
+  View,
+  Image,
+  TouchableOpacity,
+  Alert,
+  Text
+} from 'react-native';
+import * as ImagePicker from 'expo-image-picker';
+import { Modal } from '../Modal';
+import { Input } from '../Input';
+import { styles } from './style';
+
+import AddSVG from '../../../assets/icons/add.svg';
+import TrashSVG from '../../../assets/icons/trash.svg';
+
+interface AddPhotoProps {
+  isModalVisible: boolean;
+  initialImagePath?: string | null;
+  initialImageDescription?: string;
+  closeModal: () => void;
+}
+
+const AddPhoto: React.FC<AddPhotoProps> = ({ isModalVisible, initialImagePath, initialImageDescription, closeModal }) => {
+  const [image, setImage] = useState(initialImagePath);
+  const [description, setDescription] = useState(initialImageDescription ?? '');
+
+  const handleDelete = () => {
+    Alert.alert('Delete Image', 'Are you sure you want to delete this image?', [
+      { text: 'Cancel' },
+      { text: 'Delete', onPress: () => {
+        setImage(null);
+      }},
+    ]);
+  };
+
+  const handleImagePick = async () => {
+    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
+    if (status !== 'granted') {
+      Alert.alert('Permission Denied', 'Sorry, we need media library permissions to make this work!');
+      return;
+    }
+
+    let result = await ImagePicker.launchImageLibraryAsync({
+      mediaTypes: ImagePicker.MediaTypeOptions.Images,
+      quality: 1,
+    });
+
+    if (!result.canceled) {
+      setImage(result.assets[0].uri);
+    }
+  };
+
+  return (
+    <Modal
+      visibleInPercent={'90%'}
+      visible={isModalVisible}
+      onRequestClose={closeModal}
+      headerTitle='Add Photo'
+    >
+      <View style={[styles.container, styles.centerPosition]}>
+        <View style={[styles.imageContainer, styles.centerPosition]}>
+          {image ? (
+            <>
+              <TouchableOpacity onPress={handleDelete} style={styles.deleteButton}>
+                <View style={[styles.centerPosition, styles.deleteBtn]}>
+                  <TrashSVG />
+                </View>
+              </TouchableOpacity>
+              <Image source={{ uri: image }} style={styles.image} />
+            </>
+          ) : (
+            <TouchableOpacity onPress={handleImagePick} style={styles.centerPosition}>
+              <View style={[styles.centerPosition, styles.addBtn]}>
+                <AddSVG width={48} height={48} />
+              </View>
+              
+              <Text style={styles.addButtonText}>Upload Photo</Text>
+            </TouchableOpacity>
+          )}
+        </View>
+        <View style={styles.description}>
+          <Input
+            header='Description'
+            placeholder="Add photo description here"
+            onChange={setDescription}
+          />
+        </View>
+      </View>
+    </Modal>
+  );
+};
+
+export default AddPhoto;

+ 55 - 0
src/components/AddPhoto/style.tsx

@@ -0,0 +1,55 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from '../../theme';
+
+export const styles = StyleSheet.create({
+  container: {
+    padding: 24,
+  },
+  imageContainer: {
+    position: 'relative',
+    marginBottom: 8,
+    width: '100%',
+    height: 327,
+    borderRadius: 8,
+    backgroundColor: '#FAFAFA'
+  },
+  deleteButton: {
+    position: 'absolute',
+    top: 16,
+    right: 16,
+    zIndex: 1,
+  },
+  image: {
+    width: '100%',
+    height: '100%',
+    resizeMode: 'cover',
+    borderRadius: 8,
+  },
+  description: {
+    width: '100%',
+    marginTop: 8,
+  },
+  centerPosition: {
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  addButtonText: {
+    fontSize: 16,
+    marginTop: 10,
+    fontWeight: '700',
+    color: Colors.DARK_BLUE,
+    lineHeight: 24,
+  },
+  deleteBtn: {
+    width: 40,
+    height: 40,
+    backgroundColor: Colors.RED,
+    borderRadius: 20
+  },
+  addBtn: {
+    width: 48,
+    height: 48,
+    backgroundColor: Colors.DARK_BLUE,
+    borderRadius: 24
+  }
+});

+ 16 - 0
src/components/Calendars/RangeCalendar/Navigation/index.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+import { View } from 'react-native';
+
+import { styles } from './style';
+import LeftArrow from '../../../../../assets/icons/left-arrow.svg';
+import RightArrow from '../../../../../assets/icons/right-arrow.svg';
+
+const Navigation = React.memo(({ direction }: { direction: 'prev' | 'next' }) => {
+  return (
+    <View style={[styles.navigationBtn, direction === 'prev' ? styles.prevComponent : styles.nextComponent]}>
+      {direction === 'prev' ? <LeftArrow /> : <RightArrow />}
+    </View>
+  );
+});
+
+export default Navigation;

+ 16 - 0
src/components/Calendars/RangeCalendar/Navigation/style.tsx

@@ -0,0 +1,16 @@
+import { StyleSheet } from 'react-native';
+
+export const styles = StyleSheet.create({
+  navigationBtn: {
+    width: 50,
+    height: 30,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  prevComponent: {
+    left: 25,
+  },
+  nextComponent: {
+    right: 25,
+  },
+});

+ 103 - 0
src/components/Calendars/RangeCalendar/index.tsx

@@ -0,0 +1,103 @@
+import React, { useMemo, useState } from 'react';
+import { View } from 'react-native';
+import CalendarPicker, { CustomDatesStylesFunc, CustomDayHeaderStylesFunc } from 'react-native-calendar-picker';
+import moment from 'moment';
+import { Modal } from '../../Modal';
+import Navigation from './Navigation';
+
+import { styles } from './style';
+import { Colors } from '../../../theme';
+
+export default function RangeCalendar(
+  { isModalVisible, closeModal }: { isModalVisible: boolean, closeModal: () => void }
+) {
+  const [selectedStartDate, setSelectedStartDate] = useState<Date | null>(null);
+  const [selectedEndDate, setSelectedEndDate] = useState<Date | null>(null);
+
+  const handleOnDateChange = (date: Date, type: string) => {
+    if (type === 'END_DATE') {
+      setSelectedEndDate(date);
+    } else {
+      setSelectedStartDate(date);
+      setSelectedEndDate(null);
+    }
+  };
+
+  const customDayHeaderStyles: CustomDayHeaderStylesFunc = () => {
+    return {
+      textStyle: styles.dayHeader,
+    };
+  };
+
+  const customSelectedDatesStyles: CustomDatesStylesFunc = useMemo(() => {
+    return (date: moment.Moment) => {
+      if (date.isSame(moment(), 'day')) {
+        return {
+          containerStyle: {},
+          textStyle: {
+            borderWidth: 1,
+            borderColor: Colors.DARK_BLUE,
+            borderRadius: 17,
+            height: 34,
+            width: 34,
+            textAlign: 'center',
+            verticalAlign: 'middle',
+          },
+          style: {
+            backgroundColor: Colors.WHITE,
+          }
+        };
+      }
+      return {
+        containerStyle: {},
+        textStyle: {},
+      };
+    };
+  }, []);
+
+  const resetSelections = () => {
+    closeModal();
+    setSelectedStartDate(null);
+    setSelectedEndDate(null);
+  };
+
+  const prevNavigationComponent = useMemo(() => <Navigation direction="prev" />, []);
+  const nextNavigationComponent = useMemo(() => <Navigation direction="next" />, []);
+
+  return (
+    <Modal
+      visibleInPercent={'70%'}
+      visible={isModalVisible}
+      onRequestClose={resetSelections}
+      headerTitle='Select Date'
+    >
+      <View style={styles.modalContent}>
+        <CalendarPicker
+          scaleFactor={400}
+          allowRangeSelection
+          allowBackwardRangeSelect
+          onDateChange={handleOnDateChange as any}
+          selectedStartDate={selectedStartDate as Date}
+          selectedEndDate={selectedEndDate as Date}
+          scrollable
+          showDayStragglers
+          previousComponent={prevNavigationComponent}
+          nextComponent={nextNavigationComponent}
+          dayLabelsWrapper={styles.labelsWrapper}
+          selectedRangeStyle={styles.rangeStyle}
+          selectedRangeEndTextStyle={styles.rangeStartEndTextStyle}
+          selectedRangeStartTextStyle={styles.rangeStartEndTextStyle}
+          selectedRangeEndStyle={[styles.rangeStartEndStyle, styles.rangeEndStyle]}
+          selectedRangeStartStyle={[styles.rangeStartEndStyle, styles.rangeStartStyle]}
+          customDayHeaderStyles={customDayHeaderStyles}
+          customDatesStyles={customSelectedDatesStyles}
+          disabledDatesTextStyle={styles.disabledDates}
+          textStyle={styles.textStyle}
+          monthTitleStyle={styles.dateTitle}
+          yearTitleStyle={styles.dateTitle}
+          headerWrapperStyle={styles.headerWrapper}
+        />
+      </View>
+    </Modal>
+  );
+}

+ 66 - 0
src/components/Calendars/RangeCalendar/style.ts

@@ -0,0 +1,66 @@
+import { StyleSheet } from 'react-native';
+import { Colors } from '../../../theme';
+
+export const styles = StyleSheet.create({
+  modalContent: {
+    backgroundColor: Colors.WHITE,
+    padding: 22,
+    justifyContent: 'center',
+    alignItems: 'center',
+  },
+  rangeStartEndTextStyle: {
+    color: Colors.WHITE,
+  },
+  rangeStartEndStyle: {
+    width: 40,
+  },
+  rangeEndStyle: {
+    alignSelf: 'flex-start',
+    justifyContent: 'flex-start',
+    borderTopRightRadius: 20,
+    borderBottomRightRadius: 20,
+  },
+  rangeStartStyle: {
+    alignSelf: 'flex-end',
+    justifyContent: 'flex-end',
+    borderTopLeftRadius: 20,
+    borderBottomLeftRadius: 20,
+  },
+  rangeStyle: {
+    backgroundColor: Colors.DARK_LIGHT,
+    height: 34,
+  },
+  labelsWrapper: {
+    borderTopWidth: 0,
+    borderBottomWidth: 0,
+    marginBottom: 5,
+  },
+  disabledDates: {
+    width: "100%",
+    height: "100%",
+    backgroundColor: Colors.WHITE,
+    textAlign: "center",
+    textAlignVertical: "center",
+  },
+  dateTitle: {
+    color: Colors.DARK_BLUE,
+    fontSize: 16,
+    lineHeight: 24,
+    fontWeight: '700'
+  },
+  headerWrapper: {
+    marginBottom: 15
+  },
+  textStyle: {
+    color: Colors.DARK_BLUE,
+    fontSize: 14,
+    lineHeight: 20
+  },
+  dayHeader: {
+    color: Colors.ORANGE,
+    fontSize: 12,
+    lineHeight: 16,
+    letterSpacing: 1,
+    fontWeight: '700'
+  }
+});

+ 3 - 1
src/theme.ts

@@ -1,5 +1,7 @@
 export enum Colors {
   ORANGE = '#ED9334',
   DARK_BLUE = '#0F3F4F',
-  WHITE = '#FFF'
+  WHITE = '#FFF',
+  DARK_LIGHT = '#E5E5E5',
+  RED = '#EF5B5B',
 }

Неке датотеке нису приказане због велике количине промена