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 */}