diff --git a/babel.config.js b/babel.config.js
index f7b3da3..02c7d13 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,3 +1,4 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
+ plugins: ['react-native-reanimated/plugin'],
};
diff --git a/package-lock.json b/package-lock.json
index cc5d20f..bd3e08d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,10 +32,12 @@
"react-native-keychain": "^10.0.0",
"react-native-linear-gradient": "^2.8.3",
"react-native-paper": "^5.14.5",
+ "react-native-reanimated": "^4.1.2",
"react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.16.0",
"react-native-svg": "^15.13.0",
"react-native-walkthrough-tooltip": "^1.6.0",
+ "react-native-worklets": "^0.6.0",
"react-redux": "^9.2.0"
},
"devDependencies": {
@@ -154,7 +156,6 @@
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"
@@ -183,7 +184,6 @@
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
"integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
@@ -205,7 +205,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz",
"integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.1",
@@ -249,7 +248,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
"integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.27.1",
@@ -293,7 +291,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
"integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.1"
@@ -333,7 +330,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
"integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-member-expression-to-functions": "^7.27.1",
@@ -351,7 +347,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
"integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.27.1",
@@ -699,7 +694,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
"integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@@ -817,7 +811,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
"integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@@ -850,7 +843,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
"integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@@ -934,7 +926,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
"integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.27.1",
@@ -968,7 +959,6 @@
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
@@ -1271,7 +1261,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz",
"integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.27.1",
@@ -1357,7 +1346,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
"integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@@ -1442,7 +1430,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
@@ -1664,7 +1651,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
"integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@@ -1713,7 +1699,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
"integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@@ -1745,7 +1730,6 @@
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz",
"integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
@@ -1798,7 +1782,6 @@
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
"integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.27.1",
@@ -1928,6 +1911,25 @@
"@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
}
},
+ "node_modules/@babel/preset-typescript": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz",
+ "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-option": "^7.27.1",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.27.1",
+ "@babel/plugin-transform-typescript": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
@@ -4822,6 +4824,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@@ -8177,6 +8180,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "devOptional": true,
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
@@ -8927,6 +8931,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "devOptional": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
@@ -9810,6 +9815,7 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -13422,6 +13428,34 @@
"react-native": "*"
}
},
+ "node_modules/react-native-reanimated": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.2.tgz",
+ "integrity": "sha512-qzmQiFrvjm62pRBcj97QI9Xckc3EjgHQoY1F2yjktd0kpjhoyePeuTEXjYRCAVIy7IV/1cfeSup34+zFThFoHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "react-native-is-edge-to-edge": "^1.2.1",
+ "semver": "7.7.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0",
+ "react": "*",
+ "react-native": "*",
+ "react-native-worklets": ">=0.5.0"
+ }
+ },
+ "node_modules/react-native-reanimated/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/react-native-safe-area-context": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz",
@@ -13549,6 +13583,42 @@
"@types/react": ">=16.8.24"
}
},
+ "node_modules/react-native-worklets": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.6.0.tgz",
+ "integrity": "sha512-yETMNuCcivdYWteuG4eRqgiAk2DzRCrVAaEBIEWPo4emrf3BNjadFo85L5QvyEusrX9QKE3ZEAx8U5A/nbyFgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-transform-arrow-functions": "^7.0.0-0",
+ "@babel/plugin-transform-class-properties": "^7.0.0-0",
+ "@babel/plugin-transform-classes": "^7.0.0-0",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0",
+ "@babel/plugin-transform-optional-chaining": "^7.0.0-0",
+ "@babel/plugin-transform-shorthand-properties": "^7.0.0-0",
+ "@babel/plugin-transform-template-literals": "^7.0.0-0",
+ "@babel/plugin-transform-unicode-regex": "^7.0.0-0",
+ "@babel/preset-typescript": "^7.16.7",
+ "convert-source-map": "^2.0.0",
+ "semver": "7.7.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0",
+ "react": "*",
+ "react-native": "*"
+ }
+ },
+ "node_modules/react-native-worklets/node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/react-native/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
@@ -13688,14 +13758,12 @@
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
"integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/regenerate-unicode-properties": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
"integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2"
@@ -13735,7 +13803,6 @@
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
"integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2",
@@ -13753,14 +13820,12 @@
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
"integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/regjsparser": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz",
"integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
- "devOptional": true,
"license": "BSD-2-Clause",
"dependencies": {
"jsesc": "~3.1.0"
@@ -14790,6 +14855,7 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/throat": {
@@ -15037,7 +15103,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
"integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
- "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -15047,7 +15112,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
"integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"unicode-canonical-property-names-ecmascript": "^2.0.0",
@@ -15061,7 +15125,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
"integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
- "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -15071,7 +15134,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
"integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
- "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -15248,6 +15310,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
diff --git a/package.json b/package.json
index 8886c8e..0e73695 100644
--- a/package.json
+++ b/package.json
@@ -34,10 +34,12 @@
"react-native-keychain": "^10.0.0",
"react-native-linear-gradient": "^2.8.3",
"react-native-paper": "^5.14.5",
+ "react-native-reanimated": "^4.1.2",
"react-native-safe-area-context": "^5.6.1",
"react-native-screens": "^4.16.0",
"react-native-svg": "^15.13.0",
"react-native-walkthrough-tooltip": "^1.6.0",
+ "react-native-worklets": "^0.6.0",
"react-redux": "^9.2.0"
},
"devDependencies": {
diff --git a/src/components/common/AnimatedText.tsx b/src/components/common/AnimatedText.tsx
new file mode 100644
index 0000000..066ce82
--- /dev/null
+++ b/src/components/common/AnimatedText.tsx
@@ -0,0 +1,64 @@
+import React, { useEffect } from 'react';
+import { View, Text, StyleSheet } from 'react-native';
+import Animated, {
+ useSharedValue,
+ useAnimatedStyle,
+ withTiming,
+ withRepeat,
+ withSequence,
+ withDelay,
+ Easing,
+} from 'react-native-reanimated';
+
+const AnimatedText = ({ text, style, delay = 0 }) => {
+ const opacity = useSharedValue(0);
+ const translateY = useSharedValue(20);
+
+ useEffect(() => {
+ opacity.value = withDelay(delay, withTiming(1, { duration: 800 }));
+ translateY.value = withDelay(delay, withTiming(0, { duration: 800 }));
+ }, []);
+
+ const animatedStyle = useAnimatedStyle(() => ({
+ opacity: opacity.value,
+ transform: [{ translateY: translateY.value }],
+ }));
+
+ return (
+
+ {text}
+
+ );
+};
+
+export default AnimatedText;
+
+// Usage
+const MyComponent = () => {
+ return (
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ title: {
+ fontSize: 32,
+ fontWeight: 'bold',
+ marginBottom: 10,
+ },
+ subtitle: {
+ fontSize: 18,
+ color: '#666',
+ },
+ text: {
+ textAlign: 'center',
+ },
+});
\ No newline at end of file
diff --git a/src/screens/GroupScreen.tsx b/src/screens/GroupScreen.tsx
index 34036bc..d4c5f96 100644
--- a/src/screens/GroupScreen.tsx
+++ b/src/screens/GroupScreen.tsx
@@ -27,8 +27,6 @@ import { ApiResponse } from "./home/UserProfileStatus";
import * as Keychain from "react-native-keychain";
const { width } = Dimensions.get('window');
-
-
// --- Types ---
interface Group {
id: string;
@@ -39,6 +37,7 @@ interface Group {
contributionAmount: number;
cycleType: string;
createdByName: string;
+ imageBlob: string
}
// --- UI Components ---
@@ -48,16 +47,25 @@ const GroupCard = ({ item, onJoin, onView, isGrid }: { item: Group; onJoin: (gro
return (
- {item.groupName}
- {item.description}
+
+
+ {item.groupName}
+ {item.description}
+
+
+
+
{item.currentParticipants}/{item.maxParticipants} Members
{item.cycleType}
- onView(item)}>
+ {/* onView(item)}>
View
-
+ */}
onJoin(item.id)}
@@ -70,15 +78,19 @@ const GroupCard = ({ item, onJoin, onView, isGrid }: { item: Group; onJoin: (gro
);
};
-const FeaturedGroupCard = ({ item }: { item: Group }) => (
-
-
-
- {item.groupName}
- by {item.createdByName}
+const FeaturedGroupCard = ({ item }: { item: Group }) => {
+ console.log("image item : ", item)
+ console.log("image view : ", `data:image/jpeg;base64${item?.imageBlob}`)
+ return (
+
+
+
+ {item.groupName}
+ by {item.createdByName}
+
-
-);
+ )
+};
export default function GroupScreen() {
const [activeTab, setActiveTab] = useState("Public");
@@ -114,6 +126,7 @@ export default function GroupScreen() {
}
};
+
const fetchJoinedGroups = async () => {
if (loading) return;
setLoading(true);
@@ -166,7 +179,7 @@ export default function GroupScreen() {
Alert.alert("Error", "An unexpected error occurred.");
}
};
-
+
const userStatus = async () => {
try {
const credentials = await Keychain.getGenericPassword();
@@ -264,7 +277,7 @@ export default function GroupScreen() {
refreshControl={
}
- // ListFooterComponent={}
+ // ListFooterComponent={}
/>
);
};
diff --git a/src/screens/LoginScreen.tsx b/src/screens/LoginScreen.tsx
index 5b37f77..12979a9 100644
--- a/src/screens/LoginScreen.tsx
+++ b/src/screens/LoginScreen.tsx
@@ -19,6 +19,7 @@ import type { StackNavigationProp } from "@react-navigation/stack";
import { RootStackParamList } from "../navigation/AppNavigator";
import { CustomButton, CustomInput } from "../components/common/Custom";
import colors from "../components/colors";
+import AnimatedText from "../components/common/AnimatedText";
const theme = {
primary: "#111111",
@@ -94,6 +95,7 @@ return (
{/* Top content */}
+ {/* */}
CircleCollect
Welcome Back 👋
diff --git a/src/screens/management/EditGroupScreen.tsx b/src/screens/management/EditGroupScreen.tsx
index a342086..d8e7f4b 100644
--- a/src/screens/management/EditGroupScreen.tsx
+++ b/src/screens/management/EditGroupScreen.tsx
@@ -58,7 +58,7 @@ export interface GroupDetails {
organizerId: string;
totalParticipants: number;
participants: Participant[];
- imageBlob:string
+ imageBlob: string
}
export interface EditGroupPayload {
@@ -323,82 +323,127 @@ const EditGroupScreen: React.FC = ({ route }) => {
const isInvitable = groupDetails.status === 'APPROVED';
const canStartGroup = groupDetails.status === 'APPROVED' && groupDetails.totalParticipants >= groupDetails.minParticipants;
+ // const pickImage = () => {
+ // launchImageLibrary({ mediaType: "photo", quality: 0.5 }, (response) => {
+ // if (response.didCancel) return;
+ // if (response.errorCode) {
+ // Alert.alert("Error", response.errorMessage || "Something went wrong");
+ // } else if (response.assets && response.assets.length > 0) {
+ // const selectedImage = response.assets[0];
+ // console.log(selectedImage?.uri)
+ // setSelectedImage(selectedImage?.uri)
+ // }
+ // });
+ // };
+
const pickImage = () => {
launchImageLibrary({ mediaType: "photo", quality: 0.5 }, (response) => {
if (response.didCancel) return;
+
if (response.errorCode) {
Alert.alert("Error", response.errorMessage || "Something went wrong");
- } else if (response.assets && response.assets.length > 0) {
- const selectedImage = response.assets[0];
- console.log(selectedImage?.uri)
- setSelectedImage(selectedImage?.uri)
+ return;
}
+
+ if (!response.assets || response.assets.length === 0) {
+ Alert.alert("Error", "No image selected");
+ return;
+ }
+
+ const selectedImage = response.assets[0];
+ const maxSizeKB = 1000;
+ const maxSizeBytes = maxSizeKB * 1024;
+
+ // Size validation
+ if (selectedImage.fileSize && selectedImage.fileSize > maxSizeBytes) {
+ const currentSizeKB = (selectedImage.fileSize / 1024).toFixed(2);
+ Alert.alert(
+ "File Too Large",
+ `Selected image is ${currentSizeKB}KB. Please select an image smaller than ${maxSizeKB}KB.`
+ );
+ return;
+ }
+
+ // Additional validation for required fields
+ if (!selectedImage.uri) {
+ Alert.alert("Error", "Invalid image file");
+ return;
+ }
+
+ console.log("✅ Image selected:", {
+ uri: selectedImage.uri,
+ size: (selectedImage.fileSize / 1024).toFixed(2) + "KB",
+ width: selectedImage.width,
+ height: selectedImage.height
+ });
+
+ setSelectedImage(selectedImage.uri);
});
};
-
+
const handleUpdateImage = async () => {
- try {
- const credentials = await Keychain.getGenericPassword();
- if (!credentials) {
- Alert.alert("Error", "You are not logged in");
- return;
- }
- const token = credentials.password;
-
- if (!selectedImage) {
- Alert.alert("Error", "Please select an image first");
- return;
+ try {
+ const credentials = await Keychain.getGenericPassword();
+ if (!credentials) {
+ Alert.alert("Error", "You are not logged in");
+ return;
+ }
+ const token = credentials.password;
+
+ if (!selectedImage) {
+ Alert.alert("Error", "Please select an image first");
+ return;
+ }
+
+ setImageUpdating(true);
+
+ // Get file extension and mime type dynamically
+ const fileExtension = selectedImage.split('.').pop();
+
+ const formData = new FormData();
+ formData.append("file", {
+ uri: selectedImage,
+ type: "image/jpeg",
+ name: `group_${groupId}_profile.${fileExtension || "jpg"}`,
+ });
+
+ const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`;
+
+ console.log("url : ", url)
+ console.log("formdata : ", formData)
+
+ // return
+ const response = await fetch(url, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "multipart/form-data",
+ "Authorization": `Bearer ${token}`,
+ },
+ body: formData,
+ });
+
+ console.log("image response : ", response)
+ if (response?.status === 200) {
+ fetchGroupDetails()
+ ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT)
+ }
+
+ } catch (error: any) {
+ console.error("❌ Error updating group image:", error);
+ Alert.alert("Error", error.message || "Could not update group image");
+ } finally {
+ setImageUpdating(false);
}
+ };
- setImageUpdating(true);
-
- // Get file extension and mime type dynamically
- const fileExtension = selectedImage.split('.').pop();
-
- const formData = new FormData();
- formData.append("file", {
- uri: selectedImage,
- type: "image/jpeg",
- name: `group_${groupId}_profile.${fileExtension || "jpg"}`,
- });
-
- const url = `${UPLOAD_GROUP_IMAGE}${groupId}/upload-image`;
-
- console.log("url : ", url)
- console.log("formdata : ", formData)
-
- // return
- const response = await fetch(url, {
- method: "PUT",
- headers: {
- "Content-Type": "multipart/form-data",
- "Authorization": `Bearer ${token}`,
- },
- body: formData,
- });
-
- console.log("image response : ", response)
- if(response?.status === 200){
- fetchGroupDetails()
- ToastAndroid.show('Group uploaded successfully', ToastAndroid.SHORT)
- }
-
- } catch (error:any) {
- console.error("❌ Error updating group image:", error);
- Alert.alert("Error", error.message || "Could not update group image");
- } finally {
- setImageUpdating(false);
- }
-};
-
-const getProfileImage = () => {
- return selectedImage
- ? selectedImage
- : groupDetails?.imageBlob
- ? `data:image/jpeg;base64,${groupDetails.imageBlob}`
- : "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg";
-};
-// Helper function to get MIME type
+ const getProfileImage = () => {
+ return selectedImage
+ ? selectedImage
+ : groupDetails?.imageBlob
+ ? `data:image/jpeg;base64,${groupDetails.imageBlob}`
+ : "https://img.freepik.com/premium-vector/people-group-frame-icon_791764-2649.jpg";
+ };
+ // Helper function to get MIME type
return (
@@ -430,12 +475,12 @@ const getProfileImage = () => {
-
+
diff --git a/src/screens/management/ManagementTabNavigation.tsx b/src/screens/management/ManagementTabNavigation.tsx
index dca74ad..f961a8e 100644
--- a/src/screens/management/ManagementTabNavigation.tsx
+++ b/src/screens/management/ManagementTabNavigation.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { View, Text, StyleSheet, Platform, TouchableOpacity } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
-import Ionicons from 'react-native-vector-icons/Ionicons'; // Correct import for react-native-vector-icons
+import {Ionicons} from '@react-native-vector-icons/ionicons'; // Correct import for react-native-vector-icons
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { RootStackParamList } from '../../navigation/AppNavigator'; // Adjust path
@@ -28,7 +28,7 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
console.log("option : ", options)
// A custom option to pass the icon name
- const iconName = options.tabBarIconName ?? 'help-circle';
+ const iconName = label === "Settings" ? "trail-sign-outline" : label === "Payments" ? "trending-up" : label === "Members" ? "people-outline" : "flower";
const isFocused = state.index === index;
@@ -79,7 +79,7 @@ const FloatingTabBar = ({ state, descriptors, navigation }: BottomTabBarProps) =
// --- Main Tab Navigator Component ---
const ManagementTabNavigation: React.FC = ({ route }) => {
- const { groupId } = route.params
+ const { groupId } = route.params
return (
= ({ route }) => {
options={{
tabBarLabel: 'Settings',
// Custom prop to define the icon for our FloatingTabBar
-
+
}}
/>
- = ({ route }) => {
}}
/>
-
{/* You can easily add more tabs here in the future */}