diff --git a/assets/platforms/bigbluebutton.svg b/assets/platforms/bigbluebutton.svg
new file mode 100644
index 00000000..9205c1b9
--- /dev/null
+++ b/assets/platforms/bigbluebutton.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/assets/platforms/skype.svg b/assets/platforms/skype.svg
new file mode 100644
index 00000000..7a08ef11
--- /dev/null
+++ b/assets/platforms/skype.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/platforms/zoom.svg b/assets/platforms/zoom.svg
new file mode 100644
index 00000000..72917c2f
--- /dev/null
+++ b/assets/platforms/zoom.svg
@@ -0,0 +1,12 @@
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 2f7d37df..7a093e6a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
"dependencies": {
"@expo/config-plugins": "~7.8.0",
"@expo/vector-icons": "^14.0.0",
- "@gorhom/bottom-sheet": "^5.0.0-alpha.6",
+ "@gorhom/bottom-sheet": "^5.0.0-alpha.7",
"@react-native-async-storage/async-storage": "^1.21.0",
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/material-top-tabs": "^6.6.3",
@@ -27,6 +27,7 @@
"expo-application": "~5.8.3",
"expo-background-fetch": "~11.8.1",
"expo-checkbox": "~2.7.0",
+ "expo-clipboard": "~5.0.1",
"expo-constants": "~15.4.5",
"expo-dev-client": "~3.3.8",
"expo-device": "~5.9.3",
@@ -64,7 +65,7 @@
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-native-tab-view": "^3.5.2",
- "react-native-ui-datepicker": "^1.0.11",
+ "react-native-ui-datepicker": "^2.0.0",
"react-native-web": "~0.19.6",
"react-native-webview": "13.6.4",
"react-redux": "^8.1.3",
@@ -2468,17 +2469,6 @@
"node": ">=4"
}
},
- "node_modules/@expo/cli/node_modules/picomatch": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
- "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/@expo/cli/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@@ -2655,9 +2645,9 @@
}
},
"node_modules/@expo/config-plugins/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -3680,9 +3670,9 @@
"integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="
},
"node_modules/@gorhom/bottom-sheet": {
- "version": "5.0.0-alpha.6",
- "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-5.0.0-alpha.6.tgz",
- "integrity": "sha512-0SQOZwfoyTxxad0Izy7I4s3HP7PGSsRVOhLHLAEbyh/3YHEWKKWkk27mXIVPPZIcEG6PeST8sMkGk7kMLOkxRA==",
+ "version": "5.0.0-alpha.7",
+ "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-5.0.0-alpha.7.tgz",
+ "integrity": "sha512-3Gr97lenMOjhUeExzvo+6zimcxWt5o4dQMWZ+E7b71Huqg9in+v+CrVPHAiR7sjpzjdBXwDhKkxLAH3lFRq2+A==",
"dependencies": {
"@gorhom/portal": "1.0.14",
"invariant": "^2.2.4"
@@ -4591,9 +4581,9 @@
}
},
"node_modules/@react-native-community/cli-doctor/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -5307,9 +5297,9 @@
}
},
"node_modules/@react-native-community/cli-tools/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -5547,9 +5537,9 @@
}
},
"node_modules/@react-native-community/cli/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -5945,11 +5935,11 @@
}
},
"node_modules/@react-navigation/bottom-tabs": {
- "version": "6.5.11",
- "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.11.tgz",
- "integrity": "sha512-CBN/NOdxnMvmjw+AJQI1kltOYaClTZmGec5pQ3ZNTPX86ytbIOylDIITKMfTgHZcIEFQDymx1SHeS++PIL3Szw==",
+ "version": "6.5.12",
+ "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.12.tgz",
+ "integrity": "sha512-8gBHHvgmJSRGfQ5fcFUgDFcXj1MzDzEZJ/llDYvcSb6ZxgN5xVq+4oVkwPMxOM6v+Qm2nKvXiUKuB/YydhzpLw==",
"dependencies": {
- "@react-navigation/elements": "^1.3.21",
+ "@react-navigation/elements": "^1.3.22",
"color": "^4.2.3",
"warn-once": "^0.1.0"
},
@@ -5978,9 +5968,9 @@
}
},
"node_modules/@react-navigation/elements": {
- "version": "1.3.21",
- "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.21.tgz",
- "integrity": "sha512-eyS2C6McNR8ihUoYfc166O1D8VYVh9KIl0UQPI8/ZJVsStlfSTgeEEh+WXge6+7SFPnZ4ewzEJdSAHH+jzcEfg==",
+ "version": "1.3.22",
+ "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.22.tgz",
+ "integrity": "sha512-HYKucs0TwQT8zMvgoZbJsY/3sZfzeP8Dk9IDv4agst3zlA7ReTx4+SROCG6VGC7JKqBCyQykHIwkSwxhapoc+Q==",
"peerDependencies": {
"@react-navigation/native": "^6.0.0",
"react": "*",
@@ -5989,9 +5979,9 @@
}
},
"node_modules/@react-navigation/material-top-tabs": {
- "version": "6.6.5",
- "resolved": "https://registry.npmjs.org/@react-navigation/material-top-tabs/-/material-top-tabs-6.6.5.tgz",
- "integrity": "sha512-ovKc+ltWYJwu3ju5sw1txBTMemlRM85/JceSrkqU++QnL9l0TAPiPxDlO+wJddR1iwi+P6zj5/+QkXR5Ku+trw==",
+ "version": "6.6.6",
+ "resolved": "https://registry.npmjs.org/@react-navigation/material-top-tabs/-/material-top-tabs-6.6.6.tgz",
+ "integrity": "sha512-gLKrLdWcExdBQGojfGxs8rKl4HR4LnYtCz6rLHf+etKo3PiJ73BrzRPF0XJGqFC8VF97tCdwyi10Yi/LNgTlEw==",
"dependencies": {
"color": "^4.2.3",
"warn-once": "^0.1.0"
@@ -6005,9 +5995,9 @@
}
},
"node_modules/@react-navigation/native": {
- "version": "6.1.9",
- "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.9.tgz",
- "integrity": "sha512-AMuJDpwXE7UlfyhIXaUCCynXmv69Kb8NzKgKJO7v0k0L+u6xUTbt6xvshmJ79vsvaFyaEH9Jg5FMzek5/S5qNw==",
+ "version": "6.1.10",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.10.tgz",
+ "integrity": "sha512-jDG89TbZItY7W7rIcS1RqT63vWOPD4XuQLNKqZO0DY7mKnKh/CGBd0eg3nDMXUl143Qp//IxJKe2TfBQRDEU4A==",
"dependencies": {
"@react-navigation/core": "^6.4.10",
"escape-string-regexp": "^4.0.0",
@@ -6020,11 +6010,11 @@
}
},
"node_modules/@react-navigation/native-stack": {
- "version": "6.9.17",
- "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-6.9.17.tgz",
- "integrity": "sha512-X8p8aS7JptQq7uZZNFEvfEcPf6tlK4PyVwYDdryRbG98B4bh2wFQYMThxvqa+FGEN7USEuHdv2mF0GhFKfX0ew==",
+ "version": "6.9.18",
+ "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-6.9.18.tgz",
+ "integrity": "sha512-PSe0qjROy8zD78ehW048NSuzWRktioSCJmB8LzWSR65ndgVaC2rO+xvgyjhHjqm01YdyVM1XTct2EorSjDV2Ow==",
"dependencies": {
- "@react-navigation/elements": "^1.3.21",
+ "@react-navigation/elements": "^1.3.22",
"warn-once": "^0.1.0"
},
"peerDependencies": {
@@ -6044,11 +6034,11 @@
}
},
"node_modules/@react-navigation/stack": {
- "version": "6.3.20",
- "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.3.20.tgz",
- "integrity": "sha512-vE6mgZzOgoa5Uy7ayT97Cj+ZIK7DK+JBYVuKUViILlWZy6IWK7HFDuqoChSbZ1ajTIfAxj/acVGg1jkbAKsToA==",
+ "version": "6.3.21",
+ "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.3.21.tgz",
+ "integrity": "sha512-oh059bD9w6Q7YbcK3POXRHK+bfoafPU9gvunD0MHJGmPVe9bazn5OMNzRYextvB6BfwQy+v3dy76Opf0vIGcNg==",
"dependencies": {
- "@react-navigation/elements": "^1.3.21",
+ "@react-navigation/elements": "^1.3.22",
"color": "^4.2.3",
"warn-once": "^0.1.0"
},
@@ -6149,6 +6139,99 @@
"@sentry/cli-win32-x64": "2.25.2"
}
},
+ "node_modules/@sentry/cli-darwin": {
+ "version": "2.25.2",
+ "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.25.2.tgz",
+ "integrity": "sha512-o1d5NnVUrc1dxDm56k7Co8tSTcOuxbApdxweVXXsiq20HblZCyIi7WxxRkAg4RfKx594sKGiw9uCVvECi+9UpA==",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@sentry/cli-linux-arm": {
+ "version": "2.25.2",
+ "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.25.2.tgz",
+ "integrity": "sha512-n398jd87Ymejt5k/6RjCEjXAvntOWuqhBDANxzhgr5/9FzbODJ844g1mOpcxiIlduzKSzWlPbTEKQulMp2Mt4w==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux",
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@sentry/cli-linux-arm64": {
+ "version": "2.25.2",
+ "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.25.2.tgz",
+ "integrity": "sha512-lm5jaigV6xu9Gwo0wNk+bX6yVkl5k3gNXcSXcKCISFo+Teb7Zhf9IyXANPm4VY2DdiZAjPJt8gS1bu+Mn7irtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux",
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@sentry/cli-linux-i686": {
+ "version": "2.25.2",
+ "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.25.2.tgz",
+ "integrity": "sha512-/YYx2gfqO5mkxyBgFcnDbZzkZ2+2xNarwrqWcqq3Qw0XlO9DWAQB2G+twV1RW/UfSU6fFIWErn94efh2EWmyzQ==",
+ "cpu": [
+ "x86",
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "linux",
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@sentry/cli-linux-x64": {
+ "version": "2.25.2",
+ "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.25.2.tgz",
+ "integrity": "sha512-rRafqy84R5mYA4JEfNsUeN10af5euJnK7fgqYM0mJIaplHC2YEXT9aUYWoryWPZiYqmdNUhsA6lX7iynSW9pZw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux",
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@sentry/cli-win32-i686": {
+ "version": "2.25.2",
+ "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.25.2.tgz",
+ "integrity": "sha512-plT/gi41F+67g9AwrEm4avRXnjCtHCcnRnJ6zPu/iINGap8mvYQJSU/qM0oGwV6hRGg3JJN66XIvJPIuIs8P8w==",
+ "cpu": [
+ "x86",
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@sentry/cli-win32-x64": {
"version": "2.25.2",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.25.2.tgz",
@@ -6485,9 +6568,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.11.16",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
- "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
+ "version": "20.11.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz",
+ "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==",
"dependencies": {
"undici-types": "~5.26.4"
}
@@ -6498,9 +6581,9 @@
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng=="
},
"node_modules/@types/react": {
- "version": "18.2.52",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.52.tgz",
- "integrity": "sha512-E/YjWh3tH+qsLKaUzgpZb5AY0ChVa+ZJzF7ogehVILrFpdQk6nC/WXOv0bfFEABbXbgNxLBGU7IIZByPKb6eBw==",
+ "version": "18.2.55",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz",
+ "integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -6564,16 +6647,16 @@
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.20.0.tgz",
- "integrity": "sha512-fTwGQUnjhoYHeSF6m5pWNkzmDDdsKELYrOBxhjMrofPqCkoC2k3B2wvGHFxa1CTIqkEn88nlW1HVMztjo2K8Hg==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
+ "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.5.1",
- "@typescript-eslint/scope-manager": "6.20.0",
- "@typescript-eslint/type-utils": "6.20.0",
- "@typescript-eslint/utils": "6.20.0",
- "@typescript-eslint/visitor-keys": "6.20.0",
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/type-utils": "6.21.0",
+ "@typescript-eslint/utils": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.2.4",
@@ -6611,9 +6694,9 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -6632,15 +6715,15 @@
"dev": true
},
"node_modules/@typescript-eslint/parser": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.20.0.tgz",
- "integrity": "sha512-bYerPDF/H5v6V76MdMYhjwmwgMA+jlPVqjSDq2cRqMi8bP5sR3Z+RLOiOMad3nsnmDVmn2gAFCyNgh/dIrfP/w==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
+ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "6.20.0",
- "@typescript-eslint/types": "6.20.0",
- "@typescript-eslint/typescript-estree": "6.20.0",
- "@typescript-eslint/visitor-keys": "6.20.0",
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4"
},
"engines": {
@@ -6660,13 +6743,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.20.0.tgz",
- "integrity": "sha512-p4rvHQRDTI1tGGMDFQm+GtxP1ZHyAh64WANVoyEcNMpaTFn3ox/3CcgtIlELnRfKzSs/DwYlDccJEtr3O6qBvA==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
+ "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.20.0",
- "@typescript-eslint/visitor-keys": "6.20.0"
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -6677,13 +6760,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.20.0.tgz",
- "integrity": "sha512-qnSobiJQb1F5JjN0YDRPHruQTrX7ICsmltXhkV536mp4idGAYrIyr47zF/JmkJtEcAVnIz4gUYJ7gOZa6SmN4g==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
+ "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "6.20.0",
- "@typescript-eslint/utils": "6.20.0",
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "@typescript-eslint/utils": "6.21.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.0.1"
},
@@ -6704,9 +6787,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.20.0.tgz",
- "integrity": "sha512-MM9mfZMAhiN4cOEcUOEx+0HmuaW3WBfukBZPCfwSqFnQy0grXYtngKCqpQN339X3RrwtzspWJrpbrupKYUSBXQ==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
+ "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -6717,13 +6800,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.20.0.tgz",
- "integrity": "sha512-RnRya9q5m6YYSpBN7IzKu9FmLcYtErkDkc8/dKv81I9QiLLtVBHrjz+Ev/crAqgMNW2FCsoZF4g2QUylMnJz+g==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
+ "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.20.0",
- "@typescript-eslint/visitor-keys": "6.20.0",
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -6781,9 +6864,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -6802,17 +6885,17 @@
"dev": true
},
"node_modules/@typescript-eslint/utils": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.20.0.tgz",
- "integrity": "sha512-/EKuw+kRu2vAqCoDwDCBtDRU6CTKbUmwwI7SH7AashZ+W+7o8eiyy6V2cdOqN49KsTcASWsC5QeghYuRDTyOOg==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
+ "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
- "@typescript-eslint/scope-manager": "6.20.0",
- "@typescript-eslint/types": "6.20.0",
- "@typescript-eslint/typescript-estree": "6.20.0",
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/typescript-estree": "6.21.0",
"semver": "^7.5.4"
},
"engines": {
@@ -6839,9 +6922,9 @@
}
},
"node_modules/@typescript-eslint/utils/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
@@ -6860,12 +6943,12 @@
"dev": true
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.20.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.20.0.tgz",
- "integrity": "sha512-E8Cp98kRe4gKHjJD4NExXKz/zOJ1A2hhZc+IMVD6i7w4yjIvh6VyuRI0gRtxAsXtoC35uGMaQ9rjI2zJaXDEAw==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
+ "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "6.20.0",
+ "@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -7116,6 +7199,17 @@
"node": ">= 8"
}
},
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/apisauce": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/apisauce/-/apisauce-2.1.6.tgz",
@@ -7204,17 +7298,36 @@
"node": ">=8"
}
},
- "node_modules/array.prototype.findlastindex": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
- "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==",
+ "node_modules/array.prototype.filter": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz",
+ "integrity": "sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.2",
"define-properties": "^1.2.0",
"es-abstract": "^1.22.1",
- "es-shim-unscopables": "^1.0.0",
- "get-intrinsic": "^1.2.1"
+ "es-array-method-boxes-properly": "^1.0.0",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.4.tgz",
+ "integrity": "sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
@@ -7260,30 +7373,31 @@
}
},
"node_modules/array.prototype.tosorted": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz",
- "integrity": "sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==",
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz",
+ "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1",
- "es-shim-unscopables": "^1.0.0",
- "get-intrinsic": "^1.2.1"
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.1.0",
+ "es-shim-unscopables": "^1.0.2"
}
},
"node_modules/arraybuffer.prototype.slice": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz",
- "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz",
+ "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==",
"dev": true,
"dependencies": {
- "array-buffer-byte-length": "^1.0.0",
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1",
- "get-intrinsic": "^1.2.1",
- "is-array-buffer": "^3.0.2",
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.2.1",
+ "get-intrinsic": "^1.2.3",
+ "is-array-buffer": "^3.0.4",
"is-shared-array-buffer": "^1.0.2"
},
"engines": {
@@ -7861,13 +7975,17 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/call-bind": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
- "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
+ "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==",
"dependencies": {
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.1",
- "set-function-length": "^1.1.1"
+ "get-intrinsic": "^1.2.3",
+ "set-function-length": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -7924,9 +8042,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001583",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz",
- "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==",
+ "version": "1.0.30001585",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz",
+ "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==",
"funding": [
{
"type": "opencollective",
@@ -8581,13 +8699,14 @@
}
},
"node_modules/define-data-property": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
- "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz",
+ "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==",
"dependencies": {
- "get-intrinsic": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.2",
"gopd": "^1.0.1",
- "has-property-descriptors": "^1.0.0"
+ "has-property-descriptors": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
@@ -8811,9 +8930,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/electron-to-chromium": {
- "version": "1.4.656",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz",
- "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q=="
+ "version": "1.4.665",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.665.tgz",
+ "integrity": "sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw=="
},
"node_modules/emoji-regex": {
"version": "9.2.2",
@@ -8953,34 +9072,44 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/es-errors": {
+ "node_modules/es-array-method-boxes-properly": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.0.0.tgz",
- "integrity": "sha512-yHV74THqMJUyFKkHyN7hyENcEZM3Dj2a2IrdClY+IT4BFQHkIVwlh8s6uZfjsFydMdNHv0F5mWgAA3ajFbsvVQ==",
+ "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
+ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
+ "dev": true
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-iterator-helpers": {
- "version": "1.0.15",
- "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz",
- "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==",
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.16.tgz",
+ "integrity": "sha512-CREG2A9Vq7bpDRnldhFcMKuKArvkZtsH6Y0DHOHVg49qhf+LD8uEdUM3OkOAICv0EziGtDEnQtqY2/mfBILpFw==",
"dev": true,
"dependencies": {
"asynciterator.prototype": "^1.0.0",
- "call-bind": "^1.0.2",
+ "call-bind": "^1.0.6",
"define-properties": "^1.2.1",
- "es-abstract": "^1.22.1",
- "es-set-tostringtag": "^2.0.1",
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.2",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
"globalthis": "^1.0.3",
- "has-property-descriptors": "^1.0.0",
+ "has-property-descriptors": "^1.0.1",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
- "internal-slot": "^1.0.5",
+ "internal-slot": "^1.0.7",
"iterator.prototype": "^1.1.2",
- "safe-array-concat": "^1.0.1"
+ "safe-array-concat": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
@@ -9024,9 +9153,9 @@
}
},
"node_modules/escalade": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
- "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"engines": {
"node": ">=6"
}
@@ -9742,6 +9871,14 @@
"resolved": "https://registry.npmjs.org/expo-checkbox/-/expo-checkbox-2.7.0.tgz",
"integrity": "sha512-1wYgoOZ9pBg64PPPULbdD7jEVFX354dpJdUZRmkrO/7ybI668qO4r4+9777CUQ+2JoZEEJBBQzN6UiAl0Uodkg=="
},
+ "node_modules/expo-clipboard": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/expo-clipboard/-/expo-clipboard-5.0.1.tgz",
+ "integrity": "sha512-JH853QJPr5W3h87If3aDTnMK+ESSIrwzU2TdfZrqZttVDY2pMIf/w37mVHHNYodXM4ATHXadtOkjKbAa0DWwUg==",
+ "peerDependencies": {
+ "expo": "*"
+ }
+ },
"node_modules/expo-constants": {
"version": "15.4.5",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-15.4.5.tgz",
@@ -9815,9 +9952,9 @@
}
},
"node_modules/expo-dev-launcher/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -9865,9 +10002,9 @@
}
},
"node_modules/expo-dev-menu/node_modules/semver": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
- "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -10325,9 +10462,9 @@
}
},
"node_modules/fastq": {
- "version": "1.17.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz",
- "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==",
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dependencies": {
"reusify": "^1.0.4"
}
@@ -10642,6 +10779,19 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -10694,11 +10844,11 @@
}
},
"node_modules/get-intrinsic": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.3.tgz",
- "integrity": "sha512-JIcZczvcMVE7AUOP+X72bh8HqHBRxFdz5PDHYtNG/lE3yk9b3KZBJlwFcTyPYjg3L4RLLmZJzvjxhaZVapxFrQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
"dependencies": {
- "es-errors": "^1.0.0",
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3",
@@ -10731,13 +10881,14 @@
}
},
"node_modules/get-symbol-description": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
- "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
+ "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.1.1"
+ "call-bind": "^1.0.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4"
},
"engines": {
"node": ">= 0.4"
@@ -10936,9 +11087,9 @@
}
},
"node_modules/hasown": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
- "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
+ "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
"dependencies": {
"function-bind": "^1.1.2"
},
@@ -11224,12 +11375,12 @@
}
},
"node_modules/internal-slot": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
- "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
+ "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
"dev": true,
"dependencies": {
- "get-intrinsic": "^1.2.2",
+ "es-errors": "^1.3.0",
"hasown": "^2.0.0",
"side-channel": "^1.0.4"
},
@@ -12006,6 +12157,17 @@
"node": ">=8"
}
},
+ "node_modules/jest-util/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/jest-util/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -12521,6 +12683,139 @@
"lightningcss-win32-x64-msvc": "1.19.0"
}
},
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz",
+ "integrity": "sha512-wIJmFtYX0rXHsXHSr4+sC5clwblEMji7HHQ4Ub1/CznVRxtCFha6JIt5JZaNf8vQrfdZnBxLLC6R8pC818jXqg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.19.0.tgz",
+ "integrity": "sha512-Lif1wD6P4poaw9c/4Uh2z+gmrWhw/HtXFoeZ3bEsv6Ia4tt8rOJBdkfVaUJ6VXmpKHALve+iTyP2+50xY1wKPw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.19.0.tgz",
+ "integrity": "sha512-P15VXY5682mTXaiDtbnLYQflc8BYb774j2R84FgDLJTN6Qp0ZjWEFyN1SPqyfTj2B2TFjRHRUvQSSZ7qN4Weig==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.19.0.tgz",
+ "integrity": "sha512-zwXRjWqpev8wqO0sv0M1aM1PpjHz6RVIsBcxKszIG83Befuh4yNysjgHVplF9RTU7eozGe3Ts7r6we1+Qkqsww==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.19.0.tgz",
+ "integrity": "sha512-vSCKO7SDnZaFN9zEloKSZM5/kC5gbzUjoJQ43BvUpyTFUX7ACs/mDfl2Eq6fdz2+uWhUh7vf92c4EaaP4udEtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.19.0.tgz",
+ "integrity": "sha512-0AFQKvVzXf9byrXUq9z0anMGLdZJS+XSDqidyijI5njIwj6MdbvX2UZK/c4FfNmeRa2N/8ngTffoIuOUit5eIQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.19.0.tgz",
+ "integrity": "sha512-SJoM8CLPt6ECCgSuWe+g0qo8dqQYVcPiW2s19dxkmSI5+Uu1GIRzyKA0b7QqmEXolA+oSJhQqCmJpzjY4CuZAg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.19.0.tgz",
@@ -13329,6 +13624,17 @@
"node": ">=8.6"
}
},
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
@@ -13815,15 +14121,16 @@
}
},
"node_modules/object.groupby": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz",
- "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.2.tgz",
+ "integrity": "sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1",
- "get-intrinsic": "^1.2.1"
+ "array.prototype.filter": "^1.0.3",
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.0.0"
}
},
"node_modules/object.hasown": {
@@ -14163,11 +14470,11 @@
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz",
+ "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==",
"engines": {
- "node": ">=8.6"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
@@ -14990,17 +15297,18 @@
}
},
"node_modules/react-native-ui-datepicker": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/react-native-ui-datepicker/-/react-native-ui-datepicker-1.0.11.tgz",
- "integrity": "sha512-IZEd5GRq2vSF1ZcoGQXUxyk9dgh+yqJ6PKC3UrEsDKhEFcQp9m7wAqACpHvEXFh/Y7HmDlNXXTJKz7NXeLmFuQ==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/react-native-ui-datepicker/-/react-native-ui-datepicker-2.0.0.tgz",
+ "integrity": "sha512-0H2Y/Wg+2p93poKN4NaGX/5UKQRr709+GDisPn/BDrfKkbPt87h6Uw06Sl+YX20dD+XqlnHp3+9d22NWm8G1hw==",
"dependencies": {
+ "dayjs": "^1.11.10",
+ "lodash": "^4.17.21",
"uninstall": "^0.0.0"
},
"engines": {
"node": ">= 16.0.0"
},
"peerDependencies": {
- "dayjs": "*",
"react": "*",
"react-native": "*"
}
@@ -15284,15 +15592,16 @@
}
},
"node_modules/reflect.getprototypeof": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz",
- "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz",
+ "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.2.0",
- "es-abstract": "^1.22.1",
- "get-intrinsic": "^1.2.1",
+ "call-bind": "^1.0.5",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.22.3",
+ "es-errors": "^1.0.0",
+ "get-intrinsic": "^1.2.3",
"globalthis": "^1.0.3",
"which-builtin-type": "^1.1.3"
},
@@ -15552,13 +15861,13 @@
"optional": true
},
"node_modules/safe-regex-test": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz",
- "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==",
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
+ "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.5",
- "get-intrinsic": "^1.2.2",
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
"is-regex": "^1.1.4"
},
"engines": {
@@ -15947,13 +16256,14 @@
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/set-function-length": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz",
- "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
+ "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
"dependencies": {
- "define-data-property": "^1.1.1",
+ "define-data-property": "^1.1.2",
+ "es-errors": "^1.3.0",
"function-bind": "^1.1.2",
- "get-intrinsic": "^1.2.2",
+ "get-intrinsic": "^1.2.3",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.1"
},
@@ -16032,14 +16342,18 @@
}
},
"node_modules/side-channel": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
- "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
+ "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.0",
- "get-intrinsic": "^1.0.2",
- "object-inspect": "^1.9.0"
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -16778,12 +17092,12 @@
}
},
"node_modules/ts-api-utils": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz",
- "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
+ "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
"dev": true,
"engines": {
- "node": ">=16.13.0"
+ "node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
@@ -16855,14 +17169,14 @@
}
},
"node_modules/typed-array-buffer": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
- "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz",
+ "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==",
"dev": true,
"dependencies": {
- "call-bind": "^1.0.2",
- "get-intrinsic": "^1.2.1",
- "is-typed-array": "^1.1.10"
+ "call-bind": "^1.0.6",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.13"
},
"engines": {
"node": ">= 0.4"
diff --git a/package.json b/package.json
index 571ed27a..edd842c2 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
"dependencies": {
"@expo/config-plugins": "~7.8.0",
"@expo/vector-icons": "^14.0.0",
- "@gorhom/bottom-sheet": "^5.0.0-alpha.6",
+ "@gorhom/bottom-sheet": "^5.0.0-alpha.7",
"@react-native-async-storage/async-storage": "^1.21.0",
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/material-top-tabs": "^6.6.3",
@@ -64,13 +64,14 @@
"react-native-safe-area-context": "4.8.2",
"react-native-screens": "~3.29.0",
"react-native-tab-view": "^3.5.2",
- "react-native-ui-datepicker": "^1.0.11",
+ "react-native-ui-datepicker": "^2.0.0",
"react-native-web": "~0.19.6",
"react-native-webview": "13.6.4",
"react-redux": "^8.1.3",
"sentry-expo": "~7.1.0",
"sp-react-native-in-app-updates": "^1.3.1",
- "uuid": "^9.0.1"
+ "uuid": "^9.0.1",
+ "expo-clipboard": "~5.0.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
diff --git a/src/App.tsx b/src/App.tsx
index c47943ba..609f9e3a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -9,7 +9,8 @@ import { Provider } from 'react-redux';
import StackNavigator from './navigation/StackNavigator';
import setupStore from './redux';
import { loadStorage } from './redux/storageLoader';
-import { defineFetchTask } from './tasks/signs';
+import { defineReminderTask } from './tasks/disciplineTasks';
+import { defineSignsFetchTask } from './tasks/signs';
import { checkUpdate } from './utils/inappUpdate';
import { registerForPushNotificationsAsync, setNotificationHandler } from './utils/notifications';
import { addShortcuts } from './utils/shortcuts';
@@ -19,7 +20,8 @@ SplashScreen.preventAutoHideAsync().catch((e) => e);
const store = setupStore();
store.dispatch(loadStorage());
-defineFetchTask();
+defineSignsFetchTask();
+defineReminderTask();
setNotificationHandler();
addShortcuts();
diff --git a/src/components/AnnouncePopover.tsx b/src/components/AnnouncePopover.tsx
new file mode 100644
index 00000000..5e397db4
--- /dev/null
+++ b/src/components/AnnouncePopover.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import AutoHeightWebView from 'react-native-autoheight-webview';
+import Popover, { PopoverPlacement } from 'react-native-popover-view';
+
+import { useGlobalStyles } from '../hooks';
+import { useAppTheme } from '../hooks/theme';
+import { getStyles } from '../utils/webView';
+import { Button } from './Button';
+
+const AnnouncePopover = ({ data }: { data: string }) => {
+ const globalStyles = useGlobalStyles();
+ const theme = useAppTheme();
+
+ return (
+ (
+
+ )}
+ popoverStyle={{
+ borderRadius: globalStyles.border.borderRadius,
+ backgroundColor: globalStyles.block.backgroundColor,
+ padding: '2%',
+ }}
+ >
+
+
+ );
+};
+
+export default AnnouncePopover;
diff --git a/src/components/BottomSheetModalBackdrop.tsx b/src/components/BottomSheetModalBackdrop.tsx
new file mode 100644
index 00000000..485bae6e
--- /dev/null
+++ b/src/components/BottomSheetModalBackdrop.tsx
@@ -0,0 +1,24 @@
+import { BottomSheetBackdropProps } from '@gorhom/bottom-sheet';
+import React, { useMemo } from 'react';
+import Animated, { Extrapolation, interpolate, useAnimatedStyle } from 'react-native-reanimated';
+
+const BottomSheetModalBackdrop = ({ animatedIndex, style }: BottomSheetBackdropProps) => {
+ const containerAnimatedStyle = useAnimatedStyle(() => ({
+ opacity: interpolate(animatedIndex.value, [-1, 0], [0, 0.3], Extrapolation.CLAMP),
+ }));
+
+ const containerStyle = useMemo(
+ () => [
+ style,
+ {
+ backgroundColor: '#000000',
+ },
+ containerAnimatedStyle,
+ ],
+ [style, containerAnimatedStyle]
+ );
+
+ return ;
+};
+
+export default BottomSheetModalBackdrop;
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index 7e67aa6b..e243da1f 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -1,5 +1,13 @@
import React from 'react';
-import { ActivityIndicator, StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
+import {
+ ActivityIndicator,
+ StyleProp,
+ StyleSheet,
+ Text,
+ TextStyle,
+ TouchableOpacity,
+ View,
+} from 'react-native';
import { useGlobalStyles } from '../hooks';
import { fontSize } from '../utils/texts';
@@ -13,77 +21,79 @@ const defaultStyles = StyleSheet.create({
},
});
-const Button = ({
- text,
- onPress,
- disabled,
- showLoading,
- variant,
- fontStyle,
-}: {
+interface ButtonProps {
text: string;
onPress(): void;
disabled?: boolean;
showLoading?: boolean;
variant: 'primary' | 'secondary' | 'card';
fontStyle?: StyleProp;
-}) => {
- const globalStyles = useGlobalStyles();
+}
- const styles = {
- primary: {
- textColor: globalStyles.textColor.color,
- text: [globalStyles.fontColorForPrimary, { fontWeight: '500' }, fontStyle || fontSize.xlarge],
- view: [
- defaultStyles.container,
- globalStyles.primaryBackgroundColor,
- globalStyles.borderRadius,
- ],
- },
- secondary: {
- textColor: globalStyles.textColor.color,
- text: [
- globalStyles.fontColorForSecondary,
- { fontWeight: '500' },
- fontStyle || fontSize.xlarge,
- ],
- view: [
- defaultStyles.container,
- globalStyles.secondaryBackgroundColor,
- globalStyles.borderRadius,
- ],
- },
- card: {
- textColor: globalStyles.fontColorForBlock.color,
- text: [globalStyles.fontColorForBlock, { fontWeight: '500' }, fontStyle || fontSize.xlarge],
- view: [defaultStyles.container, globalStyles.block, globalStyles.border],
- },
- };
+const Button = React.forwardRef(
+ ({ text, onPress, disabled, showLoading, variant, fontStyle }, ref) => {
+ const globalStyles = useGlobalStyles();
- if (showLoading) {
- return (
-
-
-
- );
- }
+ const styles = {
+ primary: {
+ textColor: globalStyles.textColor.color,
+ text: [
+ globalStyles.fontColorForPrimary,
+ { fontWeight: '500' },
+ fontStyle || fontSize.xlarge,
+ ],
+ view: [
+ defaultStyles.container,
+ globalStyles.primaryBackgroundColor,
+ globalStyles.borderRadius,
+ ],
+ },
+ secondary: {
+ textColor: globalStyles.textColor.color,
+ text: [
+ globalStyles.fontColorForSecondary,
+ { fontWeight: '500' },
+ fontStyle || fontSize.xlarge,
+ ],
+ view: [
+ defaultStyles.container,
+ globalStyles.secondaryBackgroundColor,
+ globalStyles.borderRadius,
+ ],
+ },
+ card: {
+ textColor: globalStyles.fontColorForBlock.color,
+ text: [globalStyles.fontColorForBlock, { fontWeight: '500' }, fontStyle || fontSize.xlarge],
+ view: [defaultStyles.container, globalStyles.block, globalStyles.border],
+ },
+ };
+
+ if (showLoading) {
+ return (
+ }>
+
+
+ );
+ }
+
+ if (disabled) {
+ return (
+ }>
+ {text}
+
+ );
+ }
- if (disabled) {
return (
-
- {text}
-
+ }
+ text={text}
+ onPress={onPress}
+ textStyle={styles[variant].text}
+ viewStyle={styles[variant].view}
+ />
);
}
-
- return (
-
- );
-};
+);
export { Button };
diff --git a/src/components/CardHeaderOut.tsx b/src/components/CardHeaderOut.tsx
index d6610455..754d605d 100644
--- a/src/components/CardHeaderOut.tsx
+++ b/src/components/CardHeaderOut.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
+import { StyleProp, StyleSheet, Text, TextStyle, View, ViewStyle } from 'react-native';
import { useGlobalStyles } from '../hooks';
import { fontSize } from '../utils/texts';
@@ -11,6 +11,7 @@ const styles = StyleSheet.create({
},
cardHeaderText: {
fontWeight: '600',
+ ...fontSize.medium,
},
});
@@ -18,19 +19,19 @@ const CardHeaderOut = ({
topText,
children,
style,
+ topTextStyle,
}: {
topText: string;
children: React.ReactNode;
style?: StyleProp;
+ topTextStyle?: StyleProp;
}) => {
const globalStyles = useGlobalStyles();
return (
<>
-
- {topText}
-
+ {topText}
{children}
>
diff --git a/src/components/ClickableText.tsx b/src/components/ClickableText.tsx
index ee7c2e32..268c162a 100644
--- a/src/components/ClickableText.tsx
+++ b/src/components/ClickableText.tsx
@@ -1,30 +1,58 @@
import React from 'react';
-import { StyleProp, TextStyle, TouchableOpacity, ViewStyle } from 'react-native';
+import {
+ StyleProp,
+ StyleSheet,
+ TextStyle,
+ TouchableOpacity,
+ TouchableOpacityProps,
+ ViewStyle,
+} from 'react-native';
import Text, { TextColorVariant } from './Text';
-interface ClickableTextProps {
+interface ClickableTextProps extends TouchableOpacityProps {
text: string | number;
textStyle?: StyleProp;
viewStyle?: StyleProp;
onPress(): void;
adjustsFontSizeToFit?: boolean;
colorVariant?: TextColorVariant;
+ iconLeft?: React.ReactNode;
+ iconRight?: React.ReactNode;
}
-const ClickableText = ({
- text,
- textStyle,
- viewStyle,
- onPress,
- adjustsFontSizeToFit,
- colorVariant,
-}: ClickableTextProps) => (
-
-
- {text}
-
-
+const ClickableText = React.forwardRef(
+ (
+ {
+ text,
+ textStyle,
+ viewStyle,
+ adjustsFontSizeToFit,
+ colorVariant,
+ iconLeft,
+ iconRight,
+ ...props
+ },
+ ref
+ ) => (
+
+ {iconLeft}
+
+ {text}
+
+ {iconRight}
+
+ )
);
export default ClickableText;
+
+const styles = StyleSheet.create({
+ row: {
+ flexDirection: 'row',
+ },
+});
diff --git a/src/components/FileTextLink.jsx b/src/components/FileTextLink.jsx
index 4fa26481..57d03eec 100644
--- a/src/components/FileTextLink.jsx
+++ b/src/components/FileTextLink.jsx
@@ -3,7 +3,7 @@ import { shareAsync } from 'expo-sharing';
import React from 'react';
import { StyleSheet, Text, ToastAndroid, TouchableOpacity } from 'react-native';
-import { downloadFile, saveFile } from '../utils';
+import { downloadFile, saveFileFromCache } from '../utils';
const defaultStyle = StyleSheet.create({
text: {
@@ -17,7 +17,7 @@ const FileTextLink = ({ src, fileName, style, children }) => {
const fileData = await downloadFile(src, fileName);
try {
- await saveFile(fileData, fileName);
+ await saveFileFromCache(fileData, fileName);
await Notifications.scheduleNotificationAsync({
content: {
title: 'Файл скачан',
diff --git a/src/data/demoClient.ts b/src/data/demoClient.ts
index db4f3d15..c81db611 100644
--- a/src/data/demoClient.ts
+++ b/src/data/demoClient.ts
@@ -16,7 +16,7 @@ import { ISessionPoints } from '../models/sessionPoints';
import { ISessionQuestionnaire, ISessionQuestionnaireLink } from '../models/sessionQuestionnaire';
import { ISessionTeachPlan } from '../models/teachPlan';
import { TeacherType } from '../models/teachers';
-import { ITimeTable, WeekTypes } from '../models/timeTable';
+import { DistancePlatformTypes, ITimeTable, WeekTypes } from '../models/timeTable';
import { StudentInfo } from '../parser/menu';
import bind from '../utils/methodBinder';
import { BaseClient } from './base';
@@ -128,7 +128,10 @@ export default class DemoClient implements BaseClient {
building: '2',
isDistance: false,
subject: 'Математический анализ (лек)',
- teacherId: '0',
+ teacher: {
+ id: '0',
+ name: 'Иванов И.П',
+ },
},
],
},
@@ -143,7 +146,10 @@ export default class DemoClient implements BaseClient {
building: '2',
isDistance: false,
subject: 'Комплексный анализ (лек)',
- teacherId: '0',
+ teacher: {
+ id: '1',
+ name: 'Иванов П.И',
+ },
},
],
},
@@ -158,7 +164,10 @@ export default class DemoClient implements BaseClient {
building: '2',
isDistance: false,
subject: 'Функциональный анализ (лек)',
- teacherId: '0',
+ teacher: {
+ id: '0',
+ name: 'Иванов И.П',
+ },
},
{
audienceText: 'ауд. Дистанционно (on-line корпус)',
@@ -167,7 +176,34 @@ export default class DemoClient implements BaseClient {
building: 'on-line',
isDistance: true,
subject: 'Программный анализ (лек)',
- teacherId: '0',
+ teacher: {
+ id: '0',
+ name: 'Иванов И.П',
+ },
+ },
+ ],
+ },
+ {
+ position: 4,
+ time: '13:30',
+ lessons: [
+ {
+ audienceText: 'ауд. Дистанционно (on-line корпус)',
+ audience: 'Дистанционно',
+ floor: undefined,
+ building: 'on-line',
+ isDistance: true,
+ subject: 'Программный анализ (лек)',
+ teacher: {
+ id: '0',
+ name: 'Иванов И.П',
+ },
+ distancePlatform: {
+ name: 'Платформа BBB',
+ url: 'https://bigbluebutton.org/',
+ type: DistancePlatformTypes.bbb,
+ imageUrl: '',
+ },
},
],
},
@@ -579,7 +615,7 @@ export default class DemoClient implements BaseClient {
photoTitle: 'Фотография загружена 01.01.2000',
},
{
- id: '0',
+ id: '1',
cathedraId: '0',
photo: 'img_peo_pkg.get_d_img',
name: 'Иванов Петр Иванович',
@@ -604,7 +640,7 @@ export default class DemoClient implements BaseClient {
photoTitle: 'Фотография загружена 01.01.2000',
},
{
- id: '0',
+ id: '1',
cathedraId: '0',
photo: 'img_peo_pkg.get_d_img',
name: 'Иванов Петр Иванович',
diff --git a/src/hooks/useBackPress.ts b/src/hooks/useBackPress.ts
new file mode 100644
index 00000000..bc1b4631
--- /dev/null
+++ b/src/hooks/useBackPress.ts
@@ -0,0 +1,16 @@
+import { useNavigation } from '@react-navigation/native';
+import { useEffect } from 'react';
+
+const useBackPress = (callback: () => boolean) => {
+ const navigation = useNavigation();
+ useEffect(() => {
+ navigation.addListener('beforeRemove', (event) => {
+ const shouldPrevent = callback();
+ if (shouldPrevent) {
+ event.preventDefault();
+ }
+ });
+ }, []);
+};
+
+export default useBackPress;
diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts
new file mode 100644
index 00000000..1127feec
--- /dev/null
+++ b/src/hooks/useNotifications.ts
@@ -0,0 +1,17 @@
+import * as Notifications from 'expo-notifications';
+import { useEffect } from 'react';
+
+import { INotificationData } from '../utils/notifications';
+
+const useNotification = (callback: (data: INotificationData) => void) => {
+ const lastNotificationResponse = Notifications.useLastNotificationResponse();
+
+ useEffect(() => {
+ if (!lastNotificationResponse) return;
+
+ const data = lastNotificationResponse.notification.request.content.data as INotificationData;
+ callback(data);
+ }, [lastNotificationResponse]);
+};
+
+export default useNotification;
diff --git a/src/hooks/useTasks.ts b/src/hooks/useTasks.ts
new file mode 100644
index 00000000..d62bf4c0
--- /dev/null
+++ b/src/hooks/useTasks.ts
@@ -0,0 +1,28 @@
+import { useEffect, useState } from 'react';
+
+import { DisciplineStorage, DisciplineTask } from '../models/disciplinesTasks';
+
+const useTasks = ({ filter }: { filter?: (task: DisciplineTask) => boolean } = {}) => {
+ const [tasks, setTasks] = useState([]);
+
+ const setTasksWithFilter = (tasks: DisciplineTask[]) => {
+ if (filter) setTasks(tasks.filter(filter));
+ else setTasks(tasks);
+ };
+
+ useEffect(() => {
+ DisciplineStorage.getTasks().then(setTasksWithFilter);
+ }, []);
+
+ const addTask = (task: DisciplineTask) =>
+ DisciplineStorage.addTask(task).then(setTasksWithFilter);
+
+ const removeTask = (task: DisciplineTask) =>
+ DisciplineStorage.removeTask(task).then(setTasksWithFilter);
+
+ const saveTasks = () => DisciplineStorage.saveTasks();
+
+ return { tasks, addTask, removeTask, saveTasks };
+};
+
+export default useTasks;
diff --git a/src/models/disciplineInfo.ts b/src/models/disciplineInfo.ts
new file mode 100644
index 00000000..c036ec7b
--- /dev/null
+++ b/src/models/disciplineInfo.ts
@@ -0,0 +1,24 @@
+export interface IDisciplineReminder {
+ datetime: string;
+ // Дата и время напоминания в формате ISO
+}
+
+export interface IDisciplineTask {
+ id: number;
+ // Id задания
+ disciplineName: string;
+ // Название дисциплины
+ description: string;
+ // Описание задания
+ datetime: string;
+ // Дата и время пары, к которому было создано задание в формате ISO
+ reminders: IDisciplineReminder[];
+ // Список напоминаний к заданию
+}
+
+export interface IDisciplineInfo {
+ name: string;
+ // Название дисциплины
+ note: string;
+ // Заметка дисциплины
+}
diff --git a/src/models/disciplinesTasks.ts b/src/models/disciplinesTasks.ts
new file mode 100644
index 00000000..a81d4726
--- /dev/null
+++ b/src/models/disciplinesTasks.ts
@@ -0,0 +1,124 @@
+import dayjs from 'dayjs';
+
+import {
+ readDisciplineInfo,
+ readDisciplinesTasks,
+ saveDisciplineInfo,
+ saveDisciplinesTasks,
+} from '../utils/files';
+import { IDisciplineInfo, IDisciplineReminder, IDisciplineTask } from './disciplineInfo';
+
+export class DisciplineReminder {
+ datetime: dayjs.Dayjs;
+
+ constructor(datetime: dayjs.Dayjs) {
+ this.datetime = datetime;
+ }
+
+ toJSON(): IDisciplineReminder {
+ return {
+ datetime: this.datetime.toISOString(),
+ };
+ }
+
+ static fromJSON(data: IDisciplineReminder) {
+ return new DisciplineReminder(dayjs(data.datetime));
+ }
+}
+
+export class DisciplineTask {
+ id: number;
+ disciplineName: string;
+ description: string;
+ datetime: dayjs.Dayjs;
+ reminders: DisciplineReminder[];
+
+ constructor(
+ id: number,
+ disciplineName: string,
+ description: string,
+ datetime: dayjs.Dayjs,
+ reminders: DisciplineReminder[]
+ ) {
+ this.id = id;
+ this.disciplineName = disciplineName;
+ this.description = description;
+ this.datetime = datetime;
+ this.reminders = reminders;
+ }
+
+ toJSON(): IDisciplineTask {
+ return {
+ id: this.id,
+ disciplineName: this.disciplineName,
+ description: this.description,
+ datetime: this.datetime.toISOString(),
+ reminders: this.reminders.map((rem) => rem.toJSON()),
+ };
+ }
+
+ static fromJSON(data: IDisciplineTask) {
+ return new DisciplineTask(
+ data.id,
+ data.disciplineName,
+ data.description,
+ dayjs(data.datetime),
+ data.reminders.map((rem) => DisciplineReminder.fromJSON(rem))
+ );
+ }
+}
+
+export class DisciplineStorage {
+ private static tasks: DisciplineTask[] = [];
+ private static info: IDisciplineInfo[] = [];
+ private static isRead: boolean = false;
+
+ static async read() {
+ if (DisciplineStorage.isRead) return;
+
+ const [tasks, info] = await Promise.all([readDisciplinesTasks(), readDisciplineInfo()]);
+
+ if (tasks) DisciplineStorage.tasks = tasks.map((task) => DisciplineTask.fromJSON(task));
+ if (info) DisciplineStorage.info = info;
+ DisciplineStorage.isRead = true;
+ }
+
+ static async addTask(task: DisciplineTask) {
+ this.tasks.push(task);
+ await this.saveTasks();
+ return this.tasks;
+ }
+
+ static async removeTask(task: DisciplineTask) {
+ this.tasks = this.tasks.filter(($task) => $task.id !== task.id);
+ await this.saveTasks();
+ return this.tasks;
+ }
+
+ static async saveTasks() {
+ return saveDisciplinesTasks(DisciplineStorage.tasks.map((task) => task.toJSON()));
+ }
+
+ static async saveInfo() {
+ return saveDisciplineInfo(DisciplineStorage.info);
+ }
+
+ static async saveAll() {
+ await Promise.all([this.saveTasks(), this.saveInfo()]);
+ }
+
+ static async getTasks(): Promise {
+ await DisciplineStorage.read();
+ return DisciplineStorage.tasks;
+ }
+
+ static async getInfo(): Promise {
+ await DisciplineStorage.read();
+ return DisciplineStorage.info;
+ }
+
+ static getNextTaskId() {
+ if (!this.isRead) return null;
+ return this.tasks.length;
+ }
+}
diff --git a/src/models/student.ts b/src/models/student.ts
index d30bb36f..780da3ed 100644
--- a/src/models/student.ts
+++ b/src/models/student.ts
@@ -4,6 +4,7 @@ export interface StudentData {
educationForm: string;
year: string;
group: string;
+ isLyceum: boolean;
}
export interface StudentState {
diff --git a/src/navigation/StackNavigator.tsx b/src/navigation/StackNavigator.tsx
index 8c8624dd..304d38be 100644
--- a/src/navigation/StackNavigator.tsx
+++ b/src/navigation/StackNavigator.tsx
@@ -1,10 +1,12 @@
+import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { setBackgroundColorAsync as setBackgroundNavigationBarColorAsync } from 'expo-navigation-bar';
import * as QuickActions from 'expo-quick-actions';
import * as SplashScreen from 'expo-splash-screen';
import { setBackgroundColorAsync } from 'expo-system-ui';
-import React, { useEffect } from 'react';
+import React, { useEffect, useMemo } from 'react';
+import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { cache } from '../cache/smartCache';
import Background from '../components/Background';
@@ -12,6 +14,8 @@ import { useAppDispatch, useAppSelector } from '../hooks';
import { useAppTheme } from '../hooks/theme';
import { PageType, changeTheme, setEvents, setInitialPage } from '../redux/reducers/settingsSlice';
import AuthPage from '../screens/auth/Auth';
+import DisciplineInfo from '../screens/disciplineInfo/DisciplineInfo';
+import DisciplinesTasks from '../screens/disciplinesTasks/DisciplinesTasks';
import Intro from '../screens/intro/Intro';
import MessageHistory from '../screens/messages/MessageHistory';
import NewYearThemes from '../screens/newYearThemes/NewYearThemes';
@@ -156,17 +160,41 @@ const StackNavigator = () => {
);
return (
-
-
-
- {component}
-
-
-
+
+
+
+
+
+
+ {component}
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/navigation/TabNavigation.tsx b/src/navigation/TabNavigation.tsx
index ca384b1a..7fb4338c 100644
--- a/src/navigation/TabNavigation.tsx
+++ b/src/navigation/TabNavigation.tsx
@@ -7,17 +7,20 @@ import { cache } from '../cache/smartCache';
import { useClient } from '../data/client';
import { useAppDispatch, useAppSelector, useGlobalStyles } from '../hooks';
import { useAppTheme } from '../hooks/theme';
+import useNotification from '../hooks/useNotifications';
import { RequestType } from '../models/results';
import { setStudentState } from '../redux/reducers/studentSlice';
import Announce from '../screens/announce/Announce';
import Messages from '../screens/messages/Messages';
import AboutSignsDetails from '../screens/signs/AboutSignsDetails';
import TimeTablePage from '../screens/timeTable/TimeTable';
-import { registerFetch } from '../tasks/signs';
+import { registerReminderTask } from '../tasks/disciplineTasks';
+import { registerSignsFetchTask } from '../tasks/signs';
import { AppShortcutItem } from '../utils/shortcuts';
import ServicesStackNavigator from './ServicesStackNavigator';
import SignsTopTabNavigator from './TopTabNavigator';
import { headerParams } from './header';
+import DisciplineTasksButton from './headerButtons/DisciplineTasksButton';
import { BottomTabsParamList, BottomTabsScreenProps } from './types';
const Tab = createBottomTabNavigator();
@@ -40,6 +43,12 @@ const TabNavigator = ({ navigation }: BottomTabsScreenProps) => {
});
}, []);
+ useNotification(async (data) => {
+ if (data.type === 'task-reminder') {
+ navigation.navigate('DisciplineTasks', { taskId: data.data.taskId });
+ }
+ });
+
const loadData = async () => {
if (isDemo || isOfflineMode) {
const result = await client.getStudentInfoData({ requestType: RequestType.forceCache });
@@ -70,9 +79,10 @@ const TabNavigator = ({ navigation }: BottomTabsScreenProps) => {
useEffect(() => {
loadData().then(() => {
if (signNotification && !isDemo && !isOfflineMode) {
- registerFetch();
+ registerSignsFetchTask();
}
});
+ registerReminderTask();
}, []);
return (
@@ -89,7 +99,6 @@ const TabNavigator = ({ navigation }: BottomTabsScreenProps) => {
},
},
},
-
tabBarActiveTintColor: globalStyles.primaryFontColor.color,
tabBarShowLabel: false,
tabBarBadgeStyle: globalStyles.primaryBackgroundColor,
@@ -102,6 +111,7 @@ const TabNavigator = ({ navigation }: BottomTabsScreenProps) => {
options={{
title: 'Расписание',
tabBarIcon: ({ size, color }) => ,
+ headerRight: () => ,
}}
/>
{
+ const navigation = useNavigation();
+ const theme = useAppTheme();
+
+ return (
+ {
+ navigation.navigate('DisciplineTasks');
+ }}
+ style={{ justifyContent: 'center', marginRight: '7%' }}
+ >
+
+
+ );
+};
+
+export default DisciplineTasksButton;
diff --git a/src/navigation/types.ts b/src/navigation/types.ts
index 2b2a77c0..eb297c8c 100644
--- a/src/navigation/types.ts
+++ b/src/navigation/types.ts
@@ -8,14 +8,20 @@ import type { StackNavigationProp, StackScreenProps } from '@react-navigation/st
import { IMessage } from '../models/messages';
import { ISubject } from '../models/sessionPoints';
+import { ILesson } from '../models/timeTable';
export type RootStackParamList = {
+ // Group 1
Onboarding: undefined;
Auth: undefined;
TabNavigator: undefined;
History: { data: IMessage[]; page: number };
SignsDetails: { subject: ISubject };
SessionQuestionnaire: { url: string };
+
+ // Group 2
+ DisciplineInfo: { lesson: ILesson; date: string; pairPosition: number };
+ DisciplineTasks?: { taskId?: number };
NewYearTheme: undefined;
};
@@ -51,7 +57,6 @@ export type SignsTopTabsParamsList = {
export type RootStackScreenProps =
StackScreenProps;
-
export type BottomTabsScreenProps =
CompositeScreenProps, RootStackScreenProps>;
export type ServiceNativeStackScreenProps<
diff --git a/src/parser/menu.ts b/src/parser/menu.ts
index 5480ba57..f6b86248 100644
--- a/src/parser/menu.ts
+++ b/src/parser/menu.ts
@@ -29,6 +29,7 @@ export default function parseMenu(html: string, parseGroupJournal = false): Stud
educationForm: null,
year: null,
group: null,
+ isLyceum: false,
},
};
@@ -56,6 +57,7 @@ export default function parseMenu(html: string, parseGroupJournal = false): Stud
educationForm,
year,
group: null,
+ isLyceum: speciality.startsWith('Лицей') || speciality.endsWith('класс'), // TODO: Убрать лишнее, как только узнаем правду
};
const menu = $('.span3');
@@ -70,6 +72,9 @@ export default function parseMenu(html: string, parseGroupJournal = false): Stud
// Получение группы студента
if (parseGroupJournal) {
data.student.group = content.find('h3').text().split(' ').at(1);
+ if (!data.student.isLyceum) {
+ data.student.isLyceum = data.student.group.startsWith('ЛЦ');
+ }
}
// Получение количества новых уведомлений
diff --git a/src/parser/regex.ts b/src/parser/regex.ts
new file mode 100644
index 00000000..a7c4b347
--- /dev/null
+++ b/src/parser/regex.ts
@@ -0,0 +1,3 @@
+/* https://regex101.com/r/gvUVMt/14 */
+export const disciplineRegex = /(.*)\s\(([а-я]+(?:,\s[а-я]+)*)\)/s;
+export const numberRegex = /(\d+)/s;
diff --git a/src/parser/teachers.ts b/src/parser/teachers.ts
index 157cf866..2acf9e32 100644
--- a/src/parser/teachers.ts
+++ b/src/parser/teachers.ts
@@ -2,12 +2,9 @@ import { load } from 'cheerio';
import { ITeacher, TeacherType } from '../models/teachers';
import { executeRegex } from '../utils/sentry';
+import { disciplineRegex, numberRegex } from './regex';
import { getTextField } from './utils';
-/* https://regex101.com/r/gvUVMt/14 */
-const subjectRegex = /(.*)\s\(([а-я]+(?:,\s[а-я]+)*)\)/s;
-const numberRegex = /(\d+)/s;
-
const groupTeachers = (data: ITeacher[]) => {
const dataGrouped = {};
data.forEach((val) => {
@@ -38,7 +35,7 @@ export default function parseTeachers(html: string): TeacherType {
getTextField(teacherEl.find('.dis'))
.split('\n')
.forEach((subject) => {
- const [, subjectUntyped, subjectType] = executeRegex(subjectRegex, subject);
+ const [, subjectUntyped, subjectType] = executeRegex(disciplineRegex, subject);
data.push({
id,
photo,
diff --git a/src/screens/disciplineInfo/AddReminderBottomModal.tsx b/src/screens/disciplineInfo/AddReminderBottomModal.tsx
new file mode 100644
index 00000000..2bc40402
--- /dev/null
+++ b/src/screens/disciplineInfo/AddReminderBottomModal.tsx
@@ -0,0 +1,114 @@
+import { BottomSheetView } from '@gorhom/bottom-sheet';
+import dayjs from 'dayjs';
+import 'dayjs/locale/ru';
+import React, { useRef, useState } from 'react';
+import { StyleSheet, TextInput, View } from 'react-native';
+import DateTimePicker from 'react-native-ui-datepicker';
+
+import ClickableText from '../../components/ClickableText';
+import Text from '../../components/Text';
+import { useGlobalStyles } from '../../hooks';
+import { useAppTheme } from '../../hooks/theme';
+import { fontSize } from '../../utils/texts';
+
+// У библиотеки react-native-ui-datepicker нереально плохо оптимизирован выбор времени, поэтому используется свой
+const TimePicker = ({
+ value,
+ onValueChange,
+}: {
+ value: dayjs.Dayjs;
+ onValueChange: (value: dayjs.Dayjs) => void;
+}) => {
+ const globalStyles = useGlobalStyles();
+
+ const handleChange = (type: 'hours' | 'minutes') => (text: string) => {
+ const $value = Number(text) || 0;
+ if (type === 'hours' && $value >= 0 && $value < 24) {
+ onValueChange(value.clone().set('hours', $value));
+ } else if (type === 'minutes' && $value >= 0 && $value < 60) {
+ onValueChange(value.clone().set('minutes', $value));
+ }
+ };
+
+ return (
+
+
+ :
+
+
+ );
+};
+
+const AddReminderBottomModal = ({ onSubmit }: { onSubmit: (datetime: dayjs.Dayjs) => void }) => {
+ const theme = useAppTheme();
+
+ const [value, setValue] = useState(dayjs());
+ const minimumDate = useRef(dayjs());
+
+ const handleDayChange = (date: dayjs.Dayjs) => {
+ setValue((prevDate) => date.set('hour', prevDate.hour()).set('minute', prevDate.minute()));
+ };
+
+ return (
+
+ handleDayChange(dayjs(date))}
+ locale={'ru'}
+ minDate={minimumDate.current}
+ firstDayOfWeek={1}
+ mode={'single'}
+ selectedItemColor={theme.colors.primary}
+ calendarTextStyle={{ color: theme.colors.textForBlock }}
+ headerTextStyle={{ color: theme.colors.textForBlock }}
+ headerButtonColor={theme.colors.textForBlock}
+ weekDaysTextStyle={{ color: theme.colors.textForBlock }}
+ yearContainerStyle={{ backgroundColor: theme.colors.block }}
+ />
+ Укажите время
+
+ onSubmit(value)}
+ viewStyle={styles.saveButton}
+ textStyle={styles.text}
+ />
+
+ );
+};
+
+export default AddReminderBottomModal;
+
+const timePickerStyles = StyleSheet.create({
+ container: { flexDirection: 'row' },
+ textInput: {
+ fontSize: 26,
+ paddingVertical: '1%',
+ paddingHorizontal: '2%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+});
+
+const styles = StyleSheet.create({
+ modalView: {
+ marginHorizontal: '2%',
+ },
+ text: {
+ fontWeight: '500',
+ ...fontSize.big,
+ },
+ saveButton: { alignSelf: 'flex-end' },
+});
diff --git a/src/screens/disciplineInfo/AddTaskModalContent.tsx b/src/screens/disciplineInfo/AddTaskModalContent.tsx
new file mode 100644
index 00000000..50c2d3be
--- /dev/null
+++ b/src/screens/disciplineInfo/AddTaskModalContent.tsx
@@ -0,0 +1,167 @@
+import { BottomSheetModal, BottomSheetView } from '@gorhom/bottom-sheet';
+import dayjs from 'dayjs';
+import 'dayjs/locale/ru';
+import React, { useRef, useState } from 'react';
+import { Alert, StyleSheet, TextInput, View } from 'react-native';
+
+import BottomSheetModalBackdrop from '../../components/BottomSheetModalBackdrop';
+import ClickableText from '../../components/ClickableText';
+import Text from '../../components/Text';
+import { useGlobalStyles } from '../../hooks';
+import { DisciplineReminder, DisciplineTask } from '../../models/disciplinesTasks';
+import { formatTime } from '../../utils/datetime';
+import { fontSize } from '../../utils/texts';
+import AddReminderBottomModal from './AddReminderBottomModal';
+import AddButton from './components/AddButton';
+import Reminder from './components/Reminder';
+
+export interface PartialTask {
+ description: string;
+ // Описание задачи
+ reminders: DisciplineReminder[];
+ // Список напоминаний к задаче
+}
+
+const AddTaskModalContent = ({
+ onTaskAdd,
+ onTaskRemove,
+ selectedTask,
+ showDisciplineInfo,
+}: {
+ onTaskAdd?: (task: PartialTask) => void;
+ onTaskRemove: (task: DisciplineTask) => void;
+ selectedTask?: DisciplineTask;
+ showDisciplineInfo?: boolean;
+}) => {
+ const [description, setDescription] = useState(selectedTask?.description || '');
+ const [reminders, setReminders] = useState(selectedTask?.reminders || []);
+ const globalStyles = useGlobalStyles();
+ const reminderModal = useRef();
+
+ const openReminderModal = () => reminderModal.current?.present();
+
+ const closeReminderModal = () => reminderModal.current.close();
+
+ const removeReminder = (index: number) => () =>
+ setReminders((prev) => [...prev.filter((_, ind) => ind !== index)]);
+
+ const addReminder = (datetime: dayjs.Dayjs) => {
+ setReminders((prev) => [...prev, new DisciplineReminder(datetime)]);
+ closeReminderModal();
+ };
+
+ const addTask = () => {
+ onTaskAdd({ description, reminders });
+ };
+
+ const removeTask = () => {
+ Alert.alert('Удаление задания', 'Вы действительно хотите удалить это задание', [
+ { text: 'Отмена' },
+ {
+ text: 'Удалить',
+ onPress: () => onTaskRemove(selectedTask),
+ },
+ ]);
+ };
+
+ return (
+
+ {showDisciplineInfo && selectedTask && (
+ <>
+ {selectedTask.disciplineName}
+ {formatTime(selectedTask.datetime)}
+ >
+ )}
+ {/* BottomSheetTextInput просто закрывается при открытии клавиатуры */}
+ Описание
+
+
+
+ Напоминания
+
+
+
+
+ {reminders.length ? (
+ reminders.map((rem, index) => (
+
+ ))
+ ) : (
+ Нет напоминаний
+ )}
+
+
+
+
+ {!!selectedTask && (
+
+ )}
+ {!!description && (
+
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default AddTaskModalContent;
+
+const styles = StyleSheet.create({
+ modalContainer: {
+ padding: '4%',
+ gap: 8,
+ },
+ disciplineText: {
+ fontWeight: '600',
+ ...fontSize.large,
+ },
+ timeText: {
+ ...fontSize.medium,
+ },
+ textInput: {
+ padding: '2%',
+ ...fontSize.large,
+ },
+ titleText: {
+ ...fontSize.big,
+ fontWeight: '500',
+ },
+ buttonsList: {
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
+ alignItems: 'flex-end',
+ gap: 8,
+ },
+ button: {
+ fontWeight: '500',
+ ...fontSize.medium,
+ },
+ row: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ noRemindersText: {
+ fontWeight: '500',
+ ...fontSize.medium,
+ },
+});
diff --git a/src/screens/disciplineInfo/DisciplineInfo.tsx b/src/screens/disciplineInfo/DisciplineInfo.tsx
new file mode 100644
index 00000000..7f3bfbc7
--- /dev/null
+++ b/src/screens/disciplineInfo/DisciplineInfo.tsx
@@ -0,0 +1,116 @@
+import dayjs from 'dayjs';
+import 'dayjs/locale/ru';
+import React, { useMemo } from 'react';
+import { StyleSheet, View } from 'react-native';
+
+import AnnouncePopover from '../../components/AnnouncePopover';
+import BorderLine from '../../components/BorderLine';
+import Screen from '../../components/Screen';
+import Text from '../../components/Text';
+import { useAppTheme } from '../../hooks/theme';
+import { RootStackScreenProps } from '../../navigation/types';
+import { disciplineRegex } from '../../parser/regex';
+import { fontSize, getDisciplineTypeName } from '../../utils/texts';
+import Note from './components/Note';
+import { TaskContainer } from './components/TaskContainer';
+import { AudienceInfo, TeacherInfo, TimeInfo } from './components/info';
+
+const TypeContainer = ({ name, type }: { name: string; type: string }) => {
+ const styles = useMemo(
+ () =>
+ StyleSheet.compose(typeContainerStyles.base, typeContainerStyles[typeNameToStyleName(type)]),
+ [type]
+ );
+
+ return (
+
+ {name}
+
+ );
+};
+
+const DisciplineInfo = ({ route }: RootStackScreenProps<'DisciplineInfo'>) => {
+ const { date: stringDate, lesson, pairPosition } = route.params;
+ const theme = useAppTheme();
+
+ // React navigation не позволяет передавать функции и экземпляры классов,
+ // поэтому пришлось преобразовать Moment в строку, а сейчас обратно
+ const date = useMemo(() => dayjs(stringDate), [stringDate]);
+
+ // TODO: move type parse in parser
+ const [disciplineName, disciplineType, typeName] = useMemo(() => {
+ const [, discipline, type] = disciplineRegex.exec(lesson.subject);
+ return [discipline, type, getDisciplineTypeName(type)];
+ }, [lesson]);
+
+ return (
+
+
+ {disciplineName}
+
+
+
+
+ {lesson.announceHTML && }
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ borderRadius: 20,
+ gap: 8,
+ padding: '2%',
+ marginBottom: '2%',
+ },
+ text: {
+ fontWeight: '500',
+ ...fontSize.big,
+ },
+});
+
+const typeContainerStyles = StyleSheet.create({
+ base: {
+ borderRadius: 5,
+ paddingHorizontal: '2%',
+ paddingVertical: '1%',
+ alignSelf: 'flex-start',
+ },
+ lecture: {
+ backgroundColor: '#C62E3E',
+ },
+ practice: {
+ backgroundColor: '#0053cd',
+ },
+ laboratory: {
+ backgroundColor: '#4CAF50',
+ },
+ unknown: {
+ backgroundColor: '#B0BEC5',
+ },
+ text: {
+ fontSize: 14,
+ fontWeight: '500',
+ color: '#FFFFFF',
+ },
+});
+
+const typeNameToStyleName = (typeName: string) =>
+ ({
+ // todo: чтоб без повторений типов было. Привести все к единому стилю
+ лек: 'lecture',
+ практ: 'practice',
+ лаб: 'laboratory',
+ })[typeName] || 'unknown';
+
+export default DisciplineInfo;
diff --git a/src/screens/disciplineInfo/HistoryButton.tsx b/src/screens/disciplineInfo/HistoryButton.tsx
new file mode 100644
index 00000000..bbbfa30f
--- /dev/null
+++ b/src/screens/disciplineInfo/HistoryButton.tsx
@@ -0,0 +1,42 @@
+import { Ionicons } from '@expo/vector-icons';
+import React from 'react';
+import { StyleSheet } from 'react-native';
+
+import ClickableText from '../../components/ClickableText';
+import { useGlobalStyles } from '../../hooks';
+import { fontSize } from '../../utils/texts';
+
+const HistoryButton = ({ onPress, showHistory }: { onPress: () => void; showHistory: boolean }) => {
+ const globalStyles = useGlobalStyles();
+
+ return (
+
+ }
+ iconRight={
+
+ }
+ />
+ );
+};
+
+export default HistoryButton;
+
+const styles = StyleSheet.create({
+ showInactiveButton: {
+ paddingVertical: '1%',
+ paddingHorizontal: '2%',
+ marginTop: '2%',
+ gap: 4,
+ },
+});
diff --git a/src/screens/disciplineInfo/TaskModal.tsx b/src/screens/disciplineInfo/TaskModal.tsx
new file mode 100644
index 00000000..ca922631
--- /dev/null
+++ b/src/screens/disciplineInfo/TaskModal.tsx
@@ -0,0 +1,39 @@
+import { BottomSheetModal } from '@gorhom/bottom-sheet';
+import React from 'react';
+
+import BottomSheetModalBackdrop from '../../components/BottomSheetModalBackdrop';
+import { useAppTheme } from '../../hooks/theme';
+import { DisciplineTask } from '../../models/disciplinesTasks';
+import AddTaskModalContent, { PartialTask } from './AddTaskModalContent';
+
+interface TaskModalProps {
+ onTaskAdd?: (partialTask: PartialTask) => void;
+ onTaskRemove: (task: DisciplineTask) => void;
+ task?: DisciplineTask;
+ onDismiss?: () => void;
+}
+
+const TaskModal = React.forwardRef(
+ ({ onTaskAdd, onTaskRemove, task, onDismiss }, ref) => {
+ const theme = useAppTheme();
+
+ return (
+
+
+
+ );
+ }
+);
+
+export default TaskModal;
diff --git a/src/screens/disciplineInfo/components/AddButton.tsx b/src/screens/disciplineInfo/components/AddButton.tsx
new file mode 100644
index 00000000..9849ad22
--- /dev/null
+++ b/src/screens/disciplineInfo/components/AddButton.tsx
@@ -0,0 +1,28 @@
+import { Ionicons } from '@expo/vector-icons';
+import React from 'react';
+import { StyleSheet, TouchableOpacity } from 'react-native';
+
+import Text from '../../../components/Text';
+import { useGlobalStyles } from '../../../hooks';
+
+const AddButton = ({ onPress }: { onPress: () => void }) => {
+ const globalStyles = useGlobalStyles();
+ return (
+
+
+ Добавить
+
+ );
+};
+
+export default AddButton;
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ paddingVertical: '1%',
+ paddingHorizontal: '2%',
+ alignSelf: 'flex-start',
+ borderRadius: 25,
+ },
+});
diff --git a/src/screens/disciplineInfo/components/Note.tsx b/src/screens/disciplineInfo/components/Note.tsx
new file mode 100644
index 00000000..f5563053
--- /dev/null
+++ b/src/screens/disciplineInfo/components/Note.tsx
@@ -0,0 +1,142 @@
+import { Ionicons } from '@expo/vector-icons';
+import { useNavigation } from '@react-navigation/native';
+import React, { useEffect, useState } from 'react';
+import { Alert, StyleSheet, TextInput, ToastAndroid, TouchableOpacity, View } from 'react-native';
+
+import BorderLine from '../../../components/BorderLine';
+import Text from '../../../components/Text';
+import { useGlobalStyles } from '../../../hooks';
+import useBackPress from '../../../hooks/useBackPress';
+import { IDisciplineInfo } from '../../../models/disciplineInfo';
+import { DisciplineStorage } from '../../../models/disciplinesTasks';
+import { RootStackNavigationProp } from '../../../navigation/types';
+import { fontSize } from '../../../utils/texts';
+
+const findDiscipline = (disciplineName: string, disciplines: IDisciplineInfo[]) => {
+ let info = disciplines.find((info) => info.name === disciplineName);
+ if (info) return info;
+ info = { note: '', name: disciplineName };
+ disciplines.push(info);
+ return info;
+};
+
+const Note = ({ disciplineName }: { disciplineName: string }) => {
+ const navigation = useNavigation();
+ const globalStyles = useGlobalStyles();
+ const [info, setInfo] = useState();
+ const [isTextChanged, setTextChanged] = useState(false);
+ const [showNote, setShowNote] = useState(true);
+
+ useEffect(() => {
+ DisciplineStorage.getInfo().then((disciplines) => {
+ setInfo(findDiscipline(disciplineName, disciplines));
+ });
+ }, []);
+
+ const handleEditNote = (value: string) => {
+ setInfo((info) => ({ ...info, note: value }));
+ setTextChanged(true);
+ };
+
+ const handleNoteSave = () => {
+ DisciplineStorage.getInfo().then((infos) => {
+ const $info = findDiscipline(disciplineName, infos);
+ $info.note = info.note;
+ DisciplineStorage.saveInfo().then(() => {
+ ToastAndroid.show('Сохранено!', ToastAndroid.LONG);
+ setTextChanged(false);
+ });
+ });
+ };
+
+ useBackPress(() => {
+ if (!isTextChanged) return false;
+ Alert.alert(
+ 'Заметка',
+ 'У вас есть несохранённые изменения в заметке. Желаете ли вы сохранить?',
+ [
+ {
+ text: 'Выйти',
+ onPress: () => navigation.goBack(),
+ },
+ {
+ text: 'Сохранить и выйти',
+ onPress: () => {
+ handleNoteSave();
+ navigation.goBack();
+ },
+ },
+ ]
+ );
+ return true;
+ });
+
+ if (!info) return;
+
+ return (
+ <>
+
+
+
+ Заметки
+ setShowNote((prev) => !prev)}>
+
+
+
+ {showNote && (
+
+
+
+ {isTextChanged && (
+
+
+
+ )}
+
+ )}
+ >
+ );
+};
+
+export default Note;
+
+const styles = StyleSheet.create({
+ noteContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ textInputContainer: {
+ flexDirection: 'row',
+ },
+ textInput: {
+ padding: '2%',
+ paddingRight: '10%',
+ ...fontSize.medium,
+ },
+ text: {
+ fontWeight: '500',
+ ...fontSize.large,
+ },
+ saveIcon: {
+ position: 'absolute',
+ right: 0,
+ bottom: 0,
+ marginRight: '1%',
+ marginBottom: '2%',
+ },
+});
diff --git a/src/screens/disciplineInfo/components/Reminder.tsx b/src/screens/disciplineInfo/components/Reminder.tsx
new file mode 100644
index 00000000..c542bd4b
--- /dev/null
+++ b/src/screens/disciplineInfo/components/Reminder.tsx
@@ -0,0 +1,37 @@
+import { Ionicons } from '@expo/vector-icons';
+import React from 'react';
+import { StyleSheet, TouchableOpacity, View } from 'react-native';
+
+import Text from '../../../components/Text';
+import { useAppTheme } from '../../../hooks/theme';
+import { DisciplineReminder } from '../../../models/disciplinesTasks';
+import { formatTime } from '../../../utils/datetime';
+import { fontSize } from '../../../utils/texts';
+
+const Reminder = ({
+ reminder,
+ onRemove,
+}: {
+ reminder: DisciplineReminder;
+ onRemove: () => void;
+}) => {
+ const theme = useAppTheme();
+
+ return (
+
+ {formatTime(reminder.datetime)}
+
+
+
+
+ );
+};
+
+export default Reminder;
+
+const styles = StyleSheet.create({
+ reminderContainer: {
+ flexDirection: 'row',
+ gap: 6,
+ },
+});
diff --git a/src/screens/disciplineInfo/components/TaskContainer.tsx b/src/screens/disciplineInfo/components/TaskContainer.tsx
new file mode 100644
index 00000000..66d1192b
--- /dev/null
+++ b/src/screens/disciplineInfo/components/TaskContainer.tsx
@@ -0,0 +1,116 @@
+import { BottomSheetModal } from '@gorhom/bottom-sheet';
+import dayjs from 'dayjs';
+import React, { useRef, useState } from 'react';
+import { StyleSheet, View } from 'react-native';
+
+import Text from '../../../components/Text';
+import useBackPress from '../../../hooks/useBackPress';
+import useTasks from '../../../hooks/useTasks';
+import { DisciplineStorage, DisciplineTask } from '../../../models/disciplinesTasks';
+import { fontSize } from '../../../utils/texts';
+import { PartialTask } from '../AddTaskModalContent';
+import TaskModal from '../TaskModal';
+import AddButton from './AddButton';
+import TaskList from './TaskList';
+
+export const TaskContainer = ({
+ disciplineName,
+ disciplineDate,
+}: {
+ disciplineName: string;
+ disciplineDate: dayjs.Dayjs;
+}) => {
+ const modalRef = useRef();
+ const modalOpened = useRef(false);
+ const [selectedTask, setSelectedTask] = useState(null);
+ const { tasks, addTask, saveTasks, removeTask } = useTasks({
+ filter: (task) => task.disciplineName === disciplineName,
+ });
+
+ const openModal = () => {
+ modalRef.current.present();
+ modalOpened.current = true;
+ };
+
+ const closeModal = () => {
+ modalRef.current.dismiss();
+ modalOpened.current = false;
+ };
+
+ const handleAddTask = (partial: PartialTask) => {
+ if (selectedTask) {
+ selectedTask.description = partial.description;
+ selectedTask.reminders = partial.reminders;
+ saveTasks().then(() => {
+ closeModal();
+ setSelectedTask(null);
+ });
+ return;
+ }
+ const task = new DisciplineTask(
+ DisciplineStorage.getNextTaskId(), // Логично, не правда ли?
+ disciplineName,
+ partial.description,
+ disciplineDate.clone(),
+ partial.reminders
+ );
+
+ addTask(task).then(closeModal);
+ };
+
+ const onRequestEdit = (task: DisciplineTask) => {
+ setSelectedTask(task);
+ openModal();
+ };
+
+ const handleTaskRemove = (task: DisciplineTask) => {
+ removeTask(task).then(closeModal);
+ };
+
+ // ВЕЗДЕ ПРОБЛЕМЫ
+ // 1. Библиотека bottom sheet modal не предоставляет информацию о состоянии модалки,
+ // поэтому используется ещё один ref
+ // 2. Ивент навигации внутри useBackPress не триггерится внутри модалки, поэтому,
+ // если выйти из модалки с добавлением напоминания, то она закроется, однако первая не появится :(
+ useBackPress(() => {
+ if (modalOpened.current) {
+ closeModal();
+ return true;
+ }
+ return false;
+ });
+
+ const currentDate = dayjs();
+
+ return (
+
+
+ Задания
+ {disciplineDate > currentDate && }
+
+
+
+
+ {
+ modalOpened.current = false;
+ }}
+ />
+
+ );
+};
+
+const styles = StyleSheet.create({
+ taskContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ taskText: {
+ fontWeight: '500',
+ ...fontSize.large,
+ },
+});
diff --git a/src/screens/disciplineInfo/components/TaskItem.tsx b/src/screens/disciplineInfo/components/TaskItem.tsx
new file mode 100644
index 00000000..d165957d
--- /dev/null
+++ b/src/screens/disciplineInfo/components/TaskItem.tsx
@@ -0,0 +1,51 @@
+import { Ionicons } from '@expo/vector-icons';
+import React from 'react';
+import { StyleSheet, TouchableOpacity } from 'react-native';
+
+import Card from '../../../components/Card';
+import Text from '../../../components/Text';
+import { useAppTheme } from '../../../hooks/theme';
+import { DisciplineTask } from '../../../models/disciplinesTasks';
+import { fontSize } from '../../../utils/texts';
+
+const TaskItem = ({
+ task,
+ onRequestEdit,
+ showDisciplineName,
+}: {
+ task: DisciplineTask;
+ onRequestEdit: (task: DisciplineTask) => void;
+ showDisciplineName?: boolean;
+}) => {
+ const theme = useAppTheme();
+
+ return (
+ <>
+ {showDisciplineName && {task.disciplineName}}
+
+ {task.description}
+ onRequestEdit(task)} style={{ alignSelf: 'center' }}>
+
+
+
+ >
+ );
+};
+
+export default TaskItem;
+
+const styles = StyleSheet.create({
+ disciplineNameText: {
+ fontWeight: '500',
+ ...fontSize.medium,
+ marginBottom: '1%',
+ },
+ outerContainer: {
+ padding: '2%',
+ },
+ innerContainer: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ marginBottom: 0,
+ },
+});
diff --git a/src/screens/disciplineInfo/components/TaskList.tsx b/src/screens/disciplineInfo/components/TaskList.tsx
new file mode 100644
index 00000000..b17336f3
--- /dev/null
+++ b/src/screens/disciplineInfo/components/TaskList.tsx
@@ -0,0 +1,106 @@
+import dayjs from 'dayjs';
+import React, { useMemo, useState } from 'react';
+import { StyleSheet, View } from 'react-native';
+
+import Text from '../../../components/Text';
+import { DisciplineTask } from '../../../models/disciplinesTasks';
+import { formatTime } from '../../../utils/datetime';
+import { fontSize } from '../../../utils/texts';
+import { groupItems } from '../../../utils/utils';
+import HistoryButton from '../HistoryButton';
+import TaskItem from './TaskItem';
+
+const GroupedTaskList = ({
+ tasks,
+ disciplineDate,
+ onRequestEdit,
+}: {
+ tasks: DisciplineTask[];
+ disciplineDate: dayjs.Dayjs;
+ onRequestEdit: (task: DisciplineTask) => void;
+}) => {
+ const { datetime } = tasks[0];
+ let time: string;
+
+ if (disciplineDate.isSame(datetime)) time = 'На эту пару';
+ else time = formatTime(datetime);
+
+ return (
+ <>
+ {time}
+
+ {tasks.map((task) => (
+
+ ))}
+
+ >
+ );
+};
+
+const TaskList = ({
+ tasks,
+ disciplineDate,
+ onRequestEdit,
+}: {
+ tasks: DisciplineTask[];
+ disciplineDate: dayjs.Dayjs;
+ onRequestEdit: (task: DisciplineTask) => void;
+}) => {
+ const [showInactiveTasks, setShowInactiveTasks] = useState(false);
+
+ const currentDate = dayjs();
+
+ const inactiveTasks = tasks.filter((task) => task.datetime < currentDate).reverse();
+ const activeTasks = tasks.filter((task) => task.datetime >= currentDate);
+ const groupedActiveTasks = useMemo(
+ () => groupItems(activeTasks, (task) => task.datetime.toISOString()),
+ [activeTasks]
+ );
+
+ return (
+ <>
+ {groupedActiveTasks.map((group) => (
+
+ ))}
+
+ {!!inactiveTasks.length && (
+ setShowInactiveTasks((prev) => !prev)}
+ showHistory={showInactiveTasks}
+ />
+ )}
+
+ {showInactiveTasks && inactiveTasks.length && (
+
+ )}
+ >
+ );
+};
+
+export default TaskList;
+
+const styles = StyleSheet.create({
+ taskListContainer: {
+ gap: 8,
+ },
+ title: {
+ marginBottom: '2%',
+ ...fontSize.big,
+ },
+ showInactiveButton: {
+ paddingVertical: '1%',
+ paddingHorizontal: '2%',
+ marginTop: '2%',
+ justifyContent: 'space-between',
+ },
+});
diff --git a/src/screens/disciplineInfo/components/info.tsx b/src/screens/disciplineInfo/components/info.tsx
new file mode 100644
index 00000000..05670178
--- /dev/null
+++ b/src/screens/disciplineInfo/components/info.tsx
@@ -0,0 +1,128 @@
+import { Ionicons } from '@expo/vector-icons';
+import dayjs from 'dayjs';
+import * as Clipboard from 'expo-clipboard';
+import { Image } from 'expo-image';
+import React from 'react';
+import { Linking, StyleSheet, ToastAndroid, View } from 'react-native';
+
+import ClickableText from '../../../components/ClickableText';
+import Text from '../../../components/Text';
+import { useClient } from '../../../data/client';
+import { useAppSelector } from '../../../hooks';
+import { useAppTheme } from '../../../hooks/theme';
+import useQuery from '../../../hooks/useQuery';
+import { RequestType } from '../../../models/results';
+import { DistancePlatformTypes, ILesson, ITeacher } from '../../../models/timeTable';
+import { getTeacherName } from '../../../utils/teachers';
+import { fontSize, formatAudience } from '../../../utils/texts';
+
+const PAIR_LENGTH = 95;
+const LESSON_LENGTH = 40;
+
+const studentTimeInfo = {
+ length: PAIR_LENGTH,
+ name: 'пара',
+ ending: 'я',
+} as const;
+
+const lyceumTimeInfo = {
+ length: LESSON_LENGTH,
+ name: 'урок',
+ ending: 'й',
+} as const;
+
+const getAssetByPlatformType = (type: DistancePlatformTypes) => {
+ const platformTypeToAsset = [
+ [DistancePlatformTypes.zoom, require('../../../../assets/platforms/zoom.svg')],
+ [DistancePlatformTypes.bbb, require('../../../../assets/platforms/bigbluebutton.svg')],
+ [DistancePlatformTypes.skype, require('../../../../assets/platforms/skype.svg')],
+ ];
+
+ return platformTypeToAsset.find(([$type]) => type === $type)[1];
+};
+
+const IconInfo = ({ icon, text }: { icon: keyof typeof Ionicons.glyphMap; text: string }) => {
+ const theme = useAppTheme();
+
+ return (
+
+
+ {text}
+
+ );
+};
+
+export const TimeInfo = ({ date, pairPosition }: { date: dayjs.Dayjs; pairPosition: number }) => {
+ const { isLyceum } = useAppSelector((state) => state.student.info);
+ const { name, length, ending } = isLyceum ? lyceumTimeInfo : studentTimeInfo;
+
+ date = date.locale('ru');
+ const day = date.format('D MMMM');
+ const startTime = date.format('HH:mm');
+ const endTime = date.clone().add(length, 'minute').format('HH:mm');
+
+ const text = `${day}\n${startTime} – ${endTime} · ${pairPosition}-${ending} ${name}`;
+ return ;
+};
+
+export const TeacherInfo = ({ teacher }: { teacher?: ITeacher }) => {
+ const client = useClient();
+ const { data } = useQuery({
+ method: client.getTeacherData,
+ payload: {
+ requestType: RequestType.tryCache,
+ },
+ });
+
+ if (!teacher || !data) return;
+
+ const teacherName = getTeacherName(data, teacher);
+ return ;
+};
+
+export const AudienceInfo = ({ lesson }: { lesson: ILesson }) => {
+ const theme = useAppTheme();
+
+ if (lesson.distancePlatform) {
+ const asset = getAssetByPlatformType(lesson.distancePlatform.type);
+ return (
+
+
+ {
+ Linking.openURL(lesson.distancePlatform.url);
+ }}
+ onLongPress={() => {
+ Clipboard.setStringAsync(lesson.distancePlatform.url).then(() => {
+ ToastAndroid.show('Скопировано в буфер обмена', ToastAndroid.LONG);
+ });
+ }}
+ textStyle={styles.text}
+ colorVariant={'block'}
+ />
+
+ );
+ }
+
+ const audience = formatAudience(lesson);
+ return ;
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+ icon: {
+ alignSelf: 'center',
+ },
+ text: {
+ flexShrink: 1,
+ ...fontSize.large,
+ },
+});
diff --git a/src/screens/disciplinesTasks/DisciplinesTasks.tsx b/src/screens/disciplinesTasks/DisciplinesTasks.tsx
new file mode 100644
index 00000000..e7342b20
--- /dev/null
+++ b/src/screens/disciplinesTasks/DisciplinesTasks.tsx
@@ -0,0 +1,138 @@
+import { BottomSheetModal } from '@gorhom/bottom-sheet';
+import dayjs from 'dayjs';
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { StyleSheet } from 'react-native';
+
+import CardHeaderOut from '../../components/CardHeaderOut';
+import CenteredText from '../../components/CenteredText';
+import Screen from '../../components/Screen';
+import useBackPress from '../../hooks/useBackPress';
+import useTasks from '../../hooks/useTasks';
+import { DisciplineStorage, DisciplineTask } from '../../models/disciplinesTasks';
+import { RootStackScreenProps } from '../../navigation/types';
+import { formatTime } from '../../utils/datetime';
+import { fontSize } from '../../utils/texts';
+import { groupItems } from '../../utils/utils';
+import HistoryButton from '../disciplineInfo/HistoryButton';
+import TaskModal from '../disciplineInfo/TaskModal';
+import TaskItem from '../disciplineInfo/components/TaskItem';
+
+const TaskGroup = ({
+ tasks,
+ onRequestEdit,
+}: {
+ tasks: DisciplineTask[];
+ onRequestEdit: (task: DisciplineTask) => void;
+}) => {
+ const time = formatTime(tasks[0].datetime, { disableTime: true });
+
+ return (
+
+ {tasks.map((task) => (
+
+ ))}
+
+ );
+};
+
+const DisciplinesTasks = ({ route }: RootStackScreenProps<'DisciplineTasks'>) => {
+ const { tasks, removeTask } = useTasks();
+
+ const [selectedTask, setSelectedTask] = useState();
+ const [showInactiveTasks, setShowInactiveTasks] = useState(false);
+ const modalRef = useRef();
+ const modalOpened = useRef(false);
+
+ const onRequestEdit = (task: DisciplineTask) => {
+ setSelectedTask(task);
+ modalRef.current.present(task);
+ modalOpened.current = true;
+ };
+
+ const handleTaskRemove = (task: DisciplineTask) => {
+ removeTask(task).then(() => {
+ modalRef.current.dismiss();
+ modalOpened.current = false;
+ });
+ };
+
+ useEffect(() => {
+ if (route.params?.taskId) {
+ DisciplineStorage.getTasks().then((tasks) => {
+ const task = tasks.find((task) => task.id === route.params?.taskId);
+ setSelectedTask(task);
+ modalRef.current.present();
+ modalOpened.current = true;
+ });
+ }
+ }, [route.params?.taskId]);
+
+ useBackPress(
+ useCallback(() => {
+ if (modalOpened.current) {
+ modalRef.current.dismiss();
+ modalOpened.current = false;
+ return true;
+ }
+ return false;
+ }, [])
+ );
+
+ const currentDate = dayjs();
+
+ const inactiveTasks = tasks.filter((task) => task.datetime < currentDate).reverse();
+ const activeTasks = tasks.filter((task) => task.datetime >= currentDate);
+ const groupedActiveTasks = useMemo(
+ () => groupItems(activeTasks, (task) => task.datetime.toISOString()),
+ [activeTasks]
+ );
+
+ return (
+
+ {!tasks.length && (
+
+ Вы ещё не добавили задания. Нажмите на пару в расписании для подробностей
+
+ )}
+
+ {groupedActiveTasks.map((group) => (
+
+ ))}
+
+ {!!inactiveTasks.length && (
+ setShowInactiveTasks((prev) => !prev)}
+ showHistory={showInactiveTasks}
+ />
+ )}
+
+ {showInactiveTasks && }
+
+ {
+ modalOpened.current = false;
+ }}
+ task={selectedTask}
+ />
+
+ );
+};
+
+export default DisciplinesTasks;
+
+const styles = StyleSheet.create({
+ taskListContainer: {
+ gap: 8,
+ },
+ title: {
+ ...fontSize.big,
+ },
+ showInactiveButton: {
+ paddingVertical: '1%',
+ paddingHorizontal: '2%',
+ marginTop: '2%',
+ gap: 4,
+ },
+});
diff --git a/src/screens/settings/ToggleSignNotification.tsx b/src/screens/settings/ToggleSignNotification.tsx
index 65cecbf6..d6d7072a 100644
--- a/src/screens/settings/ToggleSignNotification.tsx
+++ b/src/screens/settings/ToggleSignNotification.tsx
@@ -6,7 +6,7 @@ import { cache } from '../../cache/smartCache';
import Text from '../../components/Text';
import { useAppDispatch, useAppSelector, useGlobalStyles } from '../../hooks';
import { setSignNotification } from '../../redux/reducers/settingsSlice';
-import { registerFetch, unregisterBackgroundFetchAsync } from '../../tasks/signs';
+import { registerSignsFetchTask, unregisterBackgroundFetchAsync } from '../../tasks/signs';
import { NOTIFICATION_GUIDE_URL } from '../../utils';
import { fontSize } from '../../utils/texts';
@@ -32,7 +32,7 @@ const ToggleSignNotification = () => {
}
if (hasSignNotification) {
- registerFetch();
+ registerSignsFetchTask();
} else {
unregisterBackgroundFetchAsync();
}
diff --git a/src/screens/timeTable/Day.tsx b/src/screens/timeTable/Day.tsx
index 7e1a2ab3..22d35712 100644
--- a/src/screens/timeTable/Day.tsx
+++ b/src/screens/timeTable/Day.tsx
@@ -1,3 +1,4 @@
+import moment from 'moment';
import React from 'react';
import { View } from 'react-native';
@@ -16,6 +17,7 @@ import Pair from './Pair';
interface DayData {
data: ITimeTableDay;
teachersData: TeacherType;
+ date: moment.Moment;
}
const responses = ['Пар нет', 'Отдых', '💤', '😴', '🎮', '(๑ᵕ⌓ᵕ̤)'];
@@ -26,7 +28,7 @@ const getRandomResponse = (appTheme: ThemeType) => {
return getRandomItem(responses);
};
-const EmptyDay = ({ data }: DayData) => {
+const EmptyDay = ({ data }: { data: ITimeTableDay }) => {
const { date } = data;
const { theme } = useAppSelector((state) => state.settings);
@@ -41,14 +43,14 @@ const EmptyDay = ({ data }: DayData) => {
);
};
-const Day = ({ data, teachersData }: DayData) => {
- const { date, pairs } = data;
+const Day = ({ data, teachersData, date }: DayData) => {
+ const { date: dateString, pairs } = data;
return (
-
+
{pairs.map((pair, index) => (
-
+
{index !== pairs.length - 1 && }
))}
diff --git a/src/screens/timeTable/DayArray.tsx b/src/screens/timeTable/DayArray.tsx
index 3334f75e..b3702237 100644
--- a/src/screens/timeTable/DayArray.tsx
+++ b/src/screens/timeTable/DayArray.tsx
@@ -1,24 +1,37 @@
+import moment from 'moment';
import React from 'react';
import { TeacherType } from '../../models/teachers';
-import { ITimeTableDay } from '../../models/timeTable';
+import { ITimeTableDay, WeekDates } from '../../models/timeTable';
import { Day, EmptyDay } from './Day';
interface IDayArrayProps {
data: ITimeTableDay[];
teachersData: TeacherType;
+ weekDates: WeekDates;
}
-const DayArray = ({ data, teachersData }: IDayArrayProps) => (
- <>
- {data.map((day) =>
- day.pairs.length === 0 ? (
-
- ) : (
-
- )
- )}
- >
-);
+const DayArray = ({ data, teachersData, weekDates }: IDayArrayProps) => {
+ let date = moment(weekDates.start, 'DD.MM.YYYY');
+
+ const bumpDate = () => {
+ const prev = date.clone();
+ date = date.add(1, 'days');
+ return prev;
+ };
+
+ return (
+ <>
+ {data.map((day) => {
+ const date = bumpDate();
+ return day.pairs.length === 0 ? (
+
+ ) : (
+
+ );
+ })}
+ >
+ );
+};
export default DayArray;
diff --git a/src/screens/timeTable/Pair.tsx b/src/screens/timeTable/Pair.tsx
index 287be731..1ed41a35 100644
--- a/src/screens/timeTable/Pair.tsx
+++ b/src/screens/timeTable/Pair.tsx
@@ -1,19 +1,27 @@
+import { useNavigation } from '@react-navigation/native';
+import moment from 'moment/moment';
import React from 'react';
-import { Linking, StyleSheet, TouchableOpacity, View } from 'react-native';
-import AutoHeightWebView from 'react-native-autoheight-webview';
-import Popover, { PopoverPlacement } from 'react-native-popover-view';
+import { StyleSheet, TouchableOpacity, View } from 'react-native';
-import ClickableText from '../../components/ClickableText';
import Text from '../../components/Text';
-import { useGlobalStyles } from '../../hooks';
-import { useAppTheme } from '../../hooks/theme';
+import { useAppSelector } from '../../hooks';
import { TeacherType } from '../../models/teachers';
import { ILesson, IPair } from '../../models/timeTable';
-import { fontSize } from '../../utils/texts';
-import { getStyles } from '../../utils/webView';
+import { BottomTabsNavigationProp } from '../../navigation/types';
+import { getTeacherName } from '../../utils/teachers';
+import { fontSize, formatAudience } from '../../utils/texts';
-export default function Pair({ pair, teachersData }: { pair: IPair; teachersData: TeacherType }) {
- const pairText = `${pair.position} пара`;
+export default function Pair({
+ pair,
+ teachersData,
+ date,
+}: {
+ pair: IPair;
+ teachersData: TeacherType;
+ date: moment.Moment;
+}) {
+ const { isLyceum } = useAppSelector((state) => state.student.info);
+ const pairText = `${pair.position} ${isLyceum ? 'урок' : 'пара'}`;
return (
@@ -25,85 +33,63 @@ export default function Pair({ pair, teachersData }: { pair: IPair; teachersData
- {pair.lessons.map((lesson, ind) => (
-
- ))}
+ {pair.lessons.map((lesson, ind) => {
+ const time = moment(pair.time, 'HH:mm');
+
+ return (
+
+ );
+ })}
);
}
-const AnnouncePopover = ({ data }: { data: string }) => {
- const globalStyles = useGlobalStyles();
- const theme = useAppTheme();
+const Lesson = ({
+ data,
+ teachersData,
+ date,
+ pairPosition,
+}: {
+ data: ILesson;
+ teachersData: TeacherType;
+ date: moment.Moment;
+ pairPosition: number;
+}) => {
+ const navigation = useNavigation();
- return (
- (
-
-
- Объявление
-
-
- )}
- popoverStyle={{
- borderRadius: globalStyles.border.borderRadius,
- backgroundColor: globalStyles.block.backgroundColor,
- padding: '2%',
- }}
- >
-
-
- );
-};
-
-const Lesson = ({ data, teachersData }: { data: ILesson; teachersData: TeacherType }) => {
- const location =
- data.audience && data.building && data.floor
- ? `ауд. ${data.audience} (${data.building} корпус, ${data.floor} этаж)`
- : data.audienceText;
+ const location = formatAudience(data);
const audience = data.isDistance ? data.audience : location;
- let teacherName: string;
-
- if (data.teacher?.id) {
- teachersData.forEach(([, teachers]) => {
- const teacher = teachers.find((teacher) => teacher.id === data.teacher.id);
- if (teacher) teacherName = teacher.name;
- });
- } else teacherName = data.teacher?.name;
+ const teacherName = getTeacherName(teachersData, data.teacher);
return (
-
+
+ navigation.navigate('DisciplineInfo', {
+ lesson: data,
+ date: date.toISOString(),
+ pairPosition,
+ })
+ }
+ >
{data.subject}
- {data.distancePlatform ? (
- {
- Linking.openURL(data.distancePlatform.url);
- }}
- textStyle={{ textDecorationLine: 'underline', fontWeight: '500' }}
- colorVariant={'block'}
- />
- ) : audience ? (
- {audience}
- ) : (
-
- )}
+ {data.distancePlatform && {data.distancePlatform.name}}
+ {!data.distancePlatform && audience && {audience}}
+ {data.announceHTML && Объявление}
{teacherName && {teacherName}}
-
+
);
};
diff --git a/src/screens/timeTable/TimeTable.tsx b/src/screens/timeTable/TimeTable.tsx
index 132133fb..7ad16930 100644
--- a/src/screens/timeTable/TimeTable.tsx
+++ b/src/screens/timeTable/TimeTable.tsx
@@ -52,7 +52,11 @@ const TimeTable = () => {
{data.weekInfo.type === WeekTypes.holiday ? (
) : (
-
+
)}
>
)}
diff --git a/src/tasks/disciplineTasks.ts b/src/tasks/disciplineTasks.ts
new file mode 100644
index 00000000..a9bbe760
--- /dev/null
+++ b/src/tasks/disciplineTasks.ts
@@ -0,0 +1,51 @@
+import dayjs from 'dayjs';
+import * as BackgroundFetch from 'expo-background-fetch';
+import { BackgroundFetchResult } from 'expo-background-fetch';
+import * as TaskManager from 'expo-task-manager';
+
+import { DisciplineReminder, DisciplineStorage, DisciplineTask } from '../models/disciplinesTasks';
+import { sendReminderTaskNotification } from '../utils/notifications';
+import { partitionItems } from '../utils/utils';
+
+const BACKGROUND_FETCH_TASK = 'discipline-tasks-fetch';
+
+const groupReminders = (task: DisciplineTask, datetime: dayjs.Dayjs): DisciplineReminder[][] =>
+ partitionItems(task.reminders, (reminder) => datetime.diff(reminder.datetime, 'minute') >= 0);
+
+export const defineReminderTask = () =>
+ TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
+ const tasks = await DisciplineStorage.getTasks();
+ if (tasks.length === 0) {
+ return BackgroundFetchResult.NoData;
+ }
+
+ const datetime = dayjs();
+ tasks
+ .filter((task) => !!task.reminders.length)
+ .forEach((task) => {
+ const [oldReminders, futureReminders] = groupReminders(task, datetime);
+ oldReminders.forEach(() => {
+ sendReminderTaskNotification(task);
+ });
+ task.reminders = futureReminders;
+ DisciplineStorage.saveTasks();
+ });
+
+ return BackgroundFetchResult.NewData;
+ });
+
+async function registerBackgroundFetchAsync() {
+ return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
+ minimumInterval: 60, // 1 minute
+ stopOnTerminate: false, // android only,
+ startOnBoot: false, // android only
+ });
+}
+
+export async function unregisterBackgroundFetchAsync() {
+ return BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK);
+}
+
+export const registerReminderTask = async () => {
+ registerBackgroundFetchAsync().then(() => console.log('[FETCH] Reminder task registered'));
+};
diff --git a/src/tasks/signs.ts b/src/tasks/signs.ts
index 887548b6..f2974ed8 100644
--- a/src/tasks/signs.ts
+++ b/src/tasks/signs.ts
@@ -45,7 +45,7 @@ const differenceSigns = (marks1: ISubject[], marks2: ISubject[]): IDifferentChec
.flat();
};
-export const defineFetchTask = () =>
+export const defineSignsFetchTask = () =>
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
const [cachedResult, onlineResult] = await Promise.all([
await client.getSessionSignsData({
@@ -109,7 +109,7 @@ export async function unregisterBackgroundFetchAsync() {
return BackgroundFetch.unregisterTaskAsync(BACKGROUND_FETCH_TASK);
}
-export const registerFetch = async () => {
+export const registerSignsFetchTask = async () => {
client = new Client();
currentSession = (await client.getSessionSignsData({ requestType: RequestType.forceFetch })).data
?.currentSession;
diff --git a/src/utils/datetime.ts b/src/utils/datetime.ts
index acb11648..aa881722 100644
--- a/src/utils/datetime.ts
+++ b/src/utils/datetime.ts
@@ -1,4 +1,38 @@
-import moment from 'moment/moment';
+import dayjs from 'dayjs';
+import 'dayjs/locale/ru';
+import moment from 'moment';
+import 'moment/locale/ru';
+
+type DateType = moment.Moment | dayjs.Dayjs;
+
+export const compareTime = (a: DateType, b: DateType): number => {
+ if (a > b) return 1;
+ if (a < b) return -1;
+ return 0;
+};
+
+interface IFormatTimeProps {
+ disableTime?: boolean;
+ disableDate?: boolean;
+}
+
+const dateFormat = 'D MMMM';
+const timeFormat = 'HH:mm';
+export const formatTime = (
+ date: DateType,
+ { disableTime, disableDate }: IFormatTimeProps = { disableTime: false, disableDate: false }
+) => {
+ if (disableTime && disableDate) return '';
+ date = date.locale('ru') as DateType;
+
+ if (disableTime) {
+ return date.format(dateFormat);
+ }
+ if (disableDate) {
+ return date.format(timeFormat);
+ }
+ return date.format(`${dateFormat} в ${timeFormat}`);
+};
export const getCurrentEducationYear = () => {
const date = moment();
diff --git a/src/utils/files.ts b/src/utils/files.ts
index 168124fb..f513b1a0 100644
--- a/src/utils/files.ts
+++ b/src/utils/files.ts
@@ -1,18 +1,21 @@
import {
EncodingType,
StorageAccessFramework,
+ documentDirectory,
readAsStringAsync,
writeAsStringAsync,
} from 'expo-file-system';
+import { IDisciplineInfo, IDisciplineTask } from '../models/disciplineInfo';
import httpClient from './http';
const downloadFile = (url, fileName) => httpClient.downloadFile(url, fileName);
-const saveFile = async (fileData, fileName) => {
+const saveFileFromCache = async (fileData, fileName) => {
const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
if (permissions.granted) {
const base64 = await readAsStringAsync(fileData.uri, { encoding: EncodingType.Base64 });
+
const newFileUrl = await StorageAccessFramework.createFileAsync(
permissions.directoryUri,
fileName,
@@ -22,4 +25,40 @@ const saveFile = async (fileData, fileName) => {
}
};
-export { downloadFile, saveFile };
+// discipline_notes.json
+const saveJSONToDocuments = async (data: any, fileName: string) => {
+ await writeAsStringAsync(`${documentDirectory}${fileName}`, JSON.stringify(data));
+};
+
+const readJSONFromDocuments = async (fileName: string, defaultValue: any) => {
+ let stringData: string;
+
+ try {
+ stringData = await readAsStringAsync(`${documentDirectory}${fileName}`);
+ } catch (e) {
+ await saveJSONToDocuments(defaultValue, fileName);
+ stringData = await readAsStringAsync(`${documentDirectory}${fileName}`);
+ }
+ return JSON.parse(stringData);
+};
+
+const saveDisciplineInfo = (data: IDisciplineInfo[]) =>
+ saveJSONToDocuments(data, 'discipline_info.json');
+const readDisciplineInfo = (): Promise =>
+ readJSONFromDocuments('discipline_info.json', {});
+
+const saveDisciplinesTasks = (data: IDisciplineTask[]) =>
+ saveJSONToDocuments(data, 'disciplines_tasks.json');
+const readDisciplinesTasks = (): Promise =>
+ readJSONFromDocuments('disciplines_tasks.json', []);
+
+export {
+ downloadFile,
+ saveFileFromCache,
+ saveJSONToDocuments,
+ readJSONFromDocuments,
+ saveDisciplineInfo,
+ readDisciplineInfo,
+ saveDisciplinesTasks,
+ readDisciplinesTasks,
+};
diff --git a/src/utils/index.js b/src/utils/index.js
index 46838cce..82747075 100644
--- a/src/utils/index.js
+++ b/src/utils/index.js
@@ -1,11 +1,11 @@
import { GITHUB_URL, NOTIFICATION_GUIDE_URL, PRIVACY_POLICY_URL, TELEGRAM_URL } from './consts';
-import { downloadFile, saveFile } from './files';
+import { downloadFile, saveFileFromCache } from './files';
import httpClient from './http';
export {
httpClient,
downloadFile,
- saveFile,
+ saveFileFromCache,
PRIVACY_POLICY_URL,
NOTIFICATION_GUIDE_URL,
TELEGRAM_URL,
diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts
index 12c97696..591159f9 100644
--- a/src/utils/notifications.ts
+++ b/src/utils/notifications.ts
@@ -2,6 +2,7 @@ import * as Notifications from 'expo-notifications';
import { AndroidNotificationPriority } from 'expo-notifications/src/Notifications.types';
import { Platform } from 'react-native';
+import { DisciplineTask } from '../models/disciplinesTasks';
import { getPointsWord } from './texts';
import { getRandomItem } from './utils';
@@ -48,6 +49,29 @@ ${getRandomItem(messages[signType])} ${mark} ${getPointsWord(newRes)}!`,
},
});
};
+
+export const sendReminderTaskNotification = (task: DisciplineTask) => {
+ console.log('[NOTIF] Sending task reminder notification...');
+ const message =
+ task.description.length < 30 ? task.description : `${task.description.slice(0, 29)}...`;
+ Notifications.scheduleNotificationAsync({
+ content: {
+ title: task.disciplineName,
+ body: `Напоминание о задании: ${message}`,
+ data: {
+ type: 'task-reminder',
+ data: {
+ taskId: task.id,
+ },
+ },
+ },
+ trigger: {
+ seconds: 5,
+ channelId: 'default',
+ },
+ });
+};
+
export const setNotificationHandler = () =>
Notifications.setNotificationHandler({
handleNotification: async () => ({
@@ -57,6 +81,7 @@ export const setNotificationHandler = () =>
priority: AndroidNotificationPriority.MAX,
}),
});
+
export const registerForPushNotificationsAsync = async () => {
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
@@ -78,3 +103,17 @@ export const registerForPushNotificationsAsync = async () => {
console.warn('Failed to get permissions for push notification!');
}
};
+
+interface IBaseNotificationData {
+ type: string;
+ data: unknown;
+}
+
+interface ITaskReminderNotificationData extends IBaseNotificationData {
+ type: 'task-reminder';
+ data: {
+ taskId: number;
+ };
+}
+
+export interface INotificationData extends ITaskReminderNotificationData {}
diff --git a/src/utils/teachers.ts b/src/utils/teachers.ts
new file mode 100644
index 00000000..3b0cf743
--- /dev/null
+++ b/src/utils/teachers.ts
@@ -0,0 +1,16 @@
+import { TeacherType } from '../models/teachers';
+import { ITeacher } from '../models/timeTable';
+
+export const getTeacherName = (teacherGroups: TeacherType, teacher: ITeacher) => {
+ let teacherName: string;
+
+ if (teacher.id) {
+ teacherGroups.forEach(([, teachers]) => {
+ const $teacher = teachers.find(($teacher) => $teacher.id === teacher.id);
+ if ($teacher) teacherName = $teacher.name;
+ });
+ }
+ if (!teacherName) teacherName = teacher.name;
+
+ return teacherName;
+};
diff --git a/src/utils/texts.ts b/src/utils/texts.ts
index 4bebd581..9f3c2550 100644
--- a/src/utils/texts.ts
+++ b/src/utils/texts.ts
@@ -1,6 +1,7 @@
import { StyleSheet } from 'react-native';
import { ICheckPoint } from '../models/sessionPoints';
+import { ILesson } from '../models/timeTable';
export const getPointsWord = (points: number) => {
let pointsWord = 'балл';
@@ -21,6 +22,7 @@ export const formatCheckPointScore = (checkPoint: ICheckPoint) => {
if (Number.isNaN(checkPoint.points) || !checkPoint.points) return '-';
};
+// todo: rename every size to character like format
export const fontSize = StyleSheet.create({
micro: {
fontSize: 8,
@@ -34,9 +36,18 @@ export const fontSize = StyleSheet.create({
medium: {
fontSize: 16,
},
+ big: {
+ fontSize: 18,
+ },
large: {
fontSize: 20,
},
+ slarge: {
+ fontSize: 22,
+ },
+ mlarge: {
+ fontSize: 24,
+ },
xlarge: {
fontSize: 26,
},
@@ -44,3 +55,19 @@ export const fontSize = StyleSheet.create({
fontSize: 36,
},
});
+
+export const disciplineTypeNames = {
+ лек: 'Лекция',
+ практ: 'Практика',
+ лаб: 'Лабораторная',
+ зачет: 'Зачёт',
+ экзамен: 'Экзамен',
+};
+
+export const getDisciplineTypeName = (type: string): string => disciplineTypeNames[type] || type;
+
+export const formatAudience = (lesson: ILesson) => {
+ return lesson.audience && lesson.building && lesson.floor
+ ? `ауд. ${lesson.audience} (${lesson.building} корпус, ${lesson.floor} этаж)`
+ : lesson.audienceText;
+};
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index 06214319..6e45254e 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -1 +1,24 @@
export const getRandomItem = (array: T[]): T => array[Math.floor(Math.random() * array.length)];
+
+export const partitionItems = (array: T[], callback: (item: T) => boolean): T[][] =>
+ array.reduce(
+ ([group1, group2], item) =>
+ callback(item) ? [[...group1, item], group2] : [group1, [...group2, item]],
+ [[], []]
+ );
+
+type GroupT = { [s: string]: T[] };
+
+export const groupItems = (array: T[], keyExtractor: (item: T) => string) => {
+ const grouped: GroupT = {} as GroupT;
+ array.forEach((item) => {
+ const key = keyExtractor(item);
+ let group = grouped[key];
+ if (group) group.push(item);
+ else group = [item];
+
+ grouped[key] = group;
+ });
+
+ return Object.values(grouped);
+};