├── .env
├── .gitignore
├── .npmrc
├── amplify
├── .config
│ └── project-config.json
├── README.md
├── backend
│ ├── auth
│ │ └── devember0cbf1996
│ │ │ └── cli-inputs.json
│ ├── backend-config.json
│ ├── tags.json
│ └── types
│ │ └── amplify-dependent-resources-ref.d.ts
├── cli.json
├── hooks
│ └── README.md
└── team-provider-info.json
├── app.json
├── assets
├── adaptive-icon.png
├── data
│ └── day5
│ │ └── appartments.json
├── favicon.png
├── icon.png
├── lottie
│ ├── netflix.json
│ ├── netflix.lottie
│ ├── rain.json
│ └── sunny.json
└── splash.png
├── babel.config.js
├── eas.json
├── google-services.json
├── metro.config.js
├── package-lock.json
├── package.json
├── src
├── amplifyconfiguration.json
├── app
│ ├── (days)
│ │ ├── day1
│ │ │ └── index.tsx
│ │ ├── day10
│ │ │ ├── index.tsx
│ │ │ └── protected
│ │ │ │ ├── _layout.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── second.tsx
│ │ ├── day11
│ │ │ ├── camera.tsx
│ │ │ └── index.tsx
│ │ ├── day12
│ │ │ ├── feed.tsx
│ │ │ └── index.tsx
│ │ ├── day14
│ │ │ ├── index.tsx
│ │ │ └── notifications
│ │ │ │ ├── _layout.tsx
│ │ │ │ └── index.tsx
│ │ ├── day15
│ │ │ ├── index.tsx
│ │ │ └── todo
│ │ │ │ └── index.tsx
│ │ ├── day16
│ │ │ ├── index.tsx
│ │ │ └── todo
│ │ │ │ ├── _layout.tsx
│ │ │ │ └── index.tsx
│ │ ├── day17
│ │ │ ├── index.tsx
│ │ │ └── todo
│ │ │ │ ├── _layout.tsx
│ │ │ │ └── index.tsx
│ │ ├── day2
│ │ │ ├── index.tsx
│ │ │ └── onboarding.tsx
│ │ ├── day21
│ │ │ ├── index.tsx
│ │ │ ├── paywall.tsx
│ │ │ └── pro.tsx
│ │ ├── day3
│ │ │ ├── editor.tsx
│ │ │ └── index.tsx
│ │ ├── day4
│ │ │ ├── animation.tsx
│ │ │ ├── index.tsx
│ │ │ └── splash.tsx
│ │ ├── day5
│ │ │ ├── airbnb.tsx
│ │ │ └── index.tsx
│ │ ├── day6
│ │ │ ├── TinderScreen.tsx
│ │ │ ├── index.tsx
│ │ │ └── tinder.tsx
│ │ ├── day7
│ │ │ ├── index.tsx
│ │ │ └── memos.tsx
│ │ ├── day8
│ │ │ ├── index.tsx
│ │ │ └── weather.tsx
│ │ └── day9
│ │ │ ├── auth
│ │ │ ├── _layout.tsx
│ │ │ ├── sign-in.tsx
│ │ │ └── sign-up.tsx
│ │ │ ├── index.tsx
│ │ │ └── protected
│ │ │ ├── _layout.tsx
│ │ │ └── index.tsx
│ ├── _layout.tsx
│ └── index.tsx
├── aws-exports.js
└── components
│ ├── core
│ └── DayListItem.tsx
│ ├── day10
│ └── BiometricsProvider.tsx
│ ├── day12
│ └── VideoPost.tsx
│ ├── day15
│ ├── NewTaskInput.tsx
│ └── TaskListItem.tsx
│ ├── day16
│ ├── NewTaskInput.tsx
│ ├── TaskListItem.tsx
│ ├── TasksContextProvider.tsx
│ └── dummyTasks.ts
│ ├── day17
│ ├── NewTaskInput.tsx
│ ├── TaskListItem.tsx
│ ├── TasksStore.tsx
│ └── dummyTasks.ts
│ ├── day3
│ └── MarkdownDisplay.tsx
│ ├── day4
│ └── AnimatedSplashScreen.tsx
│ ├── day5
│ ├── ApartmentListItem.tsx
│ └── CustomMarker.tsx
│ ├── day6
│ └── TinderCard.tsx
│ ├── day7
│ └── MemoListItem.tsx
│ └── day8
│ └── ForecastItem.tsx
└── tsconfig.json
/.env:
--------------------------------------------------------------------------------
1 | EXPO_PUBLIC_OPEN_WEATHER_KEY=6beb908e4186fa331dd1e1f2e1b5c2a1
2 | EXPO_PUBLIC_VEXO_API_KEY=7ca01664-e3f5-4f36-bfb3-1b9ae05edd85
3 |
4 | EXPO_PUBLIC_REVENUECAT_IOS_KEY=appl_qHiIUJZCQwbFfQXkDAgnKjgSxkp
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | ios/
4 | android/
5 |
6 | # dependencies
7 | node_modules/
8 |
9 | # Expo
10 | .expo/
11 | dist/
12 | web-build/
13 |
14 | # Native
15 | *.orig.*
16 | *.jks
17 | *.p8
18 | *.p12
19 | *.key
20 | *.mobileprovision
21 |
22 | # Metro
23 | .metro-health-check*
24 |
25 | # debug
26 | npm-debug.*
27 | yarn-debug.*
28 | yarn-error.*
29 |
30 | # macOS
31 | .DS_Store
32 | *.pem
33 |
34 | # local env files
35 | .env*.local
36 |
37 | # typescript
38 | *.tsbuildinfo
39 |
40 |
41 |
42 | #amplify-do-not-edit-begin
43 | amplify/\#current-cloud-backend
44 | amplify/.config/local-*
45 | amplify/logs
46 | amplify/mock-data
47 | amplify/mock-api-resources
48 | amplify/backend/amplify-meta.json
49 | amplify/backend/.temp
50 | build/
51 | dist/
52 | node_modules/
53 | aws-exports.js
54 | awsconfiguration.json
55 | amplifyconfiguration.json
56 | amplifyconfiguration.dart
57 | amplify-build-config.json
58 | amplify-gradle-config.json
59 | amplifytools.xcconfig
60 | .secret-*
61 | **.sample
62 | #amplify-do-not-edit-end
63 |
64 |
65 | !amplifyconfiguration.json
66 | !aws-exports.js
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | legacy-peer-deps=true
--------------------------------------------------------------------------------
/amplify/.config/project-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "DEVember",
3 | "version": "3.1",
4 | "frontend": "javascript",
5 | "javascript": {
6 | "framework": "react-native",
7 | "config": {
8 | "SourceDir": "src",
9 | "DistributionDir": "/",
10 | "BuildCommand": "npm run-script build",
11 | "StartCommand": "npm run-script start"
12 | }
13 | },
14 | "providers": [
15 | "awscloudformation"
16 | ]
17 | }
--------------------------------------------------------------------------------
/amplify/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Amplify CLI
2 | This directory was generated by [Amplify CLI](https://docs.amplify.aws/cli).
3 |
4 | Helpful resources:
5 | - Amplify documentation: https://docs.amplify.aws
6 | - Amplify CLI documentation: https://docs.amplify.aws/cli
7 | - More details on this folder & generated files: https://docs.amplify.aws/cli/reference/files
8 | - Join Amplify's community: https://amplify.aws/community/
9 |
--------------------------------------------------------------------------------
/amplify/backend/auth/devember0cbf1996/cli-inputs.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1",
3 | "cognitoConfig": {
4 | "identityPoolName": "devember0cbf1996_identitypool_0cbf1996",
5 | "allowUnauthenticatedIdentities": false,
6 | "resourceNameTruncated": "devemb0cbf1996",
7 | "userPoolName": "devember0cbf1996_userpool_0cbf1996",
8 | "autoVerifiedAttributes": [
9 | "email"
10 | ],
11 | "mfaConfiguration": "OFF",
12 | "mfaTypes": [
13 | "SMS Text Message"
14 | ],
15 | "smsAuthenticationMessage": "Your authentication code is {####}",
16 | "smsVerificationMessage": "Your verification code is {####}",
17 | "emailVerificationSubject": "Your verification code",
18 | "emailVerificationMessage": "Your verification code is {####}",
19 | "defaultPasswordPolicy": false,
20 | "passwordPolicyMinLength": 8,
21 | "passwordPolicyCharacters": [],
22 | "requiredAttributes": [
23 | "email"
24 | ],
25 | "aliasAttributes": [],
26 | "userpoolClientGenerateSecret": false,
27 | "userpoolClientRefreshTokenValidity": 30,
28 | "userpoolClientWriteAttributes": [
29 | "email"
30 | ],
31 | "userpoolClientReadAttributes": [
32 | "email"
33 | ],
34 | "userpoolClientLambdaRole": "devemb0cbf1996_userpoolclient_lambda_role",
35 | "userpoolClientSetAttributes": false,
36 | "sharedId": "0cbf1996",
37 | "resourceName": "devember0cbf1996",
38 | "authSelections": "identityPoolAndUserPool",
39 | "useDefault": "default",
40 | "usernameAttributes": [
41 | "email"
42 | ],
43 | "userPoolGroupList": [],
44 | "serviceName": "Cognito",
45 | "usernameCaseSensitive": false,
46 | "useEnabledMfas": true
47 | }
48 | }
--------------------------------------------------------------------------------
/amplify/backend/backend-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "auth": {
3 | "devember0cbf1996": {
4 | "customAuth": false,
5 | "dependsOn": [],
6 | "frontendAuthConfig": {
7 | "mfaConfiguration": "OFF",
8 | "mfaTypes": [
9 | "SMS"
10 | ],
11 | "passwordProtectionSettings": {
12 | "passwordPolicyCharacters": [],
13 | "passwordPolicyMinLength": 8
14 | },
15 | "signupAttributes": [
16 | "EMAIL"
17 | ],
18 | "socialProviders": [],
19 | "usernameAttributes": [
20 | "EMAIL"
21 | ],
22 | "verificationMechanisms": [
23 | "EMAIL"
24 | ]
25 | },
26 | "providerPlugin": "awscloudformation",
27 | "service": "Cognito"
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/amplify/backend/tags.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Key": "user:Stack",
4 | "Value": "{project-env}"
5 | },
6 | {
7 | "Key": "user:Application",
8 | "Value": "{project-name}"
9 | }
10 | ]
--------------------------------------------------------------------------------
/amplify/backend/types/amplify-dependent-resources-ref.d.ts:
--------------------------------------------------------------------------------
1 | export type AmplifyDependentResourcesAttributes = {
2 | "auth": {
3 | "devember0cbf1996": {
4 | "AppClientID": "string",
5 | "AppClientIDWeb": "string",
6 | "IdentityPoolId": "string",
7 | "IdentityPoolName": "string",
8 | "UserPoolArn": "string",
9 | "UserPoolId": "string",
10 | "UserPoolName": "string"
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/amplify/cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "features": {
3 | "graphqltransformer": {
4 | "addmissingownerfields": true,
5 | "improvepluralization": false,
6 | "validatetypenamereservedwords": true,
7 | "useexperimentalpipelinedtransformer": true,
8 | "enableiterativegsiupdates": true,
9 | "secondarykeyasgsi": true,
10 | "skipoverridemutationinputtypes": true,
11 | "transformerversion": 2,
12 | "suppressschemamigrationprompt": true,
13 | "securityenhancementnotification": false,
14 | "showfieldauthnotification": false,
15 | "usesubusernamefordefaultidentityclaim": true,
16 | "usefieldnameforprimarykeyconnectionfield": false,
17 | "enableautoindexquerynames": true,
18 | "respectprimarykeyattributesonconnectionfield": true,
19 | "shoulddeepmergedirectiveconfigdefaults": false,
20 | "populateownerfieldforstaticgroupauth": true
21 | },
22 | "frontend-ios": {
23 | "enablexcodeintegration": true
24 | },
25 | "auth": {
26 | "enablecaseinsensitivity": true,
27 | "useinclusiveterminology": true,
28 | "breakcirculardependency": true,
29 | "forcealiasattributes": false,
30 | "useenabledmfas": true
31 | },
32 | "codegen": {
33 | "useappsyncmodelgenplugin": true,
34 | "usedocsgeneratorplugin": true,
35 | "usetypesgeneratorplugin": true,
36 | "cleangeneratedmodelsdirectory": true,
37 | "retaincasestyle": true,
38 | "addtimestampfields": true,
39 | "handlelistnullabilitytransparently": true,
40 | "emitauthprovider": true,
41 | "generateindexrules": true,
42 | "enabledartnullsafety": true,
43 | "generatemodelsforlazyloadandcustomselectionset": false
44 | },
45 | "appsync": {
46 | "generategraphqlpermissions": true
47 | },
48 | "latestregionsupport": {
49 | "pinpoint": 1,
50 | "translate": 1,
51 | "transcribe": 1,
52 | "rekognition": 1,
53 | "textract": 1,
54 | "comprehend": 1
55 | },
56 | "project": {
57 | "overrides": true
58 | }
59 | },
60 | "debug": {
61 | "shareProjectConfig": false
62 | }
63 | }
--------------------------------------------------------------------------------
/amplify/hooks/README.md:
--------------------------------------------------------------------------------
1 | # Command Hooks
2 |
3 | Command hooks can be used to run custom scripts upon Amplify CLI lifecycle events like pre-push, post-add-function, etc.
4 |
5 | To get started, add your script files based on the expected naming convention in this directory.
6 |
7 | Learn more about the script file naming convention, hook parameters, third party dependencies, and advanced configurations at https://docs.amplify.aws/cli/usage/command-hooks
8 |
--------------------------------------------------------------------------------
/amplify/team-provider-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "dev": {
3 | "awscloudformation": {
4 | "AuthRoleName": "amplify-devember-dev-121803-authRole",
5 | "UnauthRoleArn": "arn:aws:iam::704219588443:role/amplify-devember-dev-121803-unauthRole",
6 | "AuthRoleArn": "arn:aws:iam::704219588443:role/amplify-devember-dev-121803-authRole",
7 | "Region": "us-east-1",
8 | "DeploymentBucketName": "amplify-devember-dev-121803-deployment",
9 | "UnauthRoleName": "amplify-devember-dev-121803-unauthRole",
10 | "StackName": "amplify-devember-dev-121803",
11 | "StackId": "arn:aws:cloudformation:us-east-1:704219588443:stack/amplify-devember-dev-121803/023532a0-968d-11ee-9dd5-0ec275e4dbf7",
12 | "AmplifyAppId": "d1oqqadup6744a"
13 | },
14 | "categories": {
15 | "auth": {
16 | "devember0cbf1996": {}
17 | }
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "DEVember",
4 | "slug": "DEVember",
5 | "scheme": "devember",
6 | "version": "1.0.1",
7 | "orientation": "portrait",
8 | "icon": "./assets/icon.png",
9 | "userInterfaceStyle": "light",
10 | "splash": {
11 | "image": "./assets/splash.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#000000"
14 | },
15 | "assetBundlePatterns": ["**/*"],
16 | "ios": {
17 | "supportsTablet": true,
18 | "bundleIdentifier": "com.vadinsavin.DEVember",
19 | "infoPlist": {
20 | "NSFaceIDUsageDescription": "Allow $(PRODUCT_NAME) to use Face ID to unlock Secret Info.",
21 | "NSCameraUsageDescription": "$(PRODUCT_NAME) needs access to your Camera to take photos.",
22 | "NSMicrophoneUsageDescription": "$(PRODUCT_NAME) needs access to your Microphone."
23 | }
24 | },
25 | "android": {
26 | "adaptiveIcon": {
27 | "foregroundImage": "./assets/adaptive-icon.png",
28 | "backgroundColor": "#ffffff"
29 | },
30 | "permissions": [
31 | "android.permission.USE_BIOMETRIC",
32 | "android.permission.USE_FINGERPRINT",
33 | "android.permission.CAMERA",
34 | "android.permission.RECORD_AUDIO"
35 | ],
36 | "package": "com.vadinsavin.DEVember",
37 | "googleServicesFile": "./google-services.json"
38 | },
39 | "web": {
40 | "favicon": "./assets/favicon.png",
41 | "bundler": "metro"
42 | },
43 | "plugins": [
44 | "expo-router",
45 | [
46 | "expo-local-authentication",
47 | {
48 | "faceIDPermission": "Allow $(PRODUCT_NAME) to use Face ID to unlock Secret Info."
49 | }
50 | ],
51 | [
52 | "react-native-vision-camera",
53 | {
54 | "cameraPermissionText": "$(PRODUCT_NAME) needs access to your Camera to take photos.",
55 | "enableMicrophonePermission": true,
56 | "microphonePermissionText": "$(PRODUCT_NAME) needs access to your Microphone.",
57 | "enableCodeScanner": true
58 | }
59 | ]
60 | ],
61 | "experiments": {
62 | "tsconfigPaths": true
63 | },
64 | "extra": {
65 | "router": {
66 | "origin": false
67 | },
68 | "eas": {
69 | "projectId": "ee991416-c2d4-425a-affe-4ea58d9aa6c0"
70 | }
71 | },
72 | "runtimeVersion": {
73 | "policy": "appVersion"
74 | },
75 | "updates": {
76 | "url": "https://u.expo.dev/ee991416-c2d4-425a-affe-4ea58d9aa6c0"
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/notJust-dev/DEVember/6c089768bddc178e892fcc41f938fec168b252f4/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/data/day5/appartments.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": 1,
4 | "latitude": 37.7749,
5 | "longitude": -122.4194,
6 | "price": 150,
7 | "title": "Cozy Studio in Downtown SF",
8 | "numberOfStars": 5,
9 | "rating": 4.8,
10 | "image": "https://news.airbnb.com/wp-content/uploads/sites/4/2019/06/PJM020719Q202_Luxe_WanakaNZ_LivingRoom_0264-LightOn_R1.jpg?fit=2500%2C1666"
11 | },
12 | {
13 | "id": 2,
14 | "latitude": 37.7785,
15 | "longitude": -122.4313,
16 | "price": 200,
17 | "title": "Modern Apartment Near Golden Gate Park",
18 | "numberOfStars": 4,
19 | "rating": 4.5,
20 | "image": "https://media.cntraveler.com/photos/5d112d50c4d7bd806dbc00a4/16:9/w_2560%2Cc_limit/airbnb%2520luxe.jpg"
21 | },
22 | {
23 | "id": 3,
24 | "latitude": 37.7599,
25 | "longitude": -122.4148,
26 | "price": 120,
27 | "title": "Charming Victorian Flat in Mission District",
28 | "numberOfStars": 3,
29 | "rating": 4.2,
30 | "image": "https://a0.muscache.com/pictures/27c09c24-e29d-4cd9-8c28-edfa84868da4.jpg"
31 | },
32 | {
33 | "id": 4,
34 | "latitude": 37.7975,
35 | "longitude": -122.4143,
36 | "price": 180,
37 | "title": "Luxury Loft in Financial District",
38 | "numberOfStars": 5,
39 | "rating": 4.9,
40 | "image": "https://a0.muscache.com/im/pictures/b61ba60e-7b9e-48b5-9036-aacc2579a39d.jpg"
41 | },
42 | {
43 | "id": 5,
44 | "latitude": 37.7648,
45 | "longitude": -122.463,
46 | "price": 160,
47 | "title": "Spacious 2-Bedroom in Sunset District",
48 | "numberOfStars": 4,
49 | "rating": 4.6,
50 | "image": "https://pyxis.nymag.com/v1/imgs/5f1/db3/bce7fbac042b55a47b4d4260428262c17d-23-private-swimming-hole-cornwall-ct.rsquare.w600.jpg"
51 | }
52 | ]
53 |
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/notJust-dev/DEVember/6c089768bddc178e892fcc41f938fec168b252f4/assets/favicon.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/notJust-dev/DEVember/6c089768bddc178e892fcc41f938fec168b252f4/assets/icon.png
--------------------------------------------------------------------------------
/assets/lottie/netflix.lottie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/notJust-dev/DEVember/6c089768bddc178e892fcc41f938fec168b252f4/assets/lottie/netflix.lottie
--------------------------------------------------------------------------------
/assets/lottie/rain.json:
--------------------------------------------------------------------------------
1 | {"v":"5.5.6","fr":24,"ip":0,"op":255,"w":282,"h":184,"nm":"Layer 38 Outlines Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 38 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[139.345,101.243,0],"ix":2},"a":{"a":0,"k":[75.715,58.382,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[9.181,-20.365],[-9.181,20.365]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":16,"s":[0]},{"i":{"x":[0.587],"y":[1]},"o":{"x":[0.165],"y":[0]},"t":33,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.334],"y":[0]},"t":45,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":46,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":56,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":66,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":67,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":77,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":87,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":88,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":98,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":108,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":109,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":119,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":129,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":130,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":140,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":150,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":161,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":171,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":172,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":182,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":192,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":193,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":203,"s":[0]},{"t":213,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":3,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":16,"s":[100]},{"i":{"x":[0.587],"y":[1]},"o":{"x":[0.165],"y":[0]},"t":33,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.334],"y":[0]},"t":45,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":46,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":56,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":66,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":67,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":77,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":87,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":88,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":98,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":108,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":109,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":119,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":129,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":130,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":140,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":150,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":151,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":161,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":171,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":172,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":182,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":192,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":193,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":203,"s":[100]},{"t":213,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.999999820485,0.999999760646,0.999999820485,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[90.361,88.899],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-6.343,13.855],[6.343,-13.855]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":6,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":19,"s":[0]},{"i":{"x":[0.587],"y":[1]},"o":{"x":[0.165],"y":[0]},"t":41,"s":[0]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.334],"y":[0]},"t":53,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":54,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":64,"s":[0]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":75,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":85,"s":[0]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":95,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":96,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":106,"s":[0]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":116,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":117,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":127,"s":[0]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":137,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":138,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":148,"s":[0]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":158,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":159,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":169,"s":[0]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":179,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":180,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":190,"s":[0]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":200,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":201,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":211,"s":[0]},{"t":221,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":6,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":19,"s":[100]},{"i":{"x":[0.587],"y":[1]},"o":{"x":[0.165],"y":[0]},"t":41,"s":[100]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.334],"y":[0]},"t":53,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[12.445]},"t":54,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":64,"s":[100]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[12.445]},"t":75,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":85,"s":[100]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":95,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[12.445]},"t":96,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":106,"s":[100]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":116,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[12.445]},"t":117,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":127,"s":[100]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":137,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[12.445]},"t":138,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":148,"s":[100]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":158,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[12.445]},"t":159,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":169,"s":[100]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":179,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[12.445]},"t":180,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":190,"s":[100]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":200,"s":[0]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[12.445]},"t":201,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":211,"s":[100]},{"t":221,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.999999820485,0.999999760646,0.999999820485,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[76.505,82.39],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[7.512,-16.693],[-7.512,16.693]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":23,"s":[0]},{"i":{"x":[0.661],"y":[1]},"o":{"x":[0.186],"y":[0]},"t":51,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.334],"y":[0]},"t":63,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":64,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":84,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":85,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":95,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":105,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":106,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":116,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":126,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":127,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":137,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":147,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":148,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":158,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":168,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":169,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":179,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":189,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":190,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":200,"s":[0]},{"i":{"x":[0.668],"y":[0.99]},"o":{"x":[0.167],"y":[0]},"t":210,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[-12.445]},"t":211,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":221,"s":[0]},{"t":231,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":10,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":23,"s":[100]},{"i":{"x":[0.661],"y":[1]},"o":{"x":[0.186],"y":[0]},"t":51,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.334],"y":[0]},"t":63,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":64,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":74,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":84,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":85,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":95,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":105,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":106,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":116,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":126,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":127,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":137,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":147,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":148,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":158,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":168,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":169,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":179,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":189,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":190,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":200,"s":[100]},{"i":{"x":[0.668],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":210,"s":[100]},{"i":{"x":[0.828],"y":[1]},"o":{"x":[0.426],"y":[0]},"t":211,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":221,"s":[100]},{"t":231,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.999999820485,0.999999760646,0.999999820485,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[56.641,85.228],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,15.209],[15.304,0],[2.947,-1.066],[14.561,0],[3.498,-16.317],[2.578,0],[0,-13.092],[-13.091,0],[0,0],[0,0]],"o":[[0,-15.304],[-3.315,0],[-5.733,-12.402],[-17.362,0],[-2.322,-0.761],[-13.091,0],[0,13.091],[0,0],[0,0],[15.174,-0.151]],"v":[[68.215,9.681],[40.505,-18.03],[31.065,-16.379],[-1.773,-37.391],[-37.132,-8.839],[-44.512,-10.017],[-68.215,13.688],[-44.512,37.391],[40.788,37.391],[40.788,37.384]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[0]},{"t":23,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[0]},{"t":23,"s":[68]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.999999820485,0.999999760646,0.999999820485,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[75.715,44.891],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":3,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,15.209],[15.304,0],[2.947,-1.066],[14.561,0],[3.498,-16.317],[2.578,0],[0,-13.092],[-13.091,0],[0,0],[0,0]],"o":[[0,-15.304],[-3.315,0],[-5.733,-12.402],[-17.362,0],[-2.322,-0.761],[-13.091,0],[0,13.091],[0,0],[0,0],[15.174,-0.151]],"v":[[68.215,9.681],[40.505,-18.03],[31.065,-16.379],[-1.773,-37.391],[-37.132,-8.839],[-44.512,-10.017],[-68.215,13.688],[-44.512,37.391],[40.788,37.391],[40.788,37.384]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[0]},{"t":23,"s":[85]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.05],"y":[1]},"o":{"x":[0.5],"y":[0]},"t":0,"s":[0]},{"t":23,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.999999820485,0.999999760646,0.999999820485,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[75.715,44.891],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":255,"st":0,"bm":0}],"markers":[]}
--------------------------------------------------------------------------------
/assets/lottie/sunny.json:
--------------------------------------------------------------------------------
1 | {
2 | "v": "5.5.9",
3 | "fr": 30,
4 | "ip": 0,
5 | "op": 600,
6 | "w": 200,
7 | "h": 200,
8 | "nm": "天æ°-å¤äº",
9 | "ddd": 0,
10 | "assets": [],
11 | "layers": [
12 | {
13 | "ddd": 0,
14 | "ind": 1,
15 | "ty": 4,
16 | "nm": "â太é³âè½®å» 2",
17 | "sr": 1,
18 | "ks": {
19 | "o": { "a": 0, "k": 100, "ix": 11 },
20 | "r": { "a": 0, "k": 0, "ix": 10 },
21 | "p": { "a": 0, "k": [100.005, 99.986, 0], "ix": 2 },
22 | "a": { "a": 0, "k": [37.356, 37.368, 0], "ix": 1 },
23 | "s": { "a": 0, "k": [135, 135, 100], "ix": 6 }
24 | },
25 | "ao": 0,
26 | "shapes": [
27 | {
28 | "ty": "gr",
29 | "it": [
30 | {
31 | "ind": 0,
32 | "ty": "sh",
33 | "ix": 1,
34 | "ks": {
35 | "a": 0,
36 | "k": {
37 | "i": [
38 | [0, 0],
39 | [-20.489, -0.009],
40 | [-0.009, 20.49],
41 | [0, 0],
42 | [20.49, 0.003],
43 | [0.003, -20.49]
44 | ],
45 | "o": [
46 | [-0.009, 20.49],
47 | [20.49, 0.009],
48 | [0, 0],
49 | [0.003, -20.49],
50 | [-20.49, -0.004],
51 | [0, 0]
52 | ],
53 | "v": [
54 | [-37.097, -0.008],
55 | [-0.013, 37.109],
56 | [37.103, 0.025],
57 | [37.103, -0.008],
58 | [0.009, -37.113],
59 | [-37.097, -0.02]
60 | ],
61 | "c": true
62 | },
63 | "ix": 2
64 | },
65 | "nm": "è·¯å¾ 1",
66 | "mn": "ADBE Vector Shape - Group",
67 | "hd": false
68 | },
69 | {
70 | "ty": "fl",
71 | "c": {
72 | "a": 0,
73 | "k": [0.929000016755, 0.663000009574, 0.081999999402, 1],
74 | "ix": 4
75 | },
76 | "o": { "a": 0, "k": 100, "ix": 5 },
77 | "r": 1,
78 | "bm": 0,
79 | "nm": "å¡«å
1",
80 | "mn": "ADBE Vector Graphic - Fill",
81 | "hd": false
82 | },
83 | {
84 | "ty": "tr",
85 | "p": { "a": 0, "k": [37.356, 37.368], "ix": 2 },
86 | "a": { "a": 0, "k": [0, 0], "ix": 1 },
87 | "s": { "a": 0, "k": [100, 100], "ix": 3 },
88 | "r": { "a": 0, "k": 0, "ix": 6 },
89 | "o": { "a": 0, "k": 100, "ix": 7 },
90 | "sk": { "a": 0, "k": 0, "ix": 4 },
91 | "sa": { "a": 0, "k": 0, "ix": 5 },
92 | "nm": "忢"
93 | }
94 | ],
95 | "nm": "ç» 1",
96 | "np": 2,
97 | "cix": 2,
98 | "bm": 0,
99 | "ix": 1,
100 | "mn": "ADBE Vector Group",
101 | "hd": false
102 | }
103 | ],
104 | "ip": 0,
105 | "op": 601,
106 | "st": 0,
107 | "bm": 0
108 | },
109 | {
110 | "ddd": 0,
111 | "ind": 2,
112 | "ty": 4,
113 | "nm": "âå
èâè½®å» 2",
114 | "sr": 1,
115 | "ks": {
116 | "o": { "a": 0, "k": 100, "ix": 11 },
117 | "r": {
118 | "a": 1,
119 | "k": [
120 | {
121 | "i": { "x": [0.833], "y": [0.833] },
122 | "o": { "x": [0.167], "y": [0.167] },
123 | "t": 0,
124 | "s": [0]
125 | },
126 | { "t": 600, "s": [360] }
127 | ],
128 | "ix": 10
129 | },
130 | "p": { "a": 0, "k": [100.018, 100.016, 0], "ix": 2 },
131 | "a": { "a": 0, "k": [58.9, 61.35, 0], "ix": 1 },
132 | "s": { "a": 0, "k": [135, 135, 100], "ix": 6 }
133 | },
134 | "ao": 0,
135 | "shapes": [
136 | {
137 | "ty": "gr",
138 | "it": [
139 | {
140 | "ind": 0,
141 | "ty": "sh",
142 | "ix": 1,
143 | "ks": {
144 | "a": 0,
145 | "k": {
146 | "i": [
147 | [1.3, 1],
148 | [-0.901, 1.3],
149 | [0, 0],
150 | [-1.299, -0.9],
151 | [0.9, -1.3],
152 | [0, 0]
153 | ],
154 | "o": [
155 | [-1.301, -1],
156 | [0, 0],
157 | [1, -1.3],
158 | [1.3, 1],
159 | [0, 0],
160 | [-1, 1.3]
161 | ],
162 | "v": [
163 | [27.951, -38.5],
164 | [27.251, -42.7],
165 | [31.65, -48.7],
166 | [35.85, -49.4],
167 | [36.551, -45.2],
168 | [32.15, -39.2]
169 | ],
170 | "c": true
171 | },
172 | "ix": 2
173 | },
174 | "nm": "è·¯å¾ 1",
175 | "mn": "ADBE Vector Shape - Group",
176 | "hd": false
177 | },
178 | {
179 | "ind": 1,
180 | "ty": "sh",
181 | "ix": 2,
182 | "ks": {
183 | "a": 0,
184 | "k": {
185 | "i": [
186 | [0.5, 1.6],
187 | [-1.5, 0.5],
188 | [0, 0],
189 | [-0.5, -1.5],
190 | [1.5, -0.5],
191 | [0, 0]
192 | ],
193 | "o": [
194 | [-0.5, -1.6],
195 | [0, 0],
196 | [1.599, -0.5],
197 | [0.5, 1.6],
198 | [0, 0],
199 | [-1.6, 0.5]
200 | ],
201 | "v": [
202 | [45.251, -14.7],
203 | [47.15, -18.5],
204 | [54.251, -20.8],
205 | [58.051, -18.9],
206 | [56.15, -15.1],
207 | [49.051, -12.8]
208 | ],
209 | "c": true
210 | },
211 | "ix": 2
212 | },
213 | "nm": "è·¯å¾ 2",
214 | "mn": "ADBE Vector Shape - Group",
215 | "hd": false
216 | },
217 | {
218 | "ind": 2,
219 | "ty": "sh",
220 | "ix": 3,
221 | "ks": {
222 | "a": 0,
223 | "k": {
224 | "i": [
225 | [-0.5, 1.6],
226 | [-1.6, -0.6],
227 | [0, 0],
228 | [0.599, -1.6],
229 | [1.599, 0.6],
230 | [0, 0]
231 | ],
232 | "o": [
233 | [0.5, -1.6],
234 | [0, 0],
235 | [1.601, 0.5],
236 | [-0.5, 1.6],
237 | [0, 0],
238 | [-1.599, -0.5]
239 | ],
240 | "v": [
241 | [45.251, 14.7],
242 | [49.051, 12.8],
243 | [56.15, 15.1],
244 | [58.051, 18.9],
245 | [54.251, 20.8],
246 | [47.15, 18.5]
247 | ],
248 | "c": true
249 | },
250 | "ix": 2
251 | },
252 | "nm": "è·¯å¾ 3",
253 | "mn": "ADBE Vector Shape - Group",
254 | "hd": false
255 | },
256 | {
257 | "ind": 3,
258 | "ty": "sh",
259 | "ix": 4,
260 | "ks": {
261 | "a": 0,
262 | "k": {
263 | "i": [
264 | [-1.4, 1],
265 | [-0.899, -1.4],
266 | [0, 0],
267 | [1.401, -0.9],
268 | [0.901, 1.4],
269 | [0, 0]
270 | ],
271 | "o": [
272 | [1.3, -1],
273 | [0, 0],
274 | [1, 1.3],
275 | [-1.299, 1],
276 | [0, 0],
277 | [-1, -1.3]
278 | ],
279 | "v": [
280 | [27.951, 38.5],
281 | [32.15, 39.2],
282 | [36.551, 45.2],
283 | [35.85, 49.4],
284 | [31.65, 48.7],
285 | [27.251, 42.7]
286 | ],
287 | "c": true
288 | },
289 | "ix": 2
290 | },
291 | "nm": "è·¯å¾ 4",
292 | "mn": "ADBE Vector Shape - Group",
293 | "hd": false
294 | },
295 | {
296 | "ind": 4,
297 | "ty": "sh",
298 | "ix": 5,
299 | "ks": {
300 | "a": 0,
301 | "k": {
302 | "i": [
303 | [-1.7, 0],
304 | [0, -1.6],
305 | [0, 0],
306 | [1.6, 0],
307 | [0, 1.6],
308 | [0, 0]
309 | ],
310 | "o": [
311 | [1.699, 0],
312 | [0, 0],
313 | [0, 1.7],
314 | [-1.7, 0],
315 | [0, 0],
316 | [0, -1.7]
317 | ],
318 | "v": [
319 | [-0.049, 47.6],
320 | [2.951, 50.6],
321 | [2.951, 58.1],
322 | [-0.049, 61.1],
323 | [-3.049, 58.1],
324 | [-3.049, 50.6]
325 | ],
326 | "c": true
327 | },
328 | "ix": 2
329 | },
330 | "nm": "è·¯å¾ 5",
331 | "mn": "ADBE Vector Shape - Group",
332 | "hd": false
333 | },
334 | {
335 | "ind": 5,
336 | "ty": "sh",
337 | "ix": 6,
338 | "ks": {
339 | "a": 0,
340 | "k": {
341 | "i": [
342 | [-1.3, -1],
343 | [0.901, -1.3],
344 | [0, 0],
345 | [1.299, 0.9],
346 | [-0.899, 1.3],
347 | [0, 0]
348 | ],
349 | "o": [
350 | [1.3, 1],
351 | [0, 0],
352 | [-1, 1.3],
353 | [-1.3, -1],
354 | [0, 0],
355 | [1, -1.4]
356 | ],
357 | "v": [
358 | [-28.049, 38.5],
359 | [-27.35, 42.7],
360 | [-31.749, 48.7],
361 | [-35.949, 49.4],
362 | [-36.65, 45.2],
363 | [-32.249, 39.2]
364 | ],
365 | "c": true
366 | },
367 | "ix": 2
368 | },
369 | "nm": "è·¯å¾ 6",
370 | "mn": "ADBE Vector Shape - Group",
371 | "hd": false
372 | },
373 | {
374 | "ind": 6,
375 | "ty": "sh",
376 | "ix": 7,
377 | "ks": {
378 | "a": 0,
379 | "k": {
380 | "i": [
381 | [-0.5, -1.6],
382 | [1.5, -0.5],
383 | [0, 0],
384 | [0.5, 1.5],
385 | [-1.5, 0.5],
386 | [0, 0]
387 | ],
388 | "o": [
389 | [0.5, 1.6],
390 | [0, 0],
391 | [-1.6, 0.5],
392 | [-0.5, -1.6],
393 | [0, 0],
394 | [1.601, -0.6]
395 | ],
396 | "v": [
397 | [-45.349, 14.7],
398 | [-47.249, 18.5],
399 | [-54.349, 20.8],
400 | [-58.15, 18.9],
401 | [-56.249, 15.1],
402 | [-49.15, 12.8]
403 | ],
404 | "c": true
405 | },
406 | "ix": 2
407 | },
408 | "nm": "è·¯å¾ 7",
409 | "mn": "ADBE Vector Shape - Group",
410 | "hd": false
411 | },
412 | {
413 | "ind": 7,
414 | "ty": "sh",
415 | "ix": 8,
416 | "ks": {
417 | "a": 0,
418 | "k": {
419 | "i": [
420 | [0.641, -1.597],
421 | [1.512, 0.696],
422 | [0, 0],
423 | [-0.695, 1.512],
424 | [-1.512, -0.695],
425 | [0, 0]
426 | ],
427 | "o": [
428 | [-0.558, 1.542],
429 | [0, 0],
430 | [-1.543, -0.558],
431 | [0.558, -1.542],
432 | [0, 0],
433 | [1.543, 0.557]
434 | ],
435 | "v": [
436 | [-44.187, -14.327],
437 | [-48.104, -12.657],
438 | [-55.041, -15.347],
439 | [-56.712, -19.264],
440 | [-52.795, -20.934],
441 | [-45.858, -18.244]
442 | ],
443 | "c": true
444 | },
445 | "ix": 2
446 | },
447 | "nm": "è·¯å¾ 8",
448 | "mn": "ADBE Vector Shape - Group",
449 | "hd": false
450 | },
451 | {
452 | "ind": 8,
453 | "ty": "sh",
454 | "ix": 9,
455 | "ks": {
456 | "a": 0,
457 | "k": {
458 | "i": [
459 | [1.399, -1],
460 | [0.9, 1.4],
461 | [0, 0],
462 | [-1.4, 0.9],
463 | [-0.901, -1.4],
464 | [0, 0]
465 | ],
466 | "o": [
467 | [-1.3, 1],
468 | [0, 0],
469 | [-1, -1.3],
470 | [1.299, -1],
471 | [0, 0],
472 | [1, 1.3]
473 | ],
474 | "v": [
475 | [-28.049, -38.5],
476 | [-32.249, -39.2],
477 | [-36.65, -45.2],
478 | [-35.949, -49.4],
479 | [-31.749, -48.7],
480 | [-27.35, -42.7]
481 | ],
482 | "c": true
483 | },
484 | "ix": 2
485 | },
486 | "nm": "è·¯å¾ 9",
487 | "mn": "ADBE Vector Shape - Group",
488 | "hd": false
489 | },
490 | {
491 | "ind": 9,
492 | "ty": "sh",
493 | "ix": 10,
494 | "ks": {
495 | "a": 0,
496 | "k": {
497 | "i": [
498 | [1.699, 0],
499 | [0, 1.6],
500 | [0, 0],
501 | [-1.601, 0],
502 | [0, -1.6],
503 | [0, 0]
504 | ],
505 | "o": [
506 | [-1.7, 0],
507 | [0, 0],
508 | [0, -1.7],
509 | [1.699, 0],
510 | [0, 0],
511 | [0, 1.6]
512 | ],
513 | "v": [
514 | [-0.049, -47.6],
515 | [-3.049, -50.6],
516 | [-3.049, -58.1],
517 | [-0.049, -61.1],
518 | [2.951, -58.1],
519 | [2.951, -50.6]
520 | ],
521 | "c": true
522 | },
523 | "ix": 2
524 | },
525 | "nm": "è·¯å¾ 10",
526 | "mn": "ADBE Vector Shape - Group",
527 | "hd": false
528 | },
529 | {
530 | "ty": "mm",
531 | "mm": 1,
532 | "nm": "åå¹¶è·¯å¾ 1",
533 | "mn": "ADBE Vector Filter - Merge",
534 | "hd": false
535 | },
536 | {
537 | "ty": "fl",
538 | "c": {
539 | "a": 0,
540 | "k": [0.929000016755, 0.663000009574, 0.081999999402, 1],
541 | "ix": 4
542 | },
543 | "o": { "a": 0, "k": 100, "ix": 5 },
544 | "r": 1,
545 | "bm": 0,
546 | "nm": "å¡«å
1",
547 | "mn": "ADBE Vector Graphic - Fill",
548 | "hd": false
549 | },
550 | {
551 | "ty": "tr",
552 | "p": { "a": 0, "k": [58.9, 61.35], "ix": 2 },
553 | "a": { "a": 0, "k": [0, 0], "ix": 1 },
554 | "s": { "a": 0, "k": [100, 100], "ix": 3 },
555 | "r": { "a": 0, "k": 0, "ix": 6 },
556 | "o": { "a": 0, "k": 100, "ix": 7 },
557 | "sk": { "a": 0, "k": 0, "ix": 4 },
558 | "sa": { "a": 0, "k": 0, "ix": 5 },
559 | "nm": "忢"
560 | }
561 | ],
562 | "nm": "ç» 1",
563 | "np": 12,
564 | "cix": 2,
565 | "bm": 0,
566 | "ix": 1,
567 | "mn": "ADBE Vector Group",
568 | "hd": false
569 | }
570 | ],
571 | "ip": 0,
572 | "op": 601,
573 | "st": 0,
574 | "bm": 0
575 | }
576 | ],
577 | "markers": []
578 | }
579 |
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/notJust-dev/DEVember/6c089768bddc178e892fcc41f938fec168b252f4/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | plugins: ['expo-router/babel', 'react-native-reanimated/plugin'],
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/eas.json:
--------------------------------------------------------------------------------
1 | {
2 | "cli": {
3 | "version": ">= 5.9.1"
4 | },
5 | "build": {
6 | "development": {
7 | "developmentClient": true,
8 | "distribution": "internal",
9 | "channel": "development",
10 | "ios": {
11 | "simulator": true
12 | }
13 | },
14 | "preview": {
15 | "distribution": "internal",
16 | "channel": "preview"
17 | },
18 | "production": {
19 | "channel": "production"
20 | }
21 | },
22 | "submit": {
23 | "production": {}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "348651167962",
4 | "project_id": "devember-c53c7",
5 | "storage_bucket": "devember-c53c7.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:348651167962:android:89b787394e9890711885e3",
11 | "android_client_info": {
12 | "package_name": "com.vadinsavin.DEVember"
13 | }
14 | },
15 | "oauth_client": [],
16 | "api_key": [
17 | {
18 | "current_key": "AIzaSyBE9gxlpNiDpQ8lMN2i9aABWBv9afFXVcY"
19 | }
20 | ],
21 | "services": {
22 | "appinvite_service": {
23 | "other_platform_oauth_client": []
24 | }
25 | }
26 | }
27 | ],
28 | "configuration_version": "1"
29 | }
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | // const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
2 | const { getDefaultConfig } = require('expo/metro-config');
3 |
4 | const config = getDefaultConfig(__dirname);
5 |
6 | config.resolver.assetExts.push(
7 | // Adds support for `.lottie` files
8 | 'lottie'
9 | );
10 |
11 | module.exports = config;
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "devember",
3 | "version": "1.0.0",
4 | "main": "expo-router/entry",
5 | "scripts": {
6 | "start": "expo start --dev-client",
7 | "android": "expo run:android",
8 | "ios": "expo run:ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "@aws-amplify/react-native": "^1.0.6",
13 | "@aws-amplify/ui-react-native": "^2.0.5",
14 | "@expo-google-fonts/amatic-sc": "^0.2.3",
15 | "@expo-google-fonts/inter": "^0.2.3",
16 | "@gorhom/bottom-sheet": "^4.5.1",
17 | "@react-native-async-storage/async-storage": "1.18.2",
18 | "@react-native-community/netinfo": "^11.2.0",
19 | "@react-native/metro-config": "^0.73.2",
20 | "aws-amplify": "^6.0.6",
21 | "dayjs": "^1.11.10",
22 | "expo": "~49.0.15",
23 | "expo-av": "~13.4.1",
24 | "expo-blur": "~12.4.1",
25 | "expo-constants": "~14.4.2",
26 | "expo-dev-client": "~2.4.12",
27 | "expo-device": "~5.4.0",
28 | "expo-font": "~11.4.0",
29 | "expo-linear-gradient": "~12.3.0",
30 | "expo-linking": "~5.0.2",
31 | "expo-local-authentication": "~13.4.1",
32 | "expo-location": "~16.1.0",
33 | "expo-notifications": "~0.20.1",
34 | "expo-router": "^2.0.0",
35 | "expo-splash-screen": "~0.20.5",
36 | "expo-status-bar": "~1.6.0",
37 | "expo-updates": "~0.18.17",
38 | "lottie-react-native": "5.1.6",
39 | "react": "18.2.0",
40 | "react-dom": "18.2.0",
41 | "react-native": "0.72.6",
42 | "react-native-gesture-handler": "~2.12.0",
43 | "react-native-get-random-values": "^1.10.0",
44 | "react-native-maps": "1.7.1",
45 | "react-native-markdown-display": "^7.0.0-alpha.2",
46 | "react-native-purchases": "^7.5.1",
47 | "react-native-reanimated": "~3.3.0",
48 | "react-native-safe-area-context": "4.6.3",
49 | "react-native-screens": "~3.22.0",
50 | "react-native-url-polyfill": "^2.0.0",
51 | "react-native-vision-camera": "^3.6.14",
52 | "react-native-web": "~0.19.6",
53 | "uuid": "^9.0.1",
54 | "vexo-analytics": "^1.3.12",
55 | "zustand": "^4.4.7"
56 | },
57 | "devDependencies": {
58 | "@babel/core": "^7.20.0",
59 | "@types/react": "~18.2.14",
60 | "typescript": "^5.1.3"
61 | },
62 | "private": true
63 | }
64 |
--------------------------------------------------------------------------------
/src/amplifyconfiguration.json:
--------------------------------------------------------------------------------
1 | {
2 | "aws_project_region": "us-east-1",
3 | "aws_cognito_identity_pool_id": "us-east-1:65cd4264-c477-488a-ba15-f866af306690",
4 | "aws_cognito_region": "us-east-1",
5 | "aws_user_pools_id": "us-east-1_ZYdrFTIm6",
6 | "aws_user_pools_web_client_id": "oo8r9kc105f82a7utmuqevg3v",
7 | "oauth": {},
8 | "aws_cognito_username_attributes": [
9 | "EMAIL"
10 | ],
11 | "aws_cognito_social_providers": [],
12 | "aws_cognito_signup_attributes": [
13 | "EMAIL"
14 | ],
15 | "aws_cognito_mfa_configuration": "OFF",
16 | "aws_cognito_mfa_types": [
17 | "SMS"
18 | ],
19 | "aws_cognito_password_protection_settings": {
20 | "passwordPolicyMinLength": 8,
21 | "passwordPolicyCharacters": []
22 | },
23 | "aws_cognito_verification_mechanisms": [
24 | "EMAIL"
25 | ]
26 | }
--------------------------------------------------------------------------------
/src/app/(days)/day1/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from 'react-native';
2 | import React from 'react';
3 | import { Stack } from 'expo-router';
4 |
5 | const DayDetailsScreen = () => {
6 | return (
7 |
8 |
9 |
10 |
11 | Day Details Screen
12 |
13 |
14 | );
15 | };
16 |
17 | export default DayDetailsScreen;
18 |
--------------------------------------------------------------------------------
/src/app/(days)/day10/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Biometrics
9 | Use FaceID and Fingerprint to unlock the next screens`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day10/protected/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from 'expo-router';
2 | import { useEffect, useState } from 'react';
3 | import * as LocalAuthentication from 'expo-local-authentication';
4 | import { Alert, Text, View } from 'react-native';
5 | import { FontAwesome5 } from '@expo/vector-icons';
6 | import { useBiometrics } from '@/components/day10/BiometricsProvider';
7 |
8 | export default function BiometricProtectedLayout() {
9 | const { isUnlocked, authenticate } = useBiometrics();
10 |
11 | useEffect(() => {
12 | if (!isUnlocked) {
13 | authenticate();
14 | }
15 | }, []);
16 |
17 | if (!isUnlocked) {
18 | return (
19 |
27 |
28 | Use FaceId to Unlock
29 |
30 |
36 |
37 | );
38 | }
39 |
40 | return ;
41 | }
42 |
--------------------------------------------------------------------------------
/src/app/(days)/day10/protected/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from 'react-native';
2 | import React from 'react';
3 | import { FontAwesome5 } from '@expo/vector-icons';
4 | import { Link } from 'expo-router';
5 |
6 | const ProtectedScreen = () => {
7 | return (
8 |
16 |
17 | Protected Info
18 |
19 |
20 |
21 | Next page
22 |
23 | );
24 | };
25 |
26 | export default ProtectedScreen;
27 |
--------------------------------------------------------------------------------
/src/app/(days)/day10/protected/second.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text } from 'react-native';
2 | import React from 'react';
3 | import { FontAwesome5 } from '@expo/vector-icons';
4 | import { Link } from 'expo-router';
5 |
6 | const ProtectedScreen = () => {
7 | return (
8 |
16 |
17 | More protected info
18 |
19 |
20 |
21 | Prev page
22 |
23 | );
24 | };
25 |
26 | export default ProtectedScreen;
27 |
--------------------------------------------------------------------------------
/src/app/(days)/day11/camera.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from 'expo-router';
2 | import { useCallback, useEffect, useRef, useState } from 'react';
3 | import {
4 | View,
5 | Text,
6 | ActivityIndicator,
7 | StyleSheet,
8 | Pressable,
9 | Image,
10 | Button,
11 | } from 'react-native';
12 | import {
13 | useCameraPermission,
14 | useMicrophonePermission,
15 | useCameraDevice,
16 | Camera,
17 | PhotoFile,
18 | TakePhotoOptions,
19 | VideoFile,
20 | useCodeScanner,
21 | } from 'react-native-vision-camera';
22 | import { useFocusEffect } from 'expo-router';
23 | import { FontAwesome5, Ionicons } from '@expo/vector-icons';
24 | import { Video } from 'expo-av';
25 |
26 | const CameraScreen = () => {
27 | const device = useCameraDevice('back', {
28 | physicalDevices: ['ultra-wide-angle-camera'],
29 | });
30 | const codeScanner = useCodeScanner({
31 | codeTypes: ['qr', 'ean-13'],
32 | onCodeScanned: (codes) => {
33 | console.log(`Scanned ${codes.length} codes!`);
34 | console.log(codes[0]);
35 | },
36 | });
37 |
38 | const { hasPermission, requestPermission } = useCameraPermission();
39 | const {
40 | hasPermission: microphonePermission,
41 | requestPermission: requestMicrophonePermission,
42 | } = useMicrophonePermission();
43 |
44 | const [isActive, setIsActive] = useState(false);
45 | const [flash, setFlash] = useState('off');
46 | const [isRecording, setIsRecording] = useState(false);
47 |
48 | const [photo, setPhoto] = useState();
49 | const [video, setVideo] = useState();
50 |
51 | const camera = useRef(null);
52 |
53 | const [mode, setMode] = useState('camera');
54 |
55 | useFocusEffect(
56 | useCallback(() => {
57 | setIsActive(true);
58 | return () => {
59 | setIsActive(false);
60 | };
61 | }, [])
62 | );
63 |
64 | useEffect(() => {
65 | if (!hasPermission) {
66 | requestPermission();
67 | }
68 |
69 | if (!microphonePermission) {
70 | requestMicrophonePermission();
71 | }
72 | }, [hasPermission, microphonePermission]);
73 |
74 | const onTakePicturePressed = async () => {
75 | if (isRecording) {
76 | camera.current?.stopRecording();
77 | return;
78 | }
79 |
80 | const photo = await camera.current?.takePhoto({
81 | flash,
82 | });
83 | setPhoto(photo);
84 | };
85 |
86 | const onStartRecording = async () => {
87 | if (!camera.current) {
88 | return;
89 | }
90 | setIsRecording(true);
91 | camera.current.startRecording({
92 | flash: flash === 'on' ? 'on' : 'off',
93 | onRecordingFinished: (video) => {
94 | console.log(video);
95 | setIsRecording(false);
96 | setVideo(video);
97 | },
98 | onRecordingError: (error) => {
99 | console.error(error);
100 | setIsRecording(false);
101 | },
102 | });
103 | };
104 |
105 | const uploadPhoto = async () => {
106 | if (!photo) {
107 | return;
108 | }
109 |
110 | const result = await fetch(`file://${photo.path}`);
111 | const data = await result.blob();
112 | // console.log(data);
113 | // upload data to your network storage (ex: s3, supabase storage, etc)
114 | };
115 |
116 | if (!hasPermission || !microphonePermission) {
117 | return ;
118 | }
119 |
120 | if (!device) {
121 | return Camera device not found;
122 | }
123 | console.log('QR camer: ', mode === 'qr' && isActive && !photo && !video);
124 | return (
125 |
126 |
127 |
128 | {mode === 'qr' ? (
129 |
135 | ) : (
136 |
145 | )}
146 |
147 | {video && (
148 | <>
149 |
157 | >
158 | )}
159 |
160 | {photo && (
161 | <>
162 |
163 | setPhoto(undefined)}
165 | name="arrow-left"
166 | size={25}
167 | color="white"
168 | style={{ position: 'absolute', top: 50, left: 30 }}
169 | />
170 |
180 |
181 |
182 | >
183 | )}
184 |
185 | {!photo && !video && (
186 | <>
187 |
198 |
201 | setFlash((curValue) => (curValue === 'off' ? 'on' : 'off'))
202 | }
203 | size={30}
204 | color="white"
205 | />
206 |
207 | setMode(mode === 'qr' ? 'camera' : 'qr')}
210 | size={30}
211 | color="white"
212 | />
213 |
214 |
215 |
228 | >
229 | )}
230 |
231 | );
232 | };
233 |
234 | export default CameraScreen;
235 |
--------------------------------------------------------------------------------
/src/app/(days)/day11/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Camera app
9 | Take photos and record videos with React Native Vision Camera`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day12/feed.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from 'expo-router';
2 | import { View, StyleSheet, FlatList, FlatListProps } from 'react-native';
3 |
4 | import { StatusBar } from 'expo-status-bar';
5 | import VideoPost from '@/components/day12/VideoPost';
6 | import { useCallback, useEffect, useRef, useState } from 'react';
7 |
8 | const dummyPosts = [
9 | {
10 | id: '2',
11 | video:
12 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-videos/2.mp4',
13 | caption: 'Caption of the post',
14 | },
15 | {
16 | id: '1',
17 | video:
18 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-videos/1.mp4',
19 | caption: 'Hey there',
20 | },
21 | {
22 | id: '3',
23 | video:
24 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-videos/3.mp4',
25 | caption: 'Hola',
26 | },
27 | {
28 | id: '4',
29 | video:
30 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-videos/4.mp4',
31 | caption: 'Piano practice',
32 | },
33 | {
34 | id: '5',
35 | video:
36 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-videos/5.mp4',
37 | caption: 'Hello World!',
38 | },
39 | ];
40 |
41 | const FeedScreen = () => {
42 | const [activePostId, setActivePostId] = useState(dummyPosts[0].id);
43 | const [posts, setPosts] = useState([]);
44 |
45 | useEffect(() => {
46 | const fetchPosts = async () => {
47 | // fetch posts from the server
48 | setPosts(dummyPosts);
49 | };
50 |
51 | fetchPosts();
52 | }, []);
53 |
54 | const viewabilityConfigCallbackPairs = useRef([
55 | {
56 | viewabilityConfig: { itemVisiblePercentThreshold: 50 },
57 | onViewableItemsChanged: ({ changed, viewableItems }) => {
58 | if (viewableItems.length > 0 && viewableItems[0].isViewable) {
59 | setActivePostId(viewableItems[0].item.id);
60 | }
61 | },
62 | },
63 | ]);
64 |
65 | const onEndReached = () => {
66 | // fetch more posts from database
67 | setPosts((currentPosts) => [...currentPosts, ...dummyPosts]);
68 | };
69 |
70 | return (
71 |
72 |
73 |
74 |
75 | (
78 |
79 | )}
80 | keyExtractor={(item, index) => `${item.id}-${index}`}
81 | pagingEnabled
82 | viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
83 | showsVerticalScrollIndicator={false}
84 | onEndReached={onEndReached}
85 | onEndReachedThreshold={3}
86 | />
87 |
88 | );
89 | };
90 |
91 | const styles = StyleSheet.create({
92 | container: {
93 | flex: 1,
94 | backgroundColor: 'black',
95 | },
96 | });
97 |
98 | export default FeedScreen;
99 |
--------------------------------------------------------------------------------
/src/app/(days)/day12/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # TikTok Feed
9 | Video feed similar to TikTok, IG Reels, Youtube Shorts`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day14/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Push Notifications
9 | Send and Receive Push Notifications`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day14/notifications/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Slot, router } from 'expo-router';
2 | import * as Notifications from 'expo-notifications';
3 | import { useEffect, useRef, useState } from 'react';
4 | import { Platform, View, Text } from 'react-native';
5 | import * as Device from 'expo-device';
6 | import { AntDesign } from '@expo/vector-icons';
7 | import Constants from 'expo-constants';
8 |
9 | Notifications.setNotificationHandler({
10 | handleNotification: async () => ({
11 | shouldShowAlert: true,
12 | shouldPlaySound: false,
13 | shouldSetBadge: true,
14 | }),
15 | });
16 |
17 | const AppWithNotificationsLayout = () => {
18 | const [expoPushToken, setExpoPushToken] = useState();
19 | const [notification, setNotification] =
20 | useState();
21 | const notificationListener = useRef();
22 | const responseListener = useRef();
23 |
24 | useEffect(() => {
25 | let isMounted = true;
26 |
27 | // fetch Expo Push Token
28 | registerForPushNotificationsAsync().then((token) =>
29 | setExpoPushToken(token)
30 | );
31 | notificationListener.current =
32 | Notifications.addNotificationReceivedListener((notification) => {
33 | setNotification(notification);
34 | });
35 |
36 | responseListener.current =
37 | Notifications.addNotificationResponseReceivedListener((response) => {
38 | redirect(response.notification);
39 | });
40 |
41 | Notifications.getLastNotificationResponseAsync().then((response) => {
42 | if (!isMounted || !response?.notification) {
43 | return;
44 | }
45 | redirect(response?.notification);
46 | });
47 |
48 | return () => {
49 | if (notificationListener.current) {
50 | Notifications.removeNotificationSubscription(
51 | notificationListener.current
52 | );
53 | }
54 | if (responseListener.current) {
55 | Notifications.removeNotificationSubscription(responseListener.current);
56 | }
57 | isMounted = false;
58 | };
59 | }, []);
60 |
61 | function redirect(notification: Notifications.Notification) {
62 | const url = notification.request.content.data?.url;
63 | if (url) {
64 | router.push(url);
65 | }
66 | }
67 |
68 | console.log('Token: ', expoPushToken);
69 | console.log(notification);
70 |
71 | return (
72 | <>
73 |
74 |
75 | {expoPushToken && (
76 |
79 | {expoPushToken}
80 |
81 | )}
82 | {notification && (
83 |
95 |
96 | Title: {notification && notification.request.content.title}{' '}
97 |
98 | Body: {notification && notification.request.content.body}
99 |
100 | Data:{' '}
101 | {notification && JSON.stringify(notification.request.content.data)}
102 |
103 | setNotification(undefined)}
109 | />
110 |
111 | )}
112 | >
113 | );
114 | };
115 |
116 | async function registerForPushNotificationsAsync() {
117 | let token;
118 |
119 | if (Platform.OS === 'android') {
120 | await Notifications.setNotificationChannelAsync('default', {
121 | name: 'default',
122 | importance: Notifications.AndroidImportance.MAX,
123 | vibrationPattern: [0, 250, 250, 250],
124 | lightColor: '#FF231F7C',
125 | });
126 | }
127 |
128 | if (Device.isDevice) {
129 | const { status: existingStatus } =
130 | await Notifications.getPermissionsAsync();
131 | let finalStatus = existingStatus;
132 | if (existingStatus !== 'granted') {
133 | const { status } = await Notifications.requestPermissionsAsync();
134 | finalStatus = status;
135 | }
136 | if (finalStatus !== 'granted') {
137 | alert('Failed to get push token for push notification!');
138 | return;
139 | }
140 | // Learn more about projectId:
141 | // https://docs.expo.dev/push-notifications/push-notifications-setup/#configure-projectid
142 |
143 | if (!Constants.expoConfig?.extra?.eas.projectId) {
144 | alert('No ProjectId found in app.json');
145 | return;
146 | }
147 | token = (
148 | await Notifications.getExpoPushTokenAsync({
149 | projectId: Constants.expoConfig.extra.eas.projectId,
150 | })
151 | ).data;
152 | console.log(token);
153 | } else {
154 | alert('Must use physical device for Push Notifications');
155 | }
156 |
157 | return token;
158 | }
159 |
160 | export default AppWithNotificationsLayout;
161 |
--------------------------------------------------------------------------------
/src/app/(days)/day14/notifications/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import * as Notifications from 'expo-notifications';
3 |
4 | const NotificationsHomeScreen = () => {
5 | return (
6 |
7 | Notifications
8 |
9 |
13 |
14 | );
15 | };
16 |
17 | async function schedulePushNotification() {
18 | await Notifications.scheduleNotificationAsync({
19 | content: {
20 | title: 'Check out the new Tinder Swipe Animation! 📬',
21 | body: 'Here is the notification body',
22 | data: { data: 'goes here', url: '/day6/tinder' },
23 | },
24 | trigger: { seconds: 5 },
25 | });
26 | }
27 |
28 | export default NotificationsHomeScreen;
29 |
--------------------------------------------------------------------------------
/src/app/(days)/day15/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Todo app
9 | Ultimate Todo app`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day15/todo/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | FlatList,
4 | KeyboardAvoidingView,
5 | Platform,
6 | Text,
7 | Button,
8 | View,
9 | } from 'react-native';
10 | import { useState } from 'react';
11 | import { Stack } from 'expo-router';
12 | import NewTaskInput from '@/components/day15/NewTaskInput';
13 | import { SafeAreaView } from 'react-native-safe-area-context';
14 | import TaskListItem from '@/components/day15/TaskListItem';
15 | import Reanimated, { CurvedTransition } from 'react-native-reanimated';
16 | import { useHeaderHeight } from '@react-navigation/elements';
17 |
18 | export type Task = {
19 | title: string;
20 | isFinished: boolean;
21 | };
22 |
23 | const dummyTasks: Task[] = [
24 | {
25 | title: 'Setup Day15 structure',
26 | isFinished: true,
27 | },
28 | {
29 | title: 'Render a list of tasks',
30 | isFinished: false,
31 | },
32 | {
33 | title: 'Add a new task',
34 | isFinished: false,
35 | },
36 | {
37 | title: 'Change the status of a task',
38 | isFinished: false,
39 | },
40 | {
41 | title: 'Separate in 2 tabs: todo, and complete',
42 | isFinished: false,
43 | },
44 | ];
45 |
46 | const TodoScreen = () => {
47 | const [tasks, setTasks] = useState(dummyTasks);
48 | const [searchQuery, setSearchQuery] = useState('');
49 | const [tab, setTab] = useState<'All' | 'Todo' | 'Finished'>('All');
50 |
51 | const headerHeight = useHeaderHeight();
52 |
53 | const filteredTasks = tasks.filter((task) => {
54 | if (task.isFinished && tab === 'Todo') {
55 | return false;
56 | }
57 | if (!task.isFinished && tab === 'Finished') {
58 | return false;
59 | }
60 |
61 | if (!searchQuery) {
62 | return true;
63 | }
64 |
65 | return task.title
66 | .toLowerCase()
67 | .trim()
68 | .includes(searchQuery.toLowerCase().trim());
69 | });
70 |
71 | const onItemPressed = (index: number) => {
72 | setTasks((currentTasks) => {
73 | const updatedTasks = [...currentTasks];
74 | updatedTasks[index].isFinished = !updatedTasks[index].isFinished;
75 | return updatedTasks;
76 | });
77 | };
78 |
79 | const deleteTask = (index: number) => {
80 | setTasks((currentTasks) => {
81 | const updatedTasks = [...currentTasks];
82 | updatedTasks.splice(index, 1);
83 | return updatedTasks;
84 | });
85 | };
86 |
87 | return (
88 |
92 | setSearchQuery(e.nativeEvent.text),
99 | },
100 | }}
101 | />
102 |
103 |
107 |
114 |
118 | item.title}
122 | renderItem={({ item, index }) => (
123 |
124 | onItemPressed(index)}
127 | onDelete={() => deleteTask(index)}
128 | />
129 |
130 | )}
131 | ListFooterComponent={() => (
132 |
134 | setTasks((currentTasks) => [...currentTasks, newTodo])
135 | }
136 | />
137 | )}
138 | />
139 |
140 |
141 | );
142 | };
143 |
144 | const styles = StyleSheet.create({
145 | page: {
146 | backgroundColor: 'white',
147 | flex: 1,
148 | },
149 | });
150 |
151 | export default TodoScreen;
152 |
--------------------------------------------------------------------------------
/src/app/(days)/day16/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Todo app with React Context
9 | Ultimate Todo app with React Context`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day16/todo/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from 'expo-router';
2 | import TasksContextProvider from '@/components/day16/TasksContextProvider';
3 |
4 | export default function TodoLayout() {
5 | return (
6 |
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/(days)/day16/todo/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | FlatList,
4 | KeyboardAvoidingView,
5 | Platform,
6 | Text,
7 | Button,
8 | View,
9 | } from 'react-native';
10 | import { SafeAreaView } from 'react-native-safe-area-context';
11 | import { Stack } from 'expo-router';
12 | import NewTaskInput from '@/components/day16/NewTaskInput';
13 | import TaskListItem from '@/components/day16/TaskListItem';
14 | import Reanimated, { CurvedTransition } from 'react-native-reanimated';
15 | import { useHeaderHeight } from '@react-navigation/elements';
16 |
17 | import { useState } from 'react';
18 | import { useTasks, type Task } from '@/components/day16/TasksContextProvider';
19 |
20 | const TodoScreen = () => {
21 | const [searchQuery, setSearchQuery] = useState('');
22 | const [tab, setTab] = useState<'All' | 'Todo' | 'Finished'>('All');
23 |
24 | const headerHeight = useHeaderHeight();
25 |
26 | const { getFilteredTasks, numberOfCompletedTasks, numberOfTasks } =
27 | useTasks();
28 |
29 | const filteredTasks = getFilteredTasks(tab, searchQuery);
30 |
31 | return (
32 |
36 | (
41 |
42 | {numberOfCompletedTasks} / {numberOfTasks}
43 |
44 | ),
45 | headerSearchBarOptions: {
46 | hideWhenScrolling: true,
47 | onChangeText: (e) => setSearchQuery(e.nativeEvent.text),
48 | },
49 | }}
50 | />
51 |
52 |
56 |
63 | setTab('All')} />
64 | setTab('Todo')} />
65 | setTab('Finished')} />
66 |
67 | (
71 |
72 |
73 |
74 | )}
75 | ListFooterComponent={() => }
76 | />
77 |
78 |
79 | );
80 | };
81 |
82 | const styles = StyleSheet.create({
83 | page: {
84 | backgroundColor: 'white',
85 | flex: 1,
86 | },
87 | });
88 |
89 | export default TodoScreen;
90 |
--------------------------------------------------------------------------------
/src/app/(days)/day17/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 | import AsyncStorage from '@react-native-async-storage/async-storage';
7 |
8 | const description = `
9 | # Todo app with Zustand
10 | Ultimate Todo app with Zustand`;
11 |
12 | const DayDetailsScreen = () => {
13 | return (
14 |
15 |
16 |
17 | {description}
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default DayDetailsScreen;
27 |
--------------------------------------------------------------------------------
/src/app/(days)/day17/todo/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from 'expo-router';
2 |
3 | export default function TodoLayout() {
4 | return (
5 | <>
6 |
7 |
8 | >
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/(days)/day17/todo/index.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | FlatList,
4 | KeyboardAvoidingView,
5 | Platform,
6 | Text,
7 | Button,
8 | View,
9 | } from 'react-native';
10 | import { SafeAreaView } from 'react-native-safe-area-context';
11 | import { Stack } from 'expo-router';
12 | import NewTaskInput from '@/components/day17/NewTaskInput';
13 | import TaskListItem from '@/components/day17/TaskListItem';
14 | import Reanimated, { CurvedTransition } from 'react-native-reanimated';
15 | import { useHeaderHeight } from '@react-navigation/elements';
16 |
17 | import { useState } from 'react';
18 | import { useTasksStore } from '@/components/day17/TasksStore';
19 |
20 | const TodoScreen = () => {
21 |
22 | const [searchQuery, setSearchQuery] = useState('');
23 | const [tab, setTab] = useState<'All' | 'Todo' | 'Finished'>('All');
24 |
25 | const headerHeight = useHeaderHeight();
26 |
27 | const numberOfCompletedTasks = useTasksStore((state) =>
28 | state.numberOfCompletedTasks(0)
29 | );
30 | const numberOfTasks = useTasksStore((state) => state.numberOfTasks());
31 |
32 | const getFilteredTasks = useTasksStore((state) => state.getFilteredTasks);
33 |
34 | const filteredTasks = getFilteredTasks(tab, searchQuery);
35 |
36 | return (
37 |
41 | (
46 |
47 | {numberOfCompletedTasks} / {numberOfTasks}
48 |
49 | ),
50 | headerSearchBarOptions: {
51 | hideWhenScrolling: true,
52 | onChangeText: (e) => setSearchQuery(e.nativeEvent.text),
53 | },
54 | }}
55 | />
56 |
57 |
61 |
68 | setTab('All')} />
69 | setTab('Todo')} />
70 | setTab('Finished')} />
71 |
72 | (
76 |
77 |
78 |
79 | )}
80 | ListFooterComponent={() => }
81 | />
82 |
83 |
84 | );
85 | };
86 |
87 | const styles = StyleSheet.create({
88 | page: {
89 | backgroundColor: 'white',
90 | flex: 1,
91 | },
92 | });
93 |
94 | export default TodoScreen;
95 |
--------------------------------------------------------------------------------
/src/app/(days)/day2/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 |
5 | const DayDetailsScreen = () => {
6 | return (
7 |
8 |
9 | Day Details Screen
10 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default DayDetailsScreen;
19 |
--------------------------------------------------------------------------------
/src/app/(days)/day2/onboarding.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Stack, router } from 'expo-router';
2 | import React, { useState } from 'react';
3 | import { Text, View, StyleSheet, SafeAreaView, Pressable } from 'react-native';
4 | import { FontAwesome5 } from '@expo/vector-icons';
5 | import { StatusBar } from 'expo-status-bar';
6 | import {
7 | GestureDetector,
8 | Gesture,
9 | Directions,
10 | } from 'react-native-gesture-handler';
11 |
12 | import Animated, {
13 | FadeIn,
14 | FadeOut,
15 | BounceInRight,
16 | SlideOutLeft,
17 | BounceOutLeft,
18 | SlideInRight,
19 | } from 'react-native-reanimated';
20 |
21 | const onboardingSteps = [
22 | {
23 | icon: 'snowflake',
24 | title: 'Welcome #DEVember',
25 | description: 'Daily React Native tutorials during December',
26 | },
27 | {
28 | icon: 'people-arrows',
29 | title: 'Learn and grow together',
30 | description: 'Learn by building 24 projects with React Native and Expo',
31 | },
32 | {
33 | icon: 'book-reader',
34 | title: 'Education for Children',
35 | description:
36 | 'Contribute to the fundraiser "Education for Children" to help Save the Children in their effort of providing education to every child',
37 | },
38 | ];
39 |
40 | export default function OnboardingScreen() {
41 | const [screenIndex, setScreenIndex] = useState(0);
42 |
43 | const data = onboardingSteps[screenIndex];
44 |
45 | const onContinue = () => {
46 | const isLastScreen = screenIndex === onboardingSteps.length - 1;
47 | if (isLastScreen) {
48 | endOnboarding();
49 | } else {
50 | setScreenIndex(screenIndex + 1);
51 | }
52 | };
53 |
54 | const onBack = () => {
55 | const isFirstScreen = screenIndex === 0;
56 | if (isFirstScreen) {
57 | endOnboarding();
58 | } else {
59 | setScreenIndex(screenIndex - 1);
60 | }
61 | };
62 |
63 | const endOnboarding = () => {
64 | setScreenIndex(0);
65 | router.back();
66 | };
67 |
68 | const swipes = Gesture.Simultaneous(
69 | Gesture.Fling().direction(Directions.LEFT).onEnd(onContinue),
70 | Gesture.Fling().direction(Directions.RIGHT).onEnd(onBack)
71 | );
72 |
73 | return (
74 |
75 |
76 |
77 |
78 |
79 | {onboardingSteps.map((step, index) => (
80 |
87 | ))}
88 |
89 |
90 |
91 |
92 |
93 |
99 |
100 |
101 |
102 |
107 | {data.title}
108 |
109 |
114 | {data.description}
115 |
116 |
117 |
118 |
119 | Skip
120 |
121 |
122 |
123 | Continue
124 |
125 |
126 |
127 |
128 |
129 |
130 | );
131 | }
132 |
133 | const styles = StyleSheet.create({
134 | page: {
135 | // alignItems: 'center',
136 | justifyContent: 'center',
137 | flex: 1,
138 | backgroundColor: '#15141A',
139 | },
140 | pageContent: {
141 | padding: 20,
142 | flex: 1,
143 | },
144 | image: {
145 | alignSelf: 'center',
146 | margin: 20,
147 | marginTop: 70,
148 | },
149 | title: {
150 | color: '#FDFDFD',
151 | fontSize: 50,
152 | fontFamily: 'InterBlack',
153 | letterSpacing: 1.3,
154 | marginVertical: 10,
155 | },
156 | description: {
157 | color: 'gray',
158 | fontSize: 20,
159 | fontFamily: 'Inter',
160 | lineHeight: 28,
161 | },
162 | footer: {
163 | marginTop: 'auto',
164 | },
165 |
166 | buttonsRow: {
167 | marginTop: 20,
168 | flexDirection: 'row',
169 | alignItems: 'center',
170 | gap: 20,
171 | },
172 | button: {
173 | backgroundColor: '#302E38',
174 | borderRadius: 50,
175 | alignItems: 'center',
176 | flex: 1,
177 | },
178 | buttonText: {
179 | color: '#FDFDFD',
180 | fontFamily: 'InterSemi',
181 | fontSize: 16,
182 |
183 | padding: 15,
184 | paddingHorizontal: 25,
185 | },
186 |
187 | // steps
188 | stepIndicatorContainer: {
189 | flexDirection: 'row',
190 | gap: 8,
191 | marginHorizontal: 15,
192 | },
193 | stepIndicator: {
194 | flex: 1,
195 | height: 3,
196 | backgroundColor: 'gray',
197 | borderRadius: 10,
198 | },
199 | });
200 |
--------------------------------------------------------------------------------
/src/app/(days)/day21/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 | import AsyncStorage from '@react-native-async-storage/async-storage';
7 |
8 | const description = `
9 | # In-app Subscriptions
10 | In-app Subscriptions with RevenueCat`;
11 |
12 | const DayDetailsScreen = () => {
13 | return (
14 |
15 |
16 |
17 | {description}
18 |
19 |
20 |
21 |
22 |
23 | );
24 | };
25 |
26 | export default DayDetailsScreen;
27 |
--------------------------------------------------------------------------------
/src/app/(days)/day21/paywall.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, Alert, Pressable } from 'react-native';
2 | import React, { useEffect, useState } from 'react';
3 | import Purchases, {
4 | PurchasesOffering,
5 | PurchasesPackage,
6 | } from 'react-native-purchases';
7 | import { Stack } from 'expo-router';
8 | import { SafeAreaView } from 'react-native-safe-area-context';
9 |
10 | const Package = ({ item }: { item: PurchasesPackage }) => {
11 | const onPressed = async () => {
12 | try {
13 | const purchaseMade = await Purchases.purchasePackage(item);
14 | if (
15 | typeof purchaseMade.customerInfo.entitlements.active['Premium'] !==
16 | 'undefined'
17 | ) {
18 | Alert.alert('Huray', 'Welcome to the PRO');
19 | }
20 | } catch (e) {
21 | if (!e.userCancelled) {
22 | console.log(e);
23 | Alert.alert('Ooopsie', 'Something went wrong');
24 | }
25 | }
26 | };
27 |
28 | return (
29 |
30 |
31 | {item.packageType === 'ANNUAL' ? 'Yearly' : ''}
32 | {item.packageType === 'MONTHLY' ? 'Monthly' : ''}
33 |
34 | {item.product.priceString}
35 |
36 | );
37 | };
38 |
39 | const Paywall = () => {
40 | const [offering, setOffering] = useState(null);
41 |
42 | const fetchOfferings = async () => {
43 | try {
44 | const offerings = await Purchases.getOfferings();
45 |
46 | if (offerings.current !== null) {
47 | // Display current offering with offerings.current
48 | setOffering(offerings.current);
49 | }
50 | } catch (e) {}
51 | };
52 |
53 | useEffect(() => {
54 | fetchOfferings();
55 | }, []);
56 |
57 | if (!offering) {
58 | return Failed to fetch the offering;
59 | }
60 |
61 | return (
62 |
63 |
64 |
65 | Unlock PRO Access
66 | All features premium features
67 |
68 |
69 | {offering.availablePackages.map((item) => (
70 |
71 | ))}
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | const styles = StyleSheet.create({
79 | page: {
80 | backgroundColor: '#EF4951',
81 | flex: 1,
82 | },
83 | container: {
84 | alignItems: 'center',
85 | },
86 | title: {
87 | fontSize: 24,
88 | color: 'white',
89 | fontWeight: 'bold',
90 | marginVertical: 20,
91 | },
92 | subtitle: {
93 | fontSize: 16,
94 | color: 'gainsboro',
95 | },
96 | packages: {
97 | flexDirection: 'row',
98 | gap: 10,
99 | marginTop: 50,
100 | },
101 | package: {
102 | backgroundColor: 'white',
103 | padding: 20,
104 | borderRadius: 10,
105 | },
106 | packageDuration: {},
107 | packagePrice: {
108 | fontSize: 24,
109 | fontWeight: 'bold',
110 | },
111 | });
112 |
113 | export default Paywall;
114 |
--------------------------------------------------------------------------------
/src/app/(days)/day21/pro.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, ActivityIndicator } from 'react-native';
2 | import React, { useEffect, useState } from 'react';
3 | import Purchases from 'react-native-purchases';
4 | import { Redirect } from 'expo-router';
5 |
6 | const ProScreen = () => {
7 | // const isSubscribed = false;
8 | const [isSubscribed, setIsSubscribed] = useState(
9 | undefined
10 | );
11 |
12 | useEffect(() => {
13 | checkSubscribtion();
14 | }, []);
15 |
16 | const checkSubscribtion = async () => {
17 | try {
18 | const customerInfo = await Purchases.getCustomerInfo();
19 | if (typeof customerInfo.entitlements.active['Premium'] !== 'undefined') {
20 | setIsSubscribed(true);
21 | } else {
22 | setIsSubscribed(false);
23 | }
24 | } catch (e) {
25 | // Error fetching purchaser info
26 | }
27 | };
28 |
29 | if (isSubscribed === undefined) {
30 | return ;
31 | }
32 |
33 | if (!isSubscribed) {
34 | return ;
35 | }
36 |
37 | return (
38 |
39 | Pro Screen for subscribed users only
40 |
41 | );
42 | };
43 |
44 | export default ProScreen;
45 |
--------------------------------------------------------------------------------
/src/app/(days)/day3/editor.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, TextInput, Pressable } from 'react-native';
2 | import React, { useState } from 'react';
3 | import MarkdownDisplay from '@components/day3/MarkdownDisplay';
4 |
5 | const template = `# 🎉 Fun with Markdown!
6 |
7 | ## 🚀 Introduction
8 | Welcome to this **fun and exciting** markdown guide! Let's dive into the world of Markdown and discover how it makes text formatting cool and easy!
9 |
10 | ## 🎈 Basics: Add Some Flair
11 |
12 | - **Bold and Beautiful:** Make your text stand out! Use \`**\` or \`__\`. Example: **Look at me!**
13 | - *Sassy Italics:* Add a slant with \`*\` or \`_\`. Example: *I'm leaning!*
14 |
15 | ### 🍔 Let's List Some Fun Things!
16 |
17 | 1. 🌟 Star gazing
18 | 2. 🏖 Beach parties
19 | 3. 🍕 Pizza nights
20 |
21 | - 🎮 Video games
22 | - 📚 Reading a good book
23 | - 🧘 Yoga time
24 |
25 | ## 🌈 Advanced Fun
26 |
27 | ### 🖼 Adding Images and Links
28 |
29 | A cute pic:
30 |
31 | 
32 |
33 | Visit a fun site: [Fun Site](https://example.com)
34 |
35 | ### 🎶 Code Block Party
36 |
37 | \`\`\`javascript
38 | // JavaScript party trick
39 | function partyTime() {
40 | console.log("Let's dance 💃🕺!");
41 | }
42 | \`\`\`
43 |
44 | ## 🎤 Conclusion
45 | Markdown is not just for formatting; it's for having fun while expressing yourself! Keep exploring and enjoy the markdown party! 🎊
46 |
47 | > Enjoy crafting your own fun markdown documents! 🎨🎉
48 | `;
49 |
50 | const EditorScreen = () => {
51 | const [content, setContent] = useState(template);
52 | const [tab, setTab] = useState('edit');
53 |
54 | return (
55 |
56 |
57 | setTab('edit')}
59 | style={[
60 | styles.tab,
61 | { borderColor: tab === 'edit' ? 'mediumorchid' : 'gray' },
62 | ]}
63 | >
64 | Edit
65 |
66 | setTab('preview')}
68 | style={[
69 | styles.tab,
70 | { borderColor: tab === 'preview' ? 'mediumorchid' : 'gray' },
71 | ]}
72 | >
73 | Preview
74 |
75 |
76 |
77 | {tab === 'edit' ? (
78 |
85 | ) : (
86 | {content}
87 | )}
88 |
89 | );
90 | };
91 |
92 | const styles = StyleSheet.create({
93 | page: {
94 | backgroundColor: 'whitesmoke',
95 | flex: 1,
96 | padding: 10,
97 | },
98 | input: {
99 | backgroundColor: 'white',
100 | flex: 1,
101 | padding: 20,
102 | paddingTop: 20,
103 | borderRadius: 10,
104 | fontSize: 16,
105 | },
106 |
107 | tabsContainer: {
108 | flexDirection: 'row',
109 | gap: 10,
110 | marginVertical: 10,
111 | },
112 | tab: {
113 | flex: 1,
114 | padding: 10,
115 | borderColor: 'gray',
116 | borderWidth: 2,
117 | borderRadius: 10,
118 | alignItems: 'center',
119 | },
120 | tabText: {
121 | fontFamily: 'InterBold',
122 | },
123 | });
124 |
125 | export default EditorScreen;
126 |
--------------------------------------------------------------------------------
/src/app/(days)/day3/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Markdown
9 |
10 | Integrate Markdown content in **React Native**
11 |
12 | 📚 Today's Agenda:
13 | - Introduction to Markdown
14 | - Markdown Syntax Overview
15 | - Setting Up React Native for Markdown
16 | - Implementing Markdown Rendering
17 | - Styling Markdown Content
18 | - Using Markdown in React Native Components
19 | - Recap and Q&A Session
20 | `;
21 |
22 | const DayDetailsScreen = () => {
23 | return (
24 |
25 |
26 |
27 | {description}
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default DayDetailsScreen;
37 |
--------------------------------------------------------------------------------
/src/app/(days)/day4/animation.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import LottieView from 'lottie-react-native';
3 | import { useRef } from 'react';
4 |
5 | const AnimationScreen = () => {
6 | const animation = useRef(null);
7 |
8 | return (
9 |
10 |
20 |
21 | animation.current?.play()} />
22 | animation.current?.pause()} />
23 | animation.current?.reset()} />
24 |
25 | );
26 | };
27 |
28 | export default AnimationScreen;
29 |
--------------------------------------------------------------------------------
/src/app/(days)/day4/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Animated splash screen
9 | `;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default DayDetailsScreen;
30 |
--------------------------------------------------------------------------------
/src/app/(days)/day4/splash.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import LottieView from 'lottie-react-native';
3 | import { useRef } from 'react';
4 | import { Stack } from 'expo-router';
5 | import AnimatedSplashScreen from '@/components/day4/AnimatedSplashScreen';
6 |
7 | const AnimationScreen = () => {
8 | const animation = useRef(null);
9 |
10 | return ;
11 | };
12 |
13 | export default AnimationScreen;
14 |
--------------------------------------------------------------------------------
/src/app/(days)/day5/airbnb.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, FlatList } from 'react-native';
2 | import React, { useMemo, useState } from 'react';
3 | import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';
4 | import { Stack } from 'expo-router';
5 | import apartments from '@assets/data/day5/appartments.json';
6 | import CustomMarker from '@/components/day5/CustomMarker';
7 | import ApartmentListItem from '@/components/day5/ApartmentListItem';
8 |
9 | import BottomSheet, { BottomSheetFlatList } from '@gorhom/bottom-sheet';
10 |
11 | type Apartment = (typeof apartments)[0];
12 |
13 | export default function AirbnbScreen() {
14 | const [selectedApartment, setSelectedApartment] = useState(
15 | null
16 | );
17 | const [mapRegion, setMapRegion] = useState({
18 | latitude: 37.78825,
19 | longitude: -122.4324,
20 | latitudeDelta: 0.0922,
21 | longitudeDelta: 0.0421,
22 | });
23 |
24 | // variables
25 | const snapPoints = useMemo(() => [80, '50%', '90%'], []);
26 |
27 | return (
28 |
29 |
30 |
31 |
32 | {apartments.map((apartment) => (
33 | setSelectedApartment(apartment)}
37 | />
38 | ))}
39 |
40 |
41 | {/* Display selected Apartment */}
42 | {selectedApartment && (
43 |
47 | )}
48 |
49 |
50 |
51 | Over {apartments.length} places
52 | }
56 | />
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | const styles = StyleSheet.create({
64 | map: {
65 | width: '100%',
66 | height: '100%',
67 | },
68 | listTitle: {
69 | textAlign: 'center',
70 | fontFamily: 'InterSemi',
71 | fontSize: 16,
72 | marginVertical: 5,
73 | marginBottom: 20,
74 | },
75 | selectedContainer: {
76 | position: 'absolute',
77 | bottom: 100,
78 | right: 10,
79 | left: 10,
80 | },
81 | });
82 |
--------------------------------------------------------------------------------
/src/app/(days)/day5/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # AirBNB Maps
9 | `;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day6/TinderScreen.tsx:
--------------------------------------------------------------------------------
1 | import TinderCard from '@/components/day6/TinderCard';
2 | import { Stack } from 'expo-router';
3 | import { View } from 'react-native';
4 | import { useSharedValue } from 'react-native-reanimated';
5 | import { users } from './tinder';
6 |
7 | export const TinderScreen = () => {
8 | const activeIndex = useSharedValue(0);
9 |
10 | return (
11 |
12 |
13 |
14 | {users.map((user, index) => (
15 |
22 | ))}
23 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day6/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Tinder Swipe Animation
9 |
10 | Let's build the Tinder Swipe Animation in React Native using Reanimated
11 | `;
12 |
13 | const DayDetailsScreen = () => {
14 | return (
15 |
16 |
17 |
18 | {description}
19 |
20 |
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default DayDetailsScreen;
28 |
--------------------------------------------------------------------------------
/src/app/(days)/day6/tinder.tsx:
--------------------------------------------------------------------------------
1 | import TinderCard from '@/components/day6/TinderCard';
2 | import { Stack } from 'expo-router';
3 | import { useEffect, useState } from 'react';
4 | import { View, Text, StyleSheet, Button } from 'react-native';
5 | import { GestureDetector, Gesture } from 'react-native-gesture-handler';
6 | import {
7 | interpolate,
8 | useAnimatedReaction,
9 | useDerivedValue,
10 | useSharedValue,
11 | withDecay,
12 | withSpring,
13 | runOnJS,
14 | } from 'react-native-reanimated';
15 |
16 | const dummuUsers = [
17 | {
18 | id: 1,
19 | image:
20 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-images/1.jpg',
21 | name: 'Dani',
22 | },
23 | {
24 | id: 2,
25 | image:
26 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-images/2.jpg',
27 | name: 'Jon',
28 | },
29 | {
30 | id: 3,
31 | image:
32 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-images/3.jpg',
33 | name: 'Dani',
34 | },
35 | {
36 | id: 4,
37 | image:
38 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-images/4.jpeg',
39 | name: 'Alice',
40 | },
41 | {
42 | id: 5,
43 | image:
44 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-images/5.jpg',
45 | name: 'Dani',
46 | },
47 | {
48 | id: 6,
49 | image:
50 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-images/6.jpg',
51 | name: 'Kelsey',
52 | },
53 | ];
54 |
55 | const TinderScreen = () => {
56 | const [users, setUsers] = useState(dummuUsers);
57 | const activeIndex = useSharedValue(0);
58 | const [index, setIndex] = useState(0);
59 |
60 | useAnimatedReaction(
61 | () => activeIndex.value,
62 | (value, prevValue) => {
63 | if (Math.floor(value) !== index) {
64 | runOnJS(setIndex)(Math.floor(value));
65 | }
66 | }
67 | );
68 |
69 | useEffect(() => {
70 | if (index > users.length - 3) {
71 | console.warn('Last 2 cards remining. Fetch more!');
72 | setUsers((usrs) => [...usrs, ...dummuUsers.reverse()]);
73 | }
74 | }, [index]);
75 |
76 | const onResponse = (res: boolean) => {
77 | console.log('on Response: ', res);
78 | };
79 |
80 | return (
81 |
82 |
83 | {/*
84 | Current index: {index}
85 | */}
86 | {users.map((user, index) => (
87 |
95 | ))}
96 |
97 | );
98 | };
99 |
100 | export default TinderScreen;
101 |
--------------------------------------------------------------------------------
/src/app/(days)/day7/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Voice Memos
9 | Work with the Microphone and Audoio playback`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day7/memos.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import {
3 | Text,
4 | View,
5 | StyleSheet,
6 | Button,
7 | FlatList,
8 | Pressable,
9 | } from 'react-native';
10 | import { Audio } from 'expo-av';
11 | import { Recording } from 'expo-av/build/Audio';
12 | import Animated, {
13 | interpolate,
14 | useAnimatedStyle,
15 | useSharedValue,
16 | withSpring,
17 | withTiming,
18 | } from 'react-native-reanimated';
19 | import MemoListItem, { Memo } from '@/components/day7/MemoListItem';
20 |
21 | export default function MemosScreen() {
22 | const [recording, setRecording] = useState();
23 | const [memos, setMemos] = useState([]);
24 |
25 | const [audioMetering, setAudioMetering] = useState([]);
26 | const metering = useSharedValue(-100);
27 |
28 | async function startRecording() {
29 | try {
30 | setAudioMetering([]);
31 |
32 | await Audio.requestPermissionsAsync();
33 | await Audio.setAudioModeAsync({
34 | allowsRecordingIOS: true,
35 | playsInSilentModeIOS: true,
36 | });
37 |
38 | const { recording } = await Audio.Recording.createAsync(
39 | Audio.RecordingOptionsPresets.HIGH_QUALITY,
40 | undefined,
41 | 100
42 | );
43 | setRecording(recording);
44 |
45 | recording.setOnRecordingStatusUpdate((status) => {
46 | if (status.metering) {
47 | metering.value = status.metering;
48 | setAudioMetering((curVal) => [...curVal, status.metering || -100]);
49 | }
50 | });
51 | } catch (err) {
52 | console.error('Failed to start recording', err);
53 | }
54 | }
55 |
56 | async function stopRecording() {
57 | if (!recording) {
58 | return;
59 | }
60 |
61 | console.log('Stopping recording..');
62 | setRecording(undefined);
63 | await recording.stopAndUnloadAsync();
64 | await Audio.setAudioModeAsync({
65 | allowsRecordingIOS: false,
66 | });
67 | const uri = recording.getURI();
68 | console.log('Recording stopped and stored at', uri);
69 | metering.value = -100;
70 | if (uri) {
71 | setMemos((existingMemos) => [
72 | { uri, metering: audioMetering },
73 | ...existingMemos,
74 | ]);
75 | }
76 | }
77 |
78 | const animatedRedCircle = useAnimatedStyle(() => ({
79 | width: withTiming(recording ? '60%' : '100%'),
80 | borderRadius: withTiming(recording ? 5 : 35),
81 | }));
82 |
83 | const animatedRecordWave = useAnimatedStyle(() => {
84 | const size = withTiming(
85 | interpolate(metering.value, [-160, -60, 0], [0, 0, -30]),
86 | { duration: 100 }
87 | );
88 | return {
89 | top: size,
90 | bottom: size,
91 | left: size,
92 | right: size,
93 | backgroundColor: `rgba(255, 45, 0, ${interpolate(
94 | metering.value,
95 | [-160, -60, -10],
96 | [0.7, 0.3, 0.7]
97 | )})`,
98 | };
99 | });
100 |
101 | return (
102 |
103 | }
106 | />
107 |
108 |
109 |
110 |
111 |
115 |
116 |
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | const styles = StyleSheet.create({
124 | container: {
125 | flex: 1,
126 | justifyContent: 'center',
127 | backgroundColor: '#ecf0f1',
128 | },
129 | footer: {
130 | backgroundColor: 'white',
131 | height: 150,
132 | alignItems: 'center',
133 | justifyContent: 'center',
134 | },
135 | recordButton: {
136 | width: 70,
137 | height: 70,
138 | borderRadius: 35,
139 |
140 | borderWidth: 3,
141 | borderColor: 'gray',
142 | padding: 3,
143 |
144 | alignItems: 'center',
145 | justifyContent: 'center',
146 | backgroundColor: 'white',
147 | },
148 | recordWave: {
149 | position: 'absolute',
150 | top: -20,
151 | bottom: -20,
152 | left: -20,
153 | right: -20,
154 | borderRadius: 1000,
155 | },
156 |
157 | redCircle: {
158 | backgroundColor: 'orangered',
159 | aspectRatio: 1,
160 | },
161 | });
162 |
--------------------------------------------------------------------------------
/src/app/(days)/day8/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Weather app
9 | Fetch weather data and display it`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day8/weather.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import {
3 | View,
4 | Text,
5 | ActivityIndicator,
6 | StyleSheet,
7 | ImageBackground,
8 | } from 'react-native';
9 | import * as Location from 'expo-location';
10 | import { FlatList } from 'react-native-gesture-handler';
11 | import ForecastItem from '@/components/day8/ForecastItem';
12 | import { Stack } from 'expo-router';
13 | import LottieView from 'lottie-react-native';
14 | import { StatusBar } from 'expo-status-bar';
15 |
16 | const BASE_URL = `https://api.openweathermap.org/data/2.5`;
17 | const OPEN_WEATHER_KEY = process.env.EXPO_PUBLIC_OPEN_WEATHER_KEY;
18 | const bgImage =
19 | 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/vertical-images/1.jpg';
20 |
21 | type MainWeather = {
22 | temp: number;
23 | feels_like: number;
24 | temp_min: number;
25 | temp_max: number;
26 | pressure: number;
27 | humidity: number;
28 | sea_level: number;
29 | grnd_level: number;
30 | };
31 |
32 | type Weather = {
33 | name: string;
34 | main: MainWeather;
35 | weather: [
36 | {
37 | id: string;
38 | main: string;
39 | description: string;
40 | icon: string;
41 | }
42 | ];
43 | };
44 |
45 | export type WeatherForecast = {
46 | main: MainWeather;
47 | dt: number;
48 | };
49 |
50 | const WeatherScreen = () => {
51 | const [location, setLocation] = useState();
52 | const [errorMsg, setErrorMsg] = useState('');
53 | const [weather, setWeather] = useState();
54 | const [forecast, setForecast] = useState();
55 |
56 | useEffect(() => {
57 | if (location) {
58 | fetchWeather();
59 | fetchForecast();
60 | }
61 | }, [location]);
62 |
63 | useEffect(() => {
64 | (async () => {
65 | let { status } = await Location.requestForegroundPermissionsAsync();
66 | if (status !== 'granted') {
67 | setErrorMsg('Permission to access location was denied');
68 | return;
69 | }
70 |
71 | let location = await Location.getCurrentPositionAsync({});
72 | setLocation(location);
73 | })();
74 | }, []);
75 |
76 | const fetchWeather = async () => {
77 | if (!location) {
78 | return;
79 | }
80 |
81 | const results = await fetch(
82 | `${BASE_URL}/weather?lat=${location.coords.latitude}&lon=${location.coords.longitude}&appid=${OPEN_WEATHER_KEY}&units=metric`
83 | );
84 | const data = await results.json();
85 | // console.log(JSON.stringify(data, null, 2));
86 | setWeather(data);
87 | };
88 |
89 | const fetchForecast = async () => {
90 | // api.openweathermap.org/data/2.5/forecast?lat=44.34&lon=10.99&appid={API key}
91 | if (!location) {
92 | return;
93 | }
94 |
95 | const results = await fetch(
96 | `${BASE_URL}/forecast?lat=${location.coords.latitude}&lon=${location.coords.longitude}&appid=${OPEN_WEATHER_KEY}&units=metric`
97 | );
98 | const data = await results.json();
99 | // console.log(JSON.stringify(data, null, 2));
100 | setForecast(data.list);
101 | };
102 |
103 | if (!weather) {
104 | return ;
105 | }
106 |
107 | return (
108 |
109 |
115 |
116 |
117 |
118 |
119 |
132 | {weather.name}
133 | {Math.round(weather.main.temp)}°
134 | {weather.weather[0].main}
135 |
136 |
137 | }
151 | />
152 |
153 |
154 |
155 | );
156 | };
157 |
158 | const styles = StyleSheet.create({
159 | container: {
160 | flex: 1,
161 | backgroundColor: 'white',
162 | alignItems: 'center',
163 | },
164 | location: {
165 | fontFamily: 'Inter',
166 | fontSize: 30,
167 | color: 'lightgray',
168 | },
169 | temp: {
170 | fontFamily: 'InterBlack',
171 | fontSize: 150,
172 | color: '#FEFEFE',
173 | },
174 | });
175 |
176 | export default WeatherScreen;
177 |
--------------------------------------------------------------------------------
/src/app/(days)/day9/auth/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Redirect, Slot } from 'expo-router';
2 | import { useAuthenticator } from '@aws-amplify/ui-react-native';
3 |
4 | export default function AuthLayout() {
5 | const { authStatus } = useAuthenticator((context) => [context.authStatus]);
6 |
7 | if (authStatus === 'authenticated') {
8 | return ;
9 | }
10 |
11 | return ;
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/(days)/day9/auth/sign-in.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { View, Text, StyleSheet, TextInput, Button } from 'react-native';
3 | import { signIn } from 'aws-amplify/auth';
4 | import { Link, router } from 'expo-router';
5 |
6 | const SignIn = () => {
7 | const [email, setEmail] = useState('');
8 | const [password, setPassword] = useState('');
9 | const [error, setError] = useState('');
10 |
11 | const onSignInPressed = async () => {
12 | setError('');
13 | try {
14 | const { isSignedIn, nextStep } = await signIn({
15 | username: email,
16 | password,
17 | });
18 | if (isSignedIn) {
19 | router.push('/(days)/day9/protected');
20 | } else {
21 | setError('Something went wrong! ' + nextStep.signInStep);
22 | }
23 | } catch (e) {
24 | setError(e.message);
25 | }
26 | };
27 |
28 | return (
29 |
30 | Sign in
31 |
32 |
38 |
44 |
45 |
46 | {error && {error}}
47 | New here? Sign up
48 |
49 | );
50 | };
51 |
52 | const styles = StyleSheet.create({
53 | container: {
54 | padding: 10,
55 | justifyContent: 'center',
56 | flex: 1,
57 | },
58 | title: {
59 | fontFamily: 'InterSemi',
60 | fontSize: 24,
61 | color: 'dimgray',
62 | },
63 | input: {
64 | borderWidth: 1,
65 | borderColor: 'gainsboro',
66 | padding: 10,
67 | marginVertical: 10,
68 | backgroundColor: 'white',
69 | borderRadius: 5,
70 | },
71 | });
72 |
73 | export default SignIn;
74 |
--------------------------------------------------------------------------------
/src/app/(days)/day9/auth/sign-up.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { View, Text, StyleSheet, TextInput, Button } from 'react-native';
3 | import { signUp } from 'aws-amplify/auth';
4 | import { Link, router } from 'expo-router';
5 |
6 | const SignUp = () => {
7 | const [email, setEmail] = useState('');
8 | const [password, setPassword] = useState('');
9 | const [error, setError] = useState('');
10 |
11 | const onSignUpPressed = async () => {
12 | setError('');
13 | try {
14 | await signUp({
15 | username: email,
16 | password,
17 | options: {
18 | userAttributes: {},
19 | autoSignIn: true,
20 | },
21 | });
22 | } catch (e) {
23 | setError(e.message);
24 | }
25 | };
26 |
27 | return (
28 |
29 | Create an account
30 |
31 |
37 |
43 |
44 |
45 | {error && {error}}
46 | Have an account? Sign in
47 |
48 | );
49 | };
50 |
51 | const styles = StyleSheet.create({
52 | container: {
53 | padding: 10,
54 | justifyContent: 'center',
55 | flex: 1,
56 | },
57 | title: {
58 | fontFamily: 'InterSemi',
59 | fontSize: 24,
60 | color: 'dimgray',
61 | },
62 | input: {
63 | borderWidth: 1,
64 | borderColor: 'gainsboro',
65 | padding: 10,
66 | marginVertical: 10,
67 | backgroundColor: 'white',
68 | borderRadius: 5,
69 | },
70 | });
71 |
72 | export default SignUp;
73 |
--------------------------------------------------------------------------------
/src/app/(days)/day9/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { Link, Stack } from 'expo-router';
4 | import MarkdownDisplay from '@/components/day3/MarkdownDisplay';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 |
7 | const description = `
8 | # Authentication
9 | AWS Amplify v6 Authentication`;
10 |
11 | const DayDetailsScreen = () => {
12 | return (
13 |
14 |
15 |
16 | {description}
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default DayDetailsScreen;
26 |
--------------------------------------------------------------------------------
/src/app/(days)/day9/protected/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Redirect, Slot } from 'expo-router';
2 | import {
3 | withAuthenticator,
4 | useAuthenticator,
5 | } from '@aws-amplify/ui-react-native';
6 |
7 | function ProtectedLayout() {
8 | const { authStatus } = useAuthenticator((context) => [context.authStatus]);
9 |
10 | if (authStatus !== 'authenticated') {
11 | return ;
12 | }
13 |
14 | return ;
15 | }
16 |
17 | // export default withAuthenticator(ProtectedLayout);
18 | export default ProtectedLayout;
19 |
--------------------------------------------------------------------------------
/src/app/(days)/day9/protected/index.tsx:
--------------------------------------------------------------------------------
1 | import { View, Text, Button } from 'react-native';
2 | import React from 'react';
3 | import { useAuthenticator } from '@aws-amplify/ui-react-native';
4 |
5 | const ProtectedScreen = () => {
6 | const { signOut } = useAuthenticator();
7 |
8 | return (
9 |
10 | Hello there
11 |
12 | You should see this page only if you are Authenticated
13 |
14 |
15 | signOut()} />
16 |
17 | );
18 | };
19 |
20 | export default ProtectedScreen;
21 |
--------------------------------------------------------------------------------
/src/app/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from 'expo-router';
2 | import { useEffect, useState } from 'react';
3 | import { GestureHandlerRootView } from 'react-native-gesture-handler';
4 | import { Platform } from 'react-native';
5 |
6 | import {
7 | useFonts,
8 | Inter_900Black,
9 | Inter_600SemiBold,
10 | Inter_700Bold,
11 | Inter_400Regular,
12 | } from '@expo-google-fonts/inter';
13 | import {
14 | AmaticSC_400Regular,
15 | AmaticSC_700Bold,
16 | } from '@expo-google-fonts/amatic-sc';
17 |
18 | import * as SplashScreen from 'expo-splash-screen';
19 | import AnimatedSplashScreen from '@/components/day4/AnimatedSplashScreen';
20 | import Animated, { FadeIn } from 'react-native-reanimated';
21 | import { ThemeProvider, Theme } from '@aws-amplify/ui-react-native';
22 |
23 | import { Amplify } from 'aws-amplify';
24 | import { Authenticator } from '@aws-amplify/ui-react-native';
25 | import ampplifyconfig from '@/amplifyconfiguration.json';
26 | import BiometricProvider from '@/components/day10/BiometricsProvider';
27 |
28 | import Purchases from 'react-native-purchases';
29 | const REVENUECAT_IOS_KEY = process.env.EXPO_PUBLIC_REVENUECAT_IOS_KEY;
30 |
31 | import { vexo } from 'vexo-analytics';
32 | vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY || '');
33 |
34 | Amplify.configure(ampplifyconfig);
35 |
36 | const theme: Theme = {
37 | tokens: {
38 | colors: {
39 | brand: {
40 | primary: 'red',
41 | },
42 |
43 | background: {
44 | primary: '{colors.gray}',
45 | },
46 | font: {
47 | primary: 'black',
48 | },
49 | },
50 | },
51 | };
52 |
53 | // SplashScreen.preventAutoHideAsync();
54 |
55 | export default function RootLayout() {
56 | const [appReady, setAppReady] = useState(false);
57 | const [splashAnimationFinished, setSplashAnimationFinished] = useState(false);
58 |
59 | const [fontsLoaded, fontError] = useFonts({
60 | Inter: Inter_400Regular,
61 | InterSemi: Inter_600SemiBold,
62 | InterBold: Inter_700Bold,
63 | InterBlack: Inter_900Black,
64 |
65 | Amatic: AmaticSC_400Regular,
66 | AmaticBold: AmaticSC_700Bold,
67 | });
68 |
69 | useEffect(() => {
70 | if (Platform.OS === 'ios' && REVENUECAT_IOS_KEY) {
71 | Purchases.configure({ apiKey: REVENUECAT_IOS_KEY });
72 | }
73 | }, []);
74 |
75 | useEffect(() => {
76 | if (fontsLoaded || fontError) {
77 | // SplashScreen.hideAsync();
78 | setAppReady(true);
79 | }
80 | }, [fontsLoaded, fontError]);
81 |
82 | const showAnimatedSplash = !appReady || !splashAnimationFinished;
83 | if (showAnimatedSplash) {
84 | return (
85 | {
87 | if (!isCancelled) {
88 | setSplashAnimationFinished(true);
89 | }
90 | }}
91 | />
92 | );
93 | }
94 |
95 | return (
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | );
110 | }
111 |
--------------------------------------------------------------------------------
/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import { StatusBar } from 'expo-status-bar';
2 | import { StyleSheet, View, FlatList } from 'react-native';
3 | import DayListItem from '@components/core/DayListItem';
4 |
5 | const days = [...Array(24)].map((val, index) => index + 1);
6 |
7 | export default function HomeScreen() {
8 | return (
9 |
10 | }
16 | />
17 |
18 |
19 |
20 | );
21 | }
22 |
23 | const styles = StyleSheet.create({
24 | container: {
25 | flex: 1,
26 | backgroundColor: '#fff',
27 | },
28 |
29 | content: {
30 | gap: 10,
31 | padding: 10,
32 | },
33 | column: {
34 | gap: 10,
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/src/aws-exports.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.
3 |
4 | const awsmobile = {
5 | "aws_project_region": "us-east-1",
6 | "aws_cognito_identity_pool_id": "us-east-1:65cd4264-c477-488a-ba15-f866af306690",
7 | "aws_cognito_region": "us-east-1",
8 | "aws_user_pools_id": "us-east-1_ZYdrFTIm6",
9 | "aws_user_pools_web_client_id": "oo8r9kc105f82a7utmuqevg3v",
10 | "oauth": {},
11 | "aws_cognito_username_attributes": [
12 | "EMAIL"
13 | ],
14 | "aws_cognito_social_providers": [],
15 | "aws_cognito_signup_attributes": [
16 | "EMAIL"
17 | ],
18 | "aws_cognito_mfa_configuration": "OFF",
19 | "aws_cognito_mfa_types": [
20 | "SMS"
21 | ],
22 | "aws_cognito_password_protection_settings": {
23 | "passwordPolicyMinLength": 8,
24 | "passwordPolicyCharacters": []
25 | },
26 | "aws_cognito_verification_mechanisms": [
27 | "EMAIL"
28 | ]
29 | };
30 |
31 |
32 | export default awsmobile;
33 |
--------------------------------------------------------------------------------
/src/components/core/DayListItem.tsx:
--------------------------------------------------------------------------------
1 | import { Text, View, StyleSheet, Pressable } from 'react-native';
2 | import { Link } from 'expo-router';
3 |
4 | type DayListItem = {
5 | day: number;
6 | };
7 |
8 | export default function DayListItem({ day }: DayListItem) {
9 | return (
10 |
11 |
12 | {day}
13 |
14 |
15 | );
16 | }
17 |
18 | const styles = StyleSheet.create({
19 | box: {
20 | backgroundColor: '#F9EDE3',
21 | flex: 1,
22 | aspectRatio: 1,
23 |
24 | borderWidth: StyleSheet.hairlineWidth,
25 | borderColor: '#9b4521',
26 | borderRadius: 20,
27 |
28 | justifyContent: 'center',
29 | alignItems: 'center',
30 | },
31 | text: {
32 | color: '#9b4521',
33 | fontSize: 75,
34 | fontFamily: 'AmaticBold',
35 | },
36 | });
37 |
--------------------------------------------------------------------------------
/src/components/day10/BiometricsProvider.tsx:
--------------------------------------------------------------------------------
1 | import { PropsWithChildren, createContext, useContext, useState } from 'react';
2 | import * as LocalAuthentication from 'expo-local-authentication';
3 | import { Alert } from 'react-native';
4 |
5 | type BiometricsContext = {
6 | isUnlocked: boolean;
7 | authenticate: () => Promise;
8 | };
9 |
10 | const BiometricsContext = createContext({
11 | isUnlocked: false,
12 | authenticate: async () => {},
13 | });
14 |
15 | const BiometricProvider = ({ children }: PropsWithChildren) => {
16 | const [isUnlocked, setIsUnlocked] = useState(false);
17 |
18 | const authenticate = async () => {
19 | const hasHardware = await LocalAuthentication.hasHardwareAsync();
20 |
21 | if (!hasHardware) {
22 | Alert.alert('Not supported');
23 | return;
24 | }
25 |
26 | const res = await LocalAuthentication.authenticateAsync();
27 | if (res.success) {
28 | setIsUnlocked(true);
29 | }
30 | };
31 |
32 | return (
33 |
34 | {children}
35 |
36 | );
37 | };
38 |
39 | export default BiometricProvider;
40 |
41 | export const useBiometrics = () => useContext(BiometricsContext);
42 |
--------------------------------------------------------------------------------
/src/components/day12/VideoPost.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | StyleSheet,
5 | Pressable,
6 | useWindowDimensions,
7 | } from 'react-native';
8 | import { Video, ResizeMode, AVPlaybackStatus } from 'expo-av';
9 | import { SafeAreaView } from 'react-native-safe-area-context';
10 | import { Ionicons } from '@expo/vector-icons';
11 | import { LinearGradient } from 'expo-linear-gradient';
12 | import { useEffect, useRef, useState } from 'react';
13 |
14 | type VideoPost = {
15 | post: {
16 | id: string;
17 | video: string;
18 | caption: string;
19 | };
20 | activePostId: string;
21 | };
22 |
23 | const VideoPost = ({ post, activePostId }: VideoPost) => {
24 | const video = useRef