diff --git a/App.tsx b/App.tsx
index 25bc1c6..650e714 100644
--- a/App.tsx
+++ b/App.tsx
@@ -28,6 +28,7 @@ import StartStudying from "./src/routes/StartStudying/StartStudying";
import { ToastProvider } from "react-native-toast-notifications";
import InfoIcon from "./src/icons/InfoIcon/InfoIcon";
import CreateGroup from "./src/routes/CreateGroup/CreateGroup";
+import BackgroundComponent from "./src/components/BackgroundTask/BackgroundTask";
const Drawer = createDrawerNavigator();
@@ -66,11 +67,11 @@ export default function App() {
icon={}
textStyle={{ ...styles.text_white_tiny_bold }}
>
+
-
}>
= 4.5.0"
}
},
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
@@ -5774,6 +5806,11 @@
"@babel/core": "^7.0.0"
}
},
+ "node_modules/badgin": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz",
+ "integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw=="
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -6920,6 +6957,19 @@
"node": ">=0.8"
}
},
+ "node_modules/define-data-property": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz",
+ "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.1",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -6928,6 +6978,22 @@
"node": ">=8"
}
},
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/define-property": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
@@ -7434,6 +7500,17 @@
"url-parse": "^1.5.9"
}
},
+ "node_modules/expo-background-fetch": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/expo-background-fetch/-/expo-background-fetch-11.1.1.tgz",
+ "integrity": "sha512-5X63ogpCqEqdqXYk4Sl4J8Lt/ZWcQCboS1pq3uprqD1KUi4ICl1P3gwPo+il3rYPV6xYb74Wv1KyyEEYj2vklg==",
+ "dependencies": {
+ "expo-task-manager": "~11.1.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-constants": {
"version": "14.2.1",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-14.2.1.tgz",
@@ -7644,11 +7721,74 @@
"invariant": "^2.2.4"
}
},
+ "node_modules/expo-notifications": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.18.1.tgz",
+ "integrity": "sha512-lOEiuPE6ubkS5u7Nj/57gkmUGD/MxsRTC6bg9SGJqXIitBQZk3Tmv9y8bjTrn71n7DsrH8K7xCZTbVwr+kLQGg==",
+ "dependencies": {
+ "@expo/image-utils": "^0.3.18",
+ "@ide/backoff": "^1.0.0",
+ "abort-controller": "^3.0.0",
+ "assert": "^2.0.0",
+ "badgin": "^1.1.5",
+ "expo-application": "~5.1.0",
+ "expo-constants": "~14.2.0",
+ "fs-extra": "^9.1.0",
+ "uuid": "^3.4.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
+ "node_modules/expo-notifications/node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/expo-notifications/node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/expo-notifications/node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
"node_modules/expo-status-bar": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-1.4.4.tgz",
"integrity": "sha512-5DV0hIEWgatSC3UgQuAZBoQeaS9CqeWRZ3vzBR9R/+IUD87Adbi4FGhU10nymRqFXOizGsureButGZIXPs7zEA=="
},
+ "node_modules/expo-task-manager": {
+ "version": "11.1.1",
+ "resolved": "https://registry.npmjs.org/expo-task-manager/-/expo-task-manager-11.1.1.tgz",
+ "integrity": "sha512-Ot4wq0fVd8+I1W7MsJz0rNdX0ma/zdnBvAppxDX1Oo0o0exo4qs1FmgrTnh3OBnn18aB4cX3wBJoXIatIgNMZQ==",
+ "dependencies": {
+ "unimodules-app-loader": "~4.1.0"
+ },
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/extend-shallow": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
@@ -7954,6 +8094,14 @@
"resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz",
"integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="
},
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
"node_modules/for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -8186,6 +8334,17 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -8232,6 +8391,17 @@
"node": ">=4"
}
},
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
@@ -8254,6 +8424,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/has-tostringtag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+ "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/has-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
@@ -8586,6 +8770,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-arguments": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
+ "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -8596,6 +8795,17 @@
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-core-module": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
@@ -8680,6 +8890,20 @@
"node": ">=4"
}
},
+ "node_modules/is-generator-function": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
+ "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -8729,6 +8953,21 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-nan": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
+ "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -8788,6 +9027,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-typed-array": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
+ "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
+ "dependencies": {
+ "which-typed-array": "^1.1.11"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
@@ -11123,6 +11376,14 @@
"mkdirp": "bin/cmd.js"
}
},
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/moti": {
"version": "0.25.3",
"resolved": "https://registry.npmjs.org/moti/-/moti-0.25.3.tgz",
@@ -11490,6 +11751,29 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object-is": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
+ "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/object-visit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
@@ -11501,6 +11785,23 @@
"node": ">=0.10.0"
}
},
+ "node_modules/object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/object.pick": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
@@ -14228,6 +14529,11 @@
"node": ">=4"
}
},
+ "node_modules/unimodules-app-loader": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/unimodules-app-loader/-/unimodules-app-loader-4.1.2.tgz",
+ "integrity": "sha512-DLYUCjNpguFhNpxNsBf47NWZi2OjIfWCVQY4f+r4/bwIqFfR7qQd6lXwCFA8EhHS1ti87CBzjMSbIC5bB0J/0Q=="
+ },
"node_modules/union-value": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
@@ -14419,6 +14725,18 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
+ "node_modules/util": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
+ "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "is-arguments": "^1.0.4",
+ "is-generator-function": "^1.0.7",
+ "is-typed-array": "^1.1.3",
+ "which-typed-array": "^1.1.2"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -14523,6 +14841,24 @@
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
},
+ "node_modules/which-typed-array": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
+ "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/wonka": {
"version": "4.0.15",
"resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz",
diff --git a/package.json b/package.json
index df97e93..d742989 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
"expo-linking": "~4.0.1",
"expo-location": "~15.1.1",
"expo-status-bar": "~1.4.4",
+ "moment": "^2.29.4",
"moti": "^0.25.3",
"react": "18.2.0",
"react-native": "0.71.8",
@@ -40,7 +41,10 @@
"react-native-toast-notifications": "^3.3.1",
"react-query": "^3.39.3",
"react-redux": "^8.1.1",
- "redux": "^4.2.1"
+ "redux": "^4.2.1",
+ "expo-task-manager": "~11.1.1",
+ "expo-background-fetch": "~11.1.1",
+ "expo-notifications": "~0.18.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
diff --git a/src/components/Api/Api.tsx b/src/components/Api/Api.tsx
index 0eaad7d..e6111a9 100644
--- a/src/components/Api/Api.tsx
+++ b/src/components/Api/Api.tsx
@@ -4,6 +4,7 @@ import {
ActivationType,
LocationType,
LoginType,
+ MessagePostType,
OnboardingType,
PatchUserInfoType,
RegistrationType,
@@ -311,7 +312,6 @@ export async function GetStudyGroupListFiltered() {
return instance
.get("/api/v1/study_groups/near/", config)
.then((response) => {
- console.log("DEBUGGG", response.data);
return [true, response.data];
})
.catch((error) => {
@@ -346,3 +346,56 @@ export async function CreateStudyGroup(info: StudyGroupCreateType) {
return [false, error_message];
});
}
+
+export async function GetStudyGroup(name: string) {
+ const config = await GetConfig();
+ return instance
+ .get(`/api/v1/study_groups/${name}`, config)
+ .then((response) => {
+ return [true, response.data];
+ })
+ .catch((error) => {
+ let error_message = ParseError(error);
+ return [false, error_message];
+ });
+}
+
+export async function GetStudyGroupMessages() {
+ const config = await GetConfig();
+ return instance
+ .get(`/api/v1/messages/`, config)
+ .then((response) => {
+ return [true, response.data];
+ })
+ .catch((error) => {
+ let error_message = ParseError(error);
+ return [false, error_message];
+ });
+}
+
+export async function GetStudyGroupMemberAvatars() {
+ const config = await GetConfig();
+ return instance
+ .get(`/api/v1/study_groups/member_avatars`, config)
+ .then((response) => {
+ return [true, response.data];
+ })
+ .catch((error) => {
+ let error_message = ParseError(error);
+ return [false, error_message];
+ });
+}
+
+export async function PostMessage(info: MessagePostType) {
+ const config = await GetConfig();
+ return instance
+ .post(`/api/v1/messages/`, info, config)
+ .then((response) => {
+ return [true, response.data];
+ })
+ .catch((error) => {
+ console.log("Error:", error.response.data);
+ let error_message = ParseError(error);
+ return [false, error_message];
+ });
+}
diff --git a/src/components/BackgroundTask/BackgroundTask.tsx b/src/components/BackgroundTask/BackgroundTask.tsx
new file mode 100644
index 0000000..57cb25b
--- /dev/null
+++ b/src/components/BackgroundTask/BackgroundTask.tsx
@@ -0,0 +1,148 @@
+import React, { useEffect } from "react";
+import { View } from "react-native";
+import * as BackgroundFetch from "expo-background-fetch";
+import * as TaskManager from "expo-task-manager";
+import * as Notifications from "expo-notifications";
+import {
+ GetStudentStatus,
+ GetStudyGroupListFiltered,
+ GetStudyGroupMessages,
+} from "../Api/Api";
+import { StudyGroupType } from "../../interfaces/Interfaces";
+import AsyncStorage from "@react-native-async-storage/async-storage";
+
+const FETCH_STUDENT_STATUS = "STUDENT_STATUS_TASK";
+const FETCH_GROUP_MESSAGES = "GROUP_MESSAGES_TASK";
+
+TaskManager.defineTask(FETCH_GROUP_MESSAGES, async () => {
+ const data = await GetStudyGroupMessages();
+ if (data[0] && data[1]) {
+ let messages_prev = await JSON.parse(
+ (await AsyncStorage.getItem("messages")) || "[]"
+ );
+ await AsyncStorage.setItem("messages", JSON.stringify(data[1]));
+ let message_curr = data[1];
+ let difference: Array = messages_prev
+ .filter(
+ (x: any) =>
+ !message_curr.some(
+ (y: any) => JSON.stringify(y) === JSON.stringify(x)
+ )
+ )
+ .concat(
+ message_curr.filter(
+ (x: any) =>
+ !messages_prev.some(
+ (y: any) => JSON.stringify(y) === JSON.stringify(x)
+ )
+ )
+ );
+
+ if (difference.length > 0) {
+ console.log(`${difference.length} unread messages`);
+ Notifications.scheduleNotificationAsync({
+ content: {
+ title: `${difference.length} unread messages`,
+ body: `${difference[0].user}: ${difference[0].message_content}`,
+ },
+ trigger: {
+ seconds: 1,
+ },
+ });
+ }
+ } else {
+ console.log(data[1].response.data);
+ }
+
+ return BackgroundFetch.BackgroundFetchResult.NewData;
+});
+
+TaskManager.defineTask(FETCH_STUDENT_STATUS, async () => {
+ const data = await GetStudyGroupListFiltered();
+ const student_status_data = await GetStudentStatus();
+ if (data[0] && data[1]) {
+ console.log("Fetching nearby study groups...");
+ const entryWithLeastDistance = data[1].reduce(
+ (prev: StudyGroupType, curr: StudyGroupType) => {
+ return prev.distance < curr.distance ? prev : curr;
+ }
+ );
+ // Only display a notification if a student isn't in a study group yet
+ if (
+ student_status_data[1].study_group == null ||
+ student_status_data[1].study_group == ""
+ ) {
+ console.log(
+ "User has no study group yet. Found nearby groups, pushing notification"
+ );
+ Notifications.scheduleNotificationAsync({
+ content: {
+ title: "Students are studying nearby",
+ body: `Nearest study group is ${Math.round(
+ entryWithLeastDistance.distance * 1000
+ )}m away`,
+ },
+ trigger: {
+ seconds: 1,
+ },
+ });
+ }
+ } else {
+ console.log(data[1].response.data);
+ }
+
+ return BackgroundFetch.BackgroundFetchResult.NewData;
+});
+
+const BackgroundComponent = () => {
+ const notification_debug = true;
+ const [Task1_isRegistered, Task1_setIsRegistered] = React.useState(false);
+ const [Task2_isRegistered, Task2_setIsRegistered] = React.useState(false);
+ const [status, setStatus] = React.useState();
+ const checkStatusAsync = async () => {
+ let status = await BackgroundFetch.getStatusAsync();
+ setStatus(status);
+ let Task1_isRegistered = await TaskManager.isTaskRegisteredAsync(
+ FETCH_STUDENT_STATUS
+ );
+ let Task2_isRegistered = await TaskManager.isTaskRegisteredAsync(
+ FETCH_GROUP_MESSAGES
+ );
+ Task1_setIsRegistered(Task1_isRegistered);
+ Task2_setIsRegistered(Task2_isRegistered);
+ };
+
+ useEffect(() => {
+ const registerTasks = async () => {
+ try {
+ await checkStatusAsync();
+ // Nearby students task
+ if (!Task1_isRegistered) {
+ await BackgroundFetch.registerTaskAsync(FETCH_STUDENT_STATUS, {
+ minimumInterval: notification_debug ? 5 : 60 * 3, // Check every 5 seconds in dev & every 3 minutes in production builds
+ });
+ console.log("Task for nearby students check registered");
+ } else {
+ console.log("Task for nearby students check already registered");
+ }
+ // Message Checking Task
+ if (!Task2_isRegistered) {
+ await BackgroundFetch.registerTaskAsync(FETCH_GROUP_MESSAGES, {
+ minimumInterval: notification_debug ? 5 : 30, // Check every 5 seconds in dev & every 30 seconds in production builds
+ });
+ console.log("Task for group messages check registered");
+ } else {
+ console.log("Task for group messages check already registered");
+ }
+ } catch (err) {
+ console.log("Task Register failed:", err);
+ }
+ };
+
+ registerTasks();
+ }, []);
+
+ return ;
+};
+
+export default BackgroundComponent;
diff --git a/src/components/DrawerSettings/CustomDrawerContent.tsx b/src/components/DrawerSettings/CustomDrawerContent.tsx
index cfd622a..1b31ad8 100644
--- a/src/components/DrawerSettings/CustomDrawerContent.tsx
+++ b/src/components/DrawerSettings/CustomDrawerContent.tsx
@@ -27,12 +27,12 @@ import { PatchStudentStatus } from "../Api/Api";
import { useToast } from "react-native-toast-notifications";
export default function CustomDrawerContent(props: {}) {
+ const debug = true;
const navigation = useNavigation();
const status = useSelector((state: RootState) => state.status);
const dispatch = useDispatch();
const queryClient = useQueryClient();
const toast = useToast();
- const debug_disable_clear_on_logout = true;
const stop_studying_logout = useMutation({
mutationFn: async (info: StudentStatusPatchType) => {
const data = await PatchStudentStatus(info);
@@ -78,7 +78,8 @@ export default function CustomDrawerContent(props: {}) {
{
- if (debug_disable_clear_on_logout) {
+ // We don't clear student statuses when logging out on debug
+ if (!debug) {
queryClient.clear();
dispatch(logout());
await AsyncStorage.clear();
@@ -131,9 +132,18 @@ export default function CustomDrawerContent(props: {}) {
Subjects
+ {
+ navigation.navigate("Conversation");
+ }}
+ >
+
+ Conversation
+
{
- if (debug_disable_clear_on_logout) {
+ // We don't clear student statuses when logging out on debug
+ if (!debug) {
queryClient.clear();
dispatch(logout());
await AsyncStorage.clear();
@@ -178,14 +188,6 @@ export default function CustomDrawerContent(props: {}) {
Register
- {
- navigation.navigate("Conversation");
- }}
- >
-
- Conversation
-
{/*
Debug buttons for accessing revalidation and activation page
;
export type StudentStatusListReturnType = [boolean, StudentStatusListType];
-
export type RawLocationType = Location.LocationObject;
export interface UserInfoType {
diff --git a/src/routes/ConversationPage/ConversationPage.tsx b/src/routes/ConversationPage/ConversationPage.tsx
index c8340da..fdba4cf 100644
--- a/src/routes/ConversationPage/ConversationPage.tsx
+++ b/src/routes/ConversationPage/ConversationPage.tsx
@@ -1,179 +1,329 @@
import * as React from "react";
+import { ActivityIndicator, Image } from "react-native";
import styles from "../../styles";
import {
View,
Text,
TextInput,
ScrollView,
- StyleSheet
+ NativeSyntheticEvent,
+ TextInputChangeEventData,
} from "react-native";
import { colors } from "../../styles";
-import { useState } from "react";
-
-const convStyles = StyleSheet.create({
- scrollViewContainer: {
- backgroundColor: colors.secondary_1,
- padding: 15,
- },
- messageContainer: {
- backgroundColor: '#00000038',
- margin: 5,
- padding: 10,
- borderRadius: 20,
- },
- badge: {
- height: 10,
- width: 10,
- borderRadius: 10,
- }
-})
-
-type ConversationType = {
- username: string;
- message: string;
- userId: number;
- type: string;
- color: string;
-}
-
+import { useRef, useState } from "react";
+import { useMutation, useQuery } from "@tanstack/react-query";
+import {
+ GetStudentStatus,
+ GetStudyGroup,
+ GetStudyGroupMemberAvatars,
+ GetStudyGroupMessages,
+ PostMessage,
+} from "../../components/Api/Api";
+import {
+ StudentStatusType,
+ StudentStatusReturnType,
+ StudyGroupType,
+ StudyGroupDetailReturnType,
+ MessageType,
+ MessageReturnType,
+ MessagePostType,
+ GroupMessageAvatarType,
+ GroupMessageAvatarReturnType,
+} from "../../interfaces/Interfaces";
+import { useToast } from "react-native-toast-notifications";
+import { useQueryClient } from "@tanstack/react-query";
+import AnimatedContainer from "../../components/AnimatedContainer/AnimatedContainer";
+import AsyncStorage from "@react-native-async-storage/async-storage";
export default function ConversationPage() {
- const [conversation, setConversation] = useState([{
- username: "You",
- message: "Hello World naa ko diri canteen gutom sh*t.",
- userId: Math.floor(Math.random() * 1000),
- type: "o",
- color: Math.floor(Math.random() * 16777215).toString(16)
- }, {
- username: "User 2",
- message: "Hahahah shor oy.",
- userId: Math.floor(Math.random() * 1000),
- type: "i",
- color: Math.floor(Math.random() * 16777215).toString(16)
- }, {
- username: "User 3",
- message: "AHAHAHHA BOBO!",
- userId: Math.floor(Math.random() * 1000),
- type: "i",
- color: Math.floor(Math.random() * 16777215).toString(16)
- },
- {
- username: "Vale",
- message: "tanga valir! bobo!",
- userId: Math.floor(Math.random() * 1000),
- type: "i",
- color: Math.floor(Math.random() * 16777215).toString(16)
- },
- {
- username: "You",
- message: "Hoyy bobo!!!",
- userId: Math.floor(Math.random() * 1000),
- type: "o",
- color: Math.floor(Math.random() * 16777215).toString(16)
- }, {
- username: "Valir",
- message: "Gago! 1v1 nalng?",
- userId: Math.floor(Math.random() * 1000),
- type: "o",
- color: Math.floor(Math.random() * 16777215).toString(16)
- }, {
- username: "User 2",
- message: "Hello World",
- userId: Math.floor(Math.random() * 1000),
- type: "i",
- color: Math.floor(Math.random() * 16777215).toString(16)
- }, {
- username: "User 3",
- message: "Hello World",
- userId: Math.floor(Math.random() * 1000),
- type: "i",
- color: Math.floor(Math.random() * 16777215).toString(16)
- },
- {
- username: "User 1",
- message: "Hello World",
- userId: Math.floor(Math.random() * 1000),
- type: "o",
- color: Math.floor(Math.random() * 16777215).toString(16)
- },
- {
- username: "User 1",
- message: "Hello World",
- userId: Math.floor(Math.random() * 1000),
- type: "o",
- color: Math.floor(Math.random() * 16777215).toString(16)
- },
- {
- username: "User 1",
- message: "Hello World",
- userId: Math.floor(Math.random() * 1000),
- type: "o",
- color: Math.floor(Math.random() * 16777215).toString(16)
- },
- {
- username: "User 1",
- message: "Hello World",
- userId: Math.floor(Math.random() * 1000),
- type: "o",
- color: Math.floor(Math.random() * 16777215).toString(16)
- }
- ]);
+ const toast = useToast();
+ // Student Status
+ const [student_status, setStudentStatus] = useState();
+ const StudentStatusQuery = useQuery({
+ queryKey: ["user_status"],
+ queryFn: async () => {
+ const data = await GetStudentStatus();
+ if (data[0] == false) {
+ return Promise.reject(new Error(JSON.stringify(data[1])));
+ }
+ return data;
+ },
+ onSuccess: (data: StudentStatusReturnType) => {
+ setStudentStatus(data[1]);
+ },
+ onError: (error: Error) => {
+ toast.show(String(error), {
+ type: "warning",
+ placement: "top",
+ duration: 2000,
+ animationType: "slide-in",
+ });
+ },
+ });
- return (
-
-
-
-
- Group#57605
+ // Study Group Detail
+ const [studygroup, setStudyGroup] = useState();
+ const StudyGroupQuery = useQuery({
+ enabled:
+ student_status?.study_group != "" && student_status?.study_group != null,
+ queryKey: ["study_group"],
+ queryFn: async () => {
+ const data = await GetStudyGroup(student_status?.study_group || "");
+ if (data[0] == false) {
+ return Promise.reject(new Error(JSON.stringify(data[1])));
+ }
+ return data;
+ },
+ onSuccess: (data: StudyGroupDetailReturnType) => {
+ if (data[1]) {
+ setStudyGroup(data[1]);
+ }
+ },
+ onError: (error: Error) => {
+ toast.show(String(error), {
+ type: "warning",
+ placement: "top",
+ duration: 2000,
+ animationType: "slide-in",
+ });
+ },
+ });
+
+ // Study Group Messages
+ const [messages, setMessages] = useState([]);
+ const MessageQuery = useQuery({
+ refetchInterval: 3000,
+ enabled:
+ !StudentStatusQuery.isLoading &&
+ (student_status?.study_group != null ||
+ student_status?.study_group != ""),
+ queryKey: ["study_group_messages"],
+ queryFn: async () => {
+ const data = await GetStudyGroupMessages();
+ if (data[0] == false) {
+ return Promise.reject(new Error(JSON.stringify(data[1])));
+ }
+ return data;
+ },
+ onSuccess: async (data: MessageReturnType) => {
+ if (data[1]) {
+ await AsyncStorage.setItem("messages", JSON.stringify(data[1]));
+ setMessages(data[1]);
+ }
+ },
+ onError: (error: Error) => {
+ toast.show(String(error), {
+ type: "warning",
+ placement: "top",
+ duration: 2000,
+ animationType: "slide-in",
+ });
+ },
+ });
+
+ // Avatar List
+ const [users, setUsers] = useState([]);
+ const AvatarsQuery = useQuery({
+ refetchInterval: 3000,
+ enabled:
+ student_status?.study_group != null ||
+ (student_status?.study_group != "" &&
+ studygroup != null &&
+ studygroup.students != null),
+ queryKey: ["study_group_avatars"],
+ queryFn: async () => {
+ const data = await GetStudyGroupMemberAvatars();
+ if (data[0] == false) {
+ return Promise.reject(new Error(JSON.stringify(data[1])));
+ }
+ return data;
+ },
+ onSuccess: (data: GroupMessageAvatarReturnType) => {
+ if (data[1]) {
+ setUsers(data[1]);
+ }
+ },
+ onError: (error: Error) => {
+ toast.show(String(error), {
+ type: "warning",
+ placement: "top",
+ duration: 2000,
+ animationType: "slide-in",
+ });
+ },
+ });
+ const scrollViewRef = useRef(null);
+ const queryClient = useQueryClient();
+ const [message, setMessage] = useState("");
+ const send_message = useMutation({
+ mutationFn: async (info: MessagePostType) => {
+ const data = await PostMessage(info);
+ if (data[0] != true) {
+ return Promise.reject(new Error());
+ }
+ return data;
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["study_group_messages"] });
+ },
+ onError: (error: Error) => {
+ toast.show(String(error), {
+ type: "warning",
+ placement: "top",
+ duration: 2000,
+ animationType: "slide-in",
+ });
+ },
+ });
+
+ if (
+ !StudyGroupQuery.isLoading &&
+ !AvatarsQuery.isLoading &&
+ !MessageQuery.isLoading &&
+ student_status &&
+ studygroup &&
+ studygroup.students
+ ) {
+ return (
+
+
+
+
+
+ {`Group: ${studygroup?.name ? studygroup.name : ""}`}
+
+
+
+
+ {studygroup.students.length} studying
+
+ {users.map((user: GroupMessageAvatarType, index: number) => {
+ if (index > 6) {
+ return ;
+ }
+ return (
+
+ {user.avatar != null && user.avatar != "" ? (
+
+ ) : (
+
+ )}
+
+ );
+ })}
+
- 3 students
-
-
-
-
-
-
- {
- conversation.map((item: ConversationType) => (
-
+ scrollViewRef.current?.scrollToEnd()}
+ ref={scrollViewRef}
+ >
+ {messages.length > 0 ? (
+ messages.map((message: MessageType, index: number) => {
+ let avatar = "";
+ users.filter((user: GroupMessageAvatarType) => {
+ if (user.username == message.user) {
+ avatar = user.avatar;
+ }
+ });
+ return (
+
+
+ {avatar != null && avatar != "" ? (
+
+ ) : (
+
+ )}
-
- {
- item.type == 'i' ? : null
- }
- {item.username}
- {
- item.type == 'o' ? : null
- }
-
-
- {item.message}
-
-
- ))
- }
-
+
+ {message.user}
+
+
+ {message.timestamp}
+
+
+
+
+ {message.message_content}
+
+
+ );
+ })
+ ) : (
+ There are no messages
+ )}
+
+
+ ): void => {
+ setMessage(e.nativeEvent.text);
+ }}
+ onSubmitEditing={() => {
+ send_message.mutate({
+ message_content: message,
+ });
+ setMessage("");
+ }}
+ />
+
-
-
+ );
+ } else if (!student_status?.study_group) {
+ return (
+
+
+
+ You are not in a study group. Join one to start a conversation!
+
+
+
+ );
+ }
+ return (
+
+
+
+ Loading...
+
+
);
}
diff --git a/src/routes/Home/Home.tsx b/src/routes/Home/Home.tsx
index c1b7ff0..bf002f8 100644
--- a/src/routes/Home/Home.tsx
+++ b/src/routes/Home/Home.tsx
@@ -38,7 +38,7 @@ import CaretUpIcon from "../../icons/CaretUpIcon/CaretUpIcon";
export default function Home() {
// Switch this condition to see the main map when debugging
- const map_debug = true;
+ const map_distance_override = true;
const navigation = useNavigation();
const [location, setLocation] = useState(null);
const [dist, setDist] = useState(null);
@@ -100,7 +100,7 @@ export default function Home() {
let dist = GetDistanceFromUSTP(location.coords);
setDist(dist);
// Deactivate student status if too far away
- if (dist >= 2 && !map_debug)
+ if (dist >= 2 && !map_distance_override)
stop_studying.mutate({
active: false,
});
@@ -121,17 +121,13 @@ export default function Home() {
return data;
},
onSuccess: (data: StudentStatusReturnType) => {
- if (data[1].active !== undefined) {
- setStudying(data[1].active);
- }
- if (data[1].subject !== undefined) {
- setSubject(data[1].subject);
- }
if (data[1].active == true) {
setButtonLabel("Stop Studying");
} else if (data[1].active == false) {
setButtonLabel("Start Studying");
}
+ setSubject(data[1].subject);
+ setStudying(data[1].active);
setStudentStatus(data[1]);
},
onError: (error: Error) => {
@@ -232,7 +228,7 @@ export default function Home() {
useState([]);
// Student Status List
const StudentStatusListQuery = useQuery({
- enabled: studying,
+ enabled: studying && !StudentStatusQuery.isLoading,
queryKey: ["user_status_list"],
queryFn: async () => {
const data = await GetStudentStatusListNear();
@@ -264,7 +260,7 @@ export default function Home() {
useState([]);
// Student Status List Global
const StudentStatusListGlobalQuery = useQuery({
- enabled: !studying,
+ enabled: !studying && !StudentStatusQuery.isLoading,
queryKey: ["user_status_list_global"],
queryFn: async () => {
const data = await GetStudentStatusList();
@@ -295,7 +291,7 @@ export default function Home() {
const [study_groups, setStudyGroups] = useState([]);
// Study Group List
const StudyGroupQuery = useQuery({
- enabled: studying,
+ enabled: studying && !StudentStatusQuery.isLoading,
queryKey: ["study_group_list"],
queryFn: async () => {
const data = await GetStudyGroupListFiltered();
@@ -323,7 +319,7 @@ export default function Home() {
>([]);
// Study Group Global List
const StudyGroupGlobalQuery = useQuery({
- enabled: !studying,
+ enabled: !studying && !StudentStatusQuery.isLoading,
queryKey: ["study_group_list_global"],
queryFn: async () => {
const data = await GetStudyGroupList();
@@ -349,7 +345,7 @@ export default function Home() {
function CustomMap() {
if (dist && location) {
- if (dist <= 2 || map_debug) {
+ if (dist <= 2 || map_distance_override) {
return (
<>
{feedback}
-