diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f0907f..3e67321 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "cSpell.words": ["MMKV", "Zustand"] } diff --git a/__mocks__/react-native-vision-camera/index.tsx b/__mocks__/react-native-vision-camera/index.tsx new file mode 100644 index 0000000..84402e3 --- /dev/null +++ b/__mocks__/react-native-vision-camera/index.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import {View} from 'react-native'; + +export function Camera() { + return ; +} + +export const useCameraDevice = jest.fn(); +export const useCameraFormat = jest.fn(); diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4122f36..0927f68 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,10 @@ + + + + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/NubbleApp/AppDelegate.h b/ios/NubbleApp/AppDelegate.h index 5d28082..a7ebb51 100644 --- a/ios/NubbleApp/AppDelegate.h +++ b/ios/NubbleApp/AppDelegate.h @@ -1,6 +1,7 @@ #import +#import #import -@interface AppDelegate : RCTAppDelegate +@interface AppDelegate : EXAppDelegateWrapper @end diff --git a/ios/NubbleApp/Info.plist b/ios/NubbleApp/Info.plist index 580dc94..a89550d 100644 --- a/ios/NubbleApp/Info.plist +++ b/ios/NubbleApp/Info.plist @@ -2,22 +2,6 @@ - UIAppFonts - - Panchang-Light.otf - - Satoshi-Black.otf - Satoshi-BlackItalic.otf - Satoshi-Bold.otf - Satoshi-BoldItalic.otf - Satoshi-Italic.otf - Satoshi-Light.otf - Satoshi-LightItalic.otf - Satoshi-Medium.otf - Satoshi-MediumItalic.otf - Satoshi-Regular.otf - - CFBundleDevelopmentRegion en CFBundleDisplayName @@ -53,6 +37,24 @@ NSLocationWhenInUseUsageDescription + NSPhotoLibraryUsageDescription + Para publicar novos posts + NSCameraUsageDescription + $(PRODUCT_NAME) precisa de acesso a camera para publicar novos posts. + UIAppFonts + + Panchang-Light.otf + Satoshi-Black.otf + Satoshi-BlackItalic.otf + Satoshi-Bold.otf + Satoshi-BoldItalic.otf + Satoshi-Italic.otf + Satoshi-Light.otf + Satoshi-LightItalic.otf + Satoshi-Medium.otf + Satoshi-MediumItalic.otf + Satoshi-Regular.otf + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/ios/Podfile b/ios/Podfile index d192a46..f64add7 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,9 +1,17 @@ +require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") require_relative '../node_modules/react-native/scripts/react_native_pods' require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' +require_relative '../node_modules/react-native-permissions/scripts/setup' -platform :ios, min_ios_version_supported +platform :ios, '13.0' prepare_react_native_project! + +setup_permissions([ + 'Camera', + 'PhotoLibrary', +]) + # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded # @@ -22,6 +30,14 @@ if linkage != nil end target 'NubbleApp' do + use_expo_modules! + post_integrate do |installer| + begin + expo_patch_react_imports!(installer) + rescue => e + Pod::UI.warn e + end + end config = use_native_modules! # Flags change depending on the env values. diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9ca027e..513c360 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2,6 +2,28 @@ PODS: - boost (1.76.0) - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) + - EXApplication (5.1.1): + - ExpoModulesCore + - EXConstants (14.2.1): + - ExpoModulesCore + - EXFileSystem (15.2.2): + - ExpoModulesCore + - EXFont (11.1.1): + - ExpoModulesCore + - EXImageLoader (4.1.1): + - ExpoModulesCore + - React-Core + - Expo (48.0.21): + - ExpoModulesCore + - ExpoImageManipulator (11.1.1): + - EXImageLoader + - ExpoModulesCore + - ExpoKeepAwake (12.0.1): + - ExpoModulesCore + - ExpoModulesCore (1.2.7): + - React-Core + - React-RCTAppDelegate + - ReactCommon/turbomodule/core - FBLazyVector (0.71.8) - FBReactNativeSpec (0.71.8): - RCT-Folly (= 2021.07.22.00) @@ -332,6 +354,8 @@ PODS: - React-jsinspector (0.71.8) - React-logger (0.71.8): - glog + - react-native-cameraroll (7.2.2): + - React-Core - react-native-mmkv (2.10.2): - MMKV (>= 1.2.13) - React-Core @@ -427,12 +451,18 @@ PODS: - React-perflogger (= 0.71.8) - RNCAsyncStorage (1.19.3): - React-Core + - RNPermissions (4.0.4): + - React-Core - RNScreens (3.20.0): - React-Core - React-RCTImage - RNSVG (13.9.0): - React-Core - SocketRocket (0.6.0) + - VisionCamera (3.8.2): + - React + - React-callinvoker + - React-Core - Yoga (1.14.0) - YogaKit (1.18.1): - Yoga (~> 1.14) @@ -440,6 +470,15 @@ PODS: DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EXApplication (from `../node_modules/expo-application/ios`) + - EXConstants (from `../node_modules/expo-constants/ios`) + - EXFileSystem (from `../node_modules/expo-file-system/ios`) + - EXFont (from `../node_modules/expo-font/ios`) + - EXImageLoader (from `../node_modules/expo-image-loader/ios`) + - Expo (from `../node_modules/expo`) + - ExpoImageManipulator (from `../node_modules/expo-image-manipulator/ios`) + - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) + - ExpoModulesCore (from `../node_modules/expo-modules-core`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - Flipper (= 0.125.0) @@ -483,6 +522,7 @@ DEPENDENCIES: - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)" - react-native-mmkv (from `../node_modules/react-native-mmkv`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) @@ -499,8 +539,10 @@ DEPENDENCIES: - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" + - RNPermissions (from `../node_modules/react-native-permissions`) - RNScreens (from `../node_modules/react-native-screens`) - RNSVG (from `../node_modules/react-native-svg`) + - VisionCamera (from `../node_modules/react-native-vision-camera`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -528,6 +570,24 @@ EXTERNAL SOURCES: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + EXApplication: + :path: "../node_modules/expo-application/ios" + EXConstants: + :path: "../node_modules/expo-constants/ios" + EXFileSystem: + :path: "../node_modules/expo-file-system/ios" + EXFont: + :path: "../node_modules/expo-font/ios" + EXImageLoader: + :path: "../node_modules/expo-image-loader/ios" + Expo: + :path: "../node_modules/expo" + ExpoImageManipulator: + :path: "../node_modules/expo-image-manipulator/ios" + ExpoKeepAwake: + :path: "../node_modules/expo-keep-awake/ios" + ExpoModulesCore: + :path: "../node_modules/expo-modules-core" FBLazyVector: :path: "../node_modules/react-native/Libraries/FBLazyVector" FBReactNativeSpec: @@ -564,6 +624,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsinspector" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" + react-native-cameraroll: + :path: "../node_modules/@react-native-camera-roll/camera-roll" react-native-mmkv: :path: "../node_modules/react-native-mmkv" react-native-safe-area-context: @@ -596,10 +658,14 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" RNCAsyncStorage: :path: "../node_modules/@react-native-async-storage/async-storage" + RNPermissions: + :path: "../node_modules/react-native-permissions" RNScreens: :path: "../node_modules/react-native-screens" RNSVG: :path: "../node_modules/react-native-svg" + VisionCamera: + :path: "../node_modules/react-native-vision-camera" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -607,6 +673,15 @@ SPEC CHECKSUMS: boost: 57d2868c099736d80fcd648bf211b4431e51a558 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 + EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903 + EXConstants: f348da07e21b23d2b085e270d7b74f282df1a7d9 + EXFileSystem: 844e86ca9b5375486ecc4ef06d3838d5597d895d + EXFont: 6ea3800df746be7233208d80fe379b8ed74f4272 + EXImageLoader: fd053169a8ee932dd83bf1fe5487a50c26d27c2b + Expo: 0d9f112757acc6bf32103eabccf91267780bd580 + ExpoImageManipulator: 85ab87e9641b9d83108f8d603e0418c12fc19e2d + ExpoKeepAwake: 69f5f627670d62318410392d03e0b5db0f85759a + ExpoModulesCore: 653958063a301098b541ae4dfed1ac0b98db607b FBLazyVector: f637f31eacba90d4fdeff3fa41608b8f361c173b FBReactNativeSpec: 0d9a4f4de7ab614c49e98c00aedfd3bfbda33d59 Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0 @@ -639,6 +714,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 747911ab5921641b4ed7e4900065896597142125 React-jsinspector: c712f9e3bb9ba4122d6b82b4f906448b8a281580 React-logger: 342f358b8decfbf8f272367f4eacf4b6154061be + react-native-cameraroll: 8acc1cf2c31d38992222fd08c7041691fc98dce3 react-native-mmkv: 9ae7ca3977e8ef48dbf7f066974eb844c20b5fd7 react-native-safe-area-context: b8979f5eda6ed5903d4dbc885be3846ea3daa753 React-perflogger: d21f182895de9d1b077f8a3cd00011095c8c9100 @@ -655,12 +731,14 @@ SPEC CHECKSUMS: React-runtimeexecutor: 7c51ae9d4b3e9608a2366e39ccaa606aa551b9ed ReactCommon: 85c98ab0a509e70bf5ee5d9715cf68dbf495b84c RNCAsyncStorage: c913ede1fa163a71cea118ed4670bbaaa4b511bb + RNPermissions: 579937aaaea1b03e03a1a89c9e9bc0e2230bca22 RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 + VisionCamera: 4b98b273902ac18491bb68481b6601f1f0da0f2d Yoga: 065f0b74dba4832d6e328238de46eb72c5de9556 YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: b03e55c075f548a48e20f6ae8f672faf1cb69411 +PODFILE CHECKSUM: daf7f4390aa5da9b7867496cc8247d5d0f2fc456 -COCOAPODS: 1.11.3 +COCOAPODS: 1.13.0 diff --git a/package.json b/package.json index 61e4c6c..315725d 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dependencies": { "@hookform/resolvers": "^3.1.0", "@react-native-async-storage/async-storage": "^1.19.3", + "@react-native-camera-roll/camera-roll": "^7.2.2", "@react-navigation/bottom-tabs": "^6.5.7", "@react-navigation/native": "^6.1.6", "@react-navigation/native-stack": "^6.9.12", @@ -24,14 +25,18 @@ "@tanstack/react-query": "^4.32.6", "axios": "^1.4.0", "date-fns": "^2.30.0", + "expo": "^48.0.0", + "expo-image-manipulator": "~11.1.1", "lodash": "^4.17.21", "react": "18.2.0", "react-hook-form": "^7.43.9", "react-native": "0.71.8", "react-native-mmkv": "^2.10.2", + "react-native-permissions": "^4.0.4", "react-native-safe-area-context": "^4.5.3", "react-native-screens": "^3.20.0", "react-native-svg": "^13.9.0", + "react-native-vision-camera": "^3.8.2", "zod": "^3.21.4", "zustand": "^4.4.1" }, diff --git a/src/api/apiConfig.ts b/src/api/apiConfig.ts index eaf2285..912ded2 100644 --- a/src/api/apiConfig.ts +++ b/src/api/apiConfig.ts @@ -1,6 +1,11 @@ import {AuthCredentials, authService} from '@domain'; import axios from 'axios'; +/** + * use your computer network IP Address when running on a real device. + * + * i.e: `'http://192.168.20.15:3333/'` + */ export const BASE_URL = 'http://127.0.0.1:3333/'; export const api = axios.create({ baseURL: BASE_URL, diff --git a/src/assets/icons/CameraClick.tsx b/src/assets/icons/CameraClick.tsx new file mode 100644 index 0000000..39a1122 --- /dev/null +++ b/src/assets/icons/CameraClick.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import {Svg, Circle} from 'react-native-svg'; + +import {IconBase} from '../../components/Icon/Icon'; + +export function CameraClick({size = 80, color = 'white'}: IconBase) { + return ( + + + + + ); +} diff --git a/src/assets/icons/cameraClick.svg b/src/assets/icons/cameraClick.svg new file mode 100644 index 0000000..b5bdb3b --- /dev/null +++ b/src/assets/icons/cameraClick.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/image_placeholder.png b/src/assets/images/image_placeholder.png new file mode 100644 index 0000000..cd5b62d Binary files /dev/null and b/src/assets/images/image_placeholder.png differ diff --git a/src/assets/index.ts b/src/assets/index.ts new file mode 100644 index 0000000..8abd2bc --- /dev/null +++ b/src/assets/index.ts @@ -0,0 +1,3 @@ +export const images = { + imagePlaceholder: require('./images/image_placeholder.png'), +}; diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 1710cfd..eb8cbe0 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -9,7 +9,7 @@ import { import {buttonPresets} from './buttonPresets'; -export type ButtonPreset = 'primary' | 'outline'; +export type ButtonPreset = 'primary' | 'outline' | 'ghost'; export interface ButtonProps extends TouchableOpacityBoxProps { title: string; @@ -38,9 +38,13 @@ export function Button({ {...buttonPreset.container} {...touchableOpacityBoxProps}> {loading ? ( - + ) : ( - + {title} )} diff --git a/src/components/Button/buttonPresets.ts b/src/components/Button/buttonPresets.ts index f7ba74d..9427f34 100644 --- a/src/components/Button/buttonPresets.ts +++ b/src/components/Button/buttonPresets.ts @@ -1,11 +1,12 @@ import {ThemeColors} from '../../theme/theme'; import {TouchableOpacityBoxProps} from '../Box/Box'; +import {TextProps} from '../Text/Text'; import {ButtonPreset} from './Button'; interface ButtonUI { container: TouchableOpacityBoxProps; - content: ThemeColors; + content: {color: ThemeColors; textProps?: TextProps}; } export const buttonPresets: Record< @@ -20,13 +21,13 @@ export const buttonPresets: Record< container: { backgroundColor: 'primary', }, - content: 'primaryContrast', + content: {color: 'primaryContrast'}, }, disabled: { container: { backgroundColor: 'gray4', }, - content: 'gray2', + content: {color: 'gray2'}, }, }, outline: { @@ -35,14 +36,36 @@ export const buttonPresets: Record< borderWidth: 1, borderColor: 'primary', }, - content: 'primary', + content: {color: 'primary'}, }, disabled: { container: { borderWidth: 1, borderColor: 'gray4', }, - content: 'gray2', + content: {color: 'gray2'}, + }, + }, + ghost: { + default: { + container: { + backgroundColor: 'white70', + height: 40, + }, + content: { + color: 'grayBlack', + textProps: { + preset: 'paragraphSmall', + bold: false, + }, + }, + }, + disabled: { + container: { + backgroundColor: 'grayWhite', + height: 40, + }, + content: {color: 'grayBlack'}, }, }, }; diff --git a/src/components/Icon/Icon.tsx b/src/components/Icon/Icon.tsx index d51ab7b..7e240f0 100644 --- a/src/components/Icon/Icon.tsx +++ b/src/components/Icon/Icon.tsx @@ -10,6 +10,7 @@ import {BellIcon} from '../../assets/icons/BellIcon'; import {BellOnIcon} from '../../assets/icons/BellOnIcon'; import {BookmarkFillIcon} from '../../assets/icons/BookmarkFillIcon'; import {BookmarkIcon} from '../../assets/icons/BookmarkIcon'; +import {CameraClick} from '../../assets/icons/CameraClick'; import {CameraIcon} from '../../assets/icons/CameraIcon'; import {ChatIcon} from '../../assets/icons/ChatIcon'; import {ChatOnIcon} from '../../assets/icons/ChatOnIcon'; @@ -74,6 +75,7 @@ const iconRegistry = { bookmark: BookmarkIcon, bookmarkFill: BookmarkFillIcon, camera: CameraIcon, + cameraClick: CameraClick, chat: ChatIcon, chatOn: ChatOnIcon, check: CheckIcon, diff --git a/src/components/PermissionManager/PermissionManager.tsx b/src/components/PermissionManager/PermissionManager.tsx new file mode 100644 index 0000000..5cdc7c4 --- /dev/null +++ b/src/components/PermissionManager/PermissionManager.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import {Linking, Platform} from 'react-native'; + +import {PermissionName, usePermission} from '@services'; + +import { + Screen, + Text, + TextProps, + Button, + ActivityIndicator, + Box, +} from '@components'; + +interface PermissionManagerProps { + permissionName: PermissionName; + description: string; + children: React.ReactElement; +} + +export function PermissionManager({ + permissionName, + description, + children, +}: PermissionManagerProps) { + const {status, isLoading} = usePermission(permissionName); + + if (status === 'granted') { + return children; + } + + return ( + + + + {description} + + {isLoading && } + {status === 'unavailable' && ( + + Esse recurso não está disponível para esse dispositivo + + )} + {status === 'never_ask_again' && ( + + {Platform.OS === 'android' && ( + + É necessário abrir e fechar o App novamente após alterar as + configurações + + )} +