From d00734bf4aea29a29487878c1229ac54e6689016 Mon Sep 17 00:00:00 2001 From: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com> Date: Sun, 22 Feb 2026 14:59:13 -0600 Subject: [PATCH 1/3] fix: respect reduced animations system settings (#990) * fix: respect reduced animations system settings lockfiles, rollback eslint * adding reduced motion config to discover screen * deduplicate logic for the card rows on the home and discover screen * respect ReduceMotion at the system level for home and discover animated rows * rolling back renimated since that ain't working with reduced animations --- App.tsx | 2 + bun.lock | 47 +++++++------------ ios/Podfile.lock | 14 +++--- package.json | 8 ++-- patches/react-native-reanimated+4.1.6.patch | 13 +++++ .../Discover/helpers/just-added.tsx | 20 +++----- .../Discover/helpers/public-playlists.tsx | 20 +++----- .../Discover/helpers/suggested-albums.tsx | 18 +++---- .../Discover/helpers/suggested-artists.tsx | 20 +++----- .../Global/helpers/animated-row.tsx | 28 +++++++++++ .../Home/helpers/frequent-artists.tsx | 13 ++--- .../Home/helpers/frequent-tracks.tsx | 13 ++--- .../Home/helpers/recent-artists.tsx | 13 ++--- .../Home/helpers/recently-played.tsx | 13 ++--- 14 files changed, 111 insertions(+), 131 deletions(-) create mode 100644 patches/react-native-reanimated+4.1.6.patch create mode 100644 src/components/Global/helpers/animated-row.tsx diff --git a/App.tsx b/App.tsx index ca0c4e66..632241e2 100644 --- a/App.tsx +++ b/App.tsx @@ -31,6 +31,7 @@ import { CarPlay } from 'react-native-carplay' import { useAutoStore } from './src/stores/auto' import { registerAutoService } from './src/player' import QueryPersistenceConfig from './src/configs/query-persistence.config' +import { ReducedMotionConfig, ReduceMotion } from 'react-native-reanimated' LogBox.ignoreAllLogs() @@ -135,6 +136,7 @@ function Container({ playerIsReady }: { playerIsReady: boolean }): React.JSX.Ele theme={getJellifyNavTheme(colorPreset, resolvedMode)} > + {playerIsReady && } diff --git a/bun.lock b/bun.lock index 7661f9dd..ae69a99a 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "jellify", @@ -43,7 +44,7 @@ "react-native-nitro-modules": "0.33.7", "react-native-nitro-ota": "^0.11.0", "react-native-pager-view": "8.0.0", - "react-native-reanimated": "4.2.1", + "react-native-reanimated": "4.1.6", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.23.0", "react-native-sortables": "1.9.4", @@ -55,7 +56,7 @@ "react-native-url-polyfill": "3.0.0", "react-native-uuid": "^2.0.3", "react-native-worklets": "0.7.2", - "react-native-worklets-core": "^1.6.2", + "react-native-worklets-core": "1.6.2", "ruby": "^0.6.1", "scheduler": "^0.26.0", "tamagui": "1.144.3", @@ -66,7 +67,7 @@ "@babel/preset-env": "7.29.0", "@babel/runtime": "7.28.6", "@eslint/eslintrc": "3.3.3", - "@eslint/js": "10.0.1", + "@eslint/js": "9.39.2", "@react-native-community/cli-platform-android": "20.1.1", "@react-native-community/cli-platform-ios": "20.1.1", "@react-native/babel-preset": "0.83.1", @@ -81,7 +82,7 @@ "@types/react-test-renderer": "19.1.0", "babel-plugin-module-resolver": "^5.0.2", "babel-plugin-react-compiler": "^1.0.0", - "eslint": "10.0.0", + "eslint": "9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "5.5.5", @@ -369,19 +370,19 @@ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - "@eslint/config-array": ["@eslint/config-array@0.23.1", "", { "dependencies": { "@eslint/object-schema": "^3.0.1", "debug": "^4.3.1", "minimatch": "^10.1.1" } }, "sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA=="], + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.5.2", "", { "dependencies": { "@eslint/core": "^1.1.0" } }, "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - "@eslint/core": ["@eslint/core@1.1.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw=="], + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], "@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="], - "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], + "@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="], - "@eslint/object-schema": ["@eslint/object-schema@3.0.1", "", {}, "sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg=="], + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.6.0", "", { "dependencies": { "@eslint/core": "^1.1.0", "levn": "^0.4.1" } }, "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ=="], + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], @@ -859,8 +860,6 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], @@ -1237,7 +1236,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@10.0.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.0", "@eslint/config-helpers": "^0.5.2", "@eslint/core": "^1.1.0", "@eslint/plugin-kit": "^0.6.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.0", "eslint-visitor-keys": "^5.0.0", "espree": "^11.1.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.1.1", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg=="], + "eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="], "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], @@ -1263,9 +1262,9 @@ "eslint-plugin-react-native-globals": ["eslint-plugin-react-native-globals@0.1.2", "", {}, "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g=="], - "eslint-scope": ["eslint-scope@9.1.0", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ=="], + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - "eslint-visitor-keys": ["eslint-visitor-keys@5.0.0", "", {}, "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q=="], + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], @@ -1651,6 +1650,8 @@ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], @@ -1925,7 +1926,7 @@ "react-native-pager-view": ["react-native-pager-view@8.0.0", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-oAwlWT1lhTkIs9HhODnjNNl/owxzn9DP1MbP+az6OTUdgbmzA16Up83sBH8NRKwrH8rNm7iuWnX1qMqiiWOLhg=="], - "react-native-reanimated": ["react-native-reanimated@4.2.1", "", { "dependencies": { "react-native-is-edge-to-edge": "1.2.1", "semver": "7.7.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": ">=0.7.0" } }, "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg=="], + "react-native-reanimated": ["react-native-reanimated@4.1.6", "", { "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" } }, "sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ=="], "react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="], @@ -2483,8 +2484,6 @@ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@eslint/config-array/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="], - "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -2659,8 +2658,6 @@ "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], @@ -2707,10 +2704,6 @@ "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - "eslint/espree": ["espree@11.1.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw=="], - - "eslint/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="], - "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -2727,8 +2720,6 @@ "eslint-plugin-react-hooks/hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], - "espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], @@ -2963,9 +2954,7 @@ "react-native-blob-util/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="], - "react-native-nitro-ota/react-native-nitro-ota": ["react-native-nitro-ota@0.10.1", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": "0.32.0" } }, "sha512-CYEN0pnjAd0BA3fZ9nghBvLKVW40+xQj0JsclrrrccSmG4TcV6FZOgjeT2Hh3zdmBjvAxnvjar5+7HtnbE/6Qg=="], - - "react-native-reanimated/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "react-native-reanimated/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "react-native-worklets/@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA=="], diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0c2db6f3..f0dd5afd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3024,7 +3024,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - RNReanimated (4.2.1): + - RNReanimated (4.1.6): - boost - DoubleConversion - fast_float @@ -3051,11 +3051,11 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated (= 4.2.1) + - RNReanimated/reanimated (= 4.1.6) - RNWorklets - SocketRocket - Yoga - - RNReanimated/reanimated (4.2.1): + - RNReanimated/reanimated (4.1.6): - boost - DoubleConversion - fast_float @@ -3082,11 +3082,11 @@ PODS: - ReactCodegen - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNReanimated/reanimated/apple (= 4.2.1) + - RNReanimated/reanimated/apple (= 4.1.6) - RNWorklets - SocketRocket - Yoga - - RNReanimated/reanimated/apple (4.2.1): + - RNReanimated/reanimated/apple (4.1.6): - boost - DoubleConversion - fast_float @@ -3650,7 +3650,7 @@ SPEC CHECKSUMS: Gifu: 9f7e52357d41c0739709019eb80a71ad9aab1b6d glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 google-cast-sdk: 32f65af50d164e3c475e79ad123db3cc26fbcd37 - hermes-engine: 6878b8fefe82b91b24caae48ac97164746244d09 + hermes-engine: 83ac7cadb2a3a158ae6d9e4192417c5232065e99 MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df NitroFetch: 1a268c80f654b3018397672d264f1e4d646076b1 NitroMmkv: 0be91455465952f2b943f753b9ee7df028d89e5c @@ -3746,7 +3746,7 @@ SPEC CHECKSUMS: RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8 RNGestureHandler: cd4be101cfa17ea6bbd438710caa02e286a84381 RNReactNativeHapticFeedback: be4f1b4bf0398c30b59b76ed92ecb0a2ff3a69c6 - RNReanimated: 292cd58688552a22b3fc1cefcfbc49b336dfed68 + RNReanimated: 942d757148da78f5663d1fdf9ab71d1e75946c22 RNScreens: afaf526a9c804c3b4503f950cf3e67ed81e29ada RNSentry: 47538022dd91abf23db1a954a09cdb8db13b1b98 RNWorklets: 01efdd402d236a13651ea5ea5437ca85a44e7afa diff --git a/package.json b/package.json index 13a645f9..afa0f272 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "react-native-nitro-modules": "0.33.7", "react-native-nitro-ota": "^0.11.0", "react-native-pager-view": "8.0.0", - "react-native-reanimated": "4.2.1", + "react-native-reanimated": "4.1.6", "react-native-safe-area-context": "5.6.2", "react-native-screens": "4.23.0", "react-native-sortables": "1.9.4", @@ -89,7 +89,7 @@ "react-native-url-polyfill": "3.0.0", "react-native-uuid": "^2.0.3", "react-native-worklets": "0.7.2", - "react-native-worklets-core": "^1.6.2", + "react-native-worklets-core": "1.6.2", "ruby": "^0.6.1", "scheduler": "^0.26.0", "tamagui": "1.144.3", @@ -100,7 +100,7 @@ "@babel/preset-env": "7.29.0", "@babel/runtime": "7.28.6", "@eslint/eslintrc": "3.3.3", - "@eslint/js": "10.0.1", + "@eslint/js": "9.39.2", "@react-native-community/cli-platform-android": "20.1.1", "@react-native-community/cli-platform-ios": "20.1.1", "@react-native/babel-preset": "0.83.1", @@ -115,7 +115,7 @@ "@types/react-test-renderer": "19.1.0", "babel-plugin-module-resolver": "^5.0.2", "babel-plugin-react-compiler": "^1.0.0", - "eslint": "10.0.0", + "eslint": "9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "5.5.5", diff --git a/patches/react-native-reanimated+4.1.6.patch b/patches/react-native-reanimated+4.1.6.patch new file mode 100644 index 00000000..999dc9dd --- /dev/null +++ b/patches/react-native-reanimated+4.1.6.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/react-native-reanimated/compatibility.json b/node_modules/react-native-reanimated/compatibility.json +index f1ba9cb..ffab7fe 100644 +--- a/node_modules/react-native-reanimated/compatibility.json ++++ b/node_modules/react-native-reanimated/compatibility.json +@@ -4,7 +4,7 @@ + "react-native-worklets": ["nightly"] + }, + "4.1.x": { +- "react-native": ["0.78", "0.79", "0.80", "0.81", "0.82"], ++ "react-native": ["0.78", "0.79", "0.80", "0.81", "0.82", "0.83", "0.84"], + "react-native-worklets": ["0.5.x", "0.6.x", "0.7.x"] + }, + "4.0.x": { diff --git a/src/components/Discover/helpers/just-added.tsx b/src/components/Discover/helpers/just-added.tsx index 1c15dc35..635ee21f 100644 --- a/src/components/Discover/helpers/just-added.tsx +++ b/src/components/Discover/helpers/just-added.tsx @@ -7,9 +7,9 @@ import { useNavigation } from '@react-navigation/native' import DiscoverStackParamList from '../../../screens/Discover/types' import navigationRef from '../../../../navigation' import { useRecentlyAddedAlbums } from '../../../api/queries/album' -import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated' +import AnimatedRow from '../../Global/helpers/animated-row' -export default function RecentlyAdded(): React.JSX.Element | null { +export default function RecentlyAdded(): React.JSX.Element { const recentlyAddedAlbumsInfinityQuery = useRecentlyAddedAlbums() const navigation = useNavigation>() @@ -18,15 +18,7 @@ export default function RecentlyAdded(): React.JSX.Element | null { recentlyAddedAlbumsInfinityQuery.data && recentlyAddedAlbumsInfinityQuery.data.length > 0 return recentlyAddedExists ? ( - + { @@ -64,6 +56,8 @@ export default function RecentlyAdded(): React.JSX.Element | null { /> )} /> - - ) : null + + ) : ( + <> + ) } diff --git a/src/components/Discover/helpers/public-playlists.tsx b/src/components/Discover/helpers/public-playlists.tsx index 97eaf8a4..a1b39aa2 100644 --- a/src/components/Discover/helpers/public-playlists.tsx +++ b/src/components/Discover/helpers/public-playlists.tsx @@ -9,9 +9,9 @@ import DiscoverStackParamList from '../../../screens/Discover/types' import navigationRef from '../../../../navigation' import { useJellifyServer } from '../../../stores' import { usePublicPlaylists } from '../../../api/queries/playlist' -import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated' +import AnimatedRow from '../../Global/helpers/animated-row' -export default function PublicPlaylists(): React.JSX.Element | null { +export default function PublicPlaylists(): React.JSX.Element { const { data: playlists, fetchNextPage, @@ -29,15 +29,7 @@ export default function PublicPlaylists(): React.JSX.Element | null { const publicPlaylistsExist = playlists && playlists.length > 0 return publicPlaylistsExist ? ( - + { @@ -80,6 +72,8 @@ export default function PublicPlaylists(): React.JSX.Element | null { /> )} /> - - ) : null + + ) : ( + <> + ) } diff --git a/src/components/Discover/helpers/suggested-albums.tsx b/src/components/Discover/helpers/suggested-albums.tsx index 585a0728..94e0c5ca 100644 --- a/src/components/Discover/helpers/suggested-albums.tsx +++ b/src/components/Discover/helpers/suggested-albums.tsx @@ -1,6 +1,5 @@ import navigationRef from '../../../../navigation' import { formatArtistNames } from '../../../utils/formatting/artist-names' -import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated' import ItemCard from '../../Global/components/item-card' import HorizontalCardList from '../../Global/components/horizontal-list' import { XStack } from 'tamagui' @@ -10,6 +9,7 @@ import { useNavigation } from '@react-navigation/native' import { NativeStackNavigationProp } from '@react-navigation/native-stack' import DiscoverStackParamList from '../../../screens/Discover/types' import { useDiscoverAlbums } from '../../../api/queries/suggestions' +import AnimatedRow from '../../Global/helpers/animated-row' export default function SuggestedAlbums() { const suggestedAlbumsInfiniteQuery = useDiscoverAlbums() @@ -20,15 +20,7 @@ export default function SuggestedAlbums() { suggestedAlbumsInfiniteQuery.data && suggestedAlbumsInfiniteQuery.data.length > 0 return suggestedAlbumsExist ? ( - + { @@ -64,6 +56,8 @@ export default function SuggestedAlbums() { /> )} /> - - ) : null + + ) : ( + <> + ) } diff --git a/src/components/Discover/helpers/suggested-artists.tsx b/src/components/Discover/helpers/suggested-artists.tsx index f29ffa7a..e0208940 100644 --- a/src/components/Discover/helpers/suggested-artists.tsx +++ b/src/components/Discover/helpers/suggested-artists.tsx @@ -7,10 +7,10 @@ import { useNavigation } from '@react-navigation/native' import DiscoverStackParamList from '../../../screens/Discover/types' import navigationRef from '../../../../navigation' import { pickFirstGenre } from '../../../utils/formatting/genres' -import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated' import { useDiscoverArtists } from '../../../api/queries/suggestions' +import AnimatedRow from '../../Global/helpers/animated-row' -export default function SuggestedArtists(): React.JSX.Element | null { +export default function SuggestedArtists(): React.JSX.Element { const suggestedArtistsInfiniteQuery = useDiscoverArtists() const navigation = useNavigation>() @@ -19,15 +19,7 @@ export default function SuggestedArtists(): React.JSX.Element | null { suggestedArtistsInfiniteQuery.data && suggestedArtistsInfiniteQuery.data.length > 0 return suggestedArtistsExist ? ( - + { @@ -63,6 +55,8 @@ export default function SuggestedArtists(): React.JSX.Element | null { /> )} /> - - ) : null + + ) : ( + <> + ) } diff --git a/src/components/Global/helpers/animated-row.tsx b/src/components/Global/helpers/animated-row.tsx new file mode 100644 index 00000000..00352ba9 --- /dev/null +++ b/src/components/Global/helpers/animated-row.tsx @@ -0,0 +1,28 @@ +import Animated, { + FadeIn, + ReduceMotion, + FadeOut, + LinearTransition, + Easing, +} from 'react-native-reanimated' + +interface AnimatedRowProps { + children: React.ReactNode + testID?: string +} + +export default function AnimatedRow({ children, testID }: AnimatedRowProps) { + return ( + + {children} + + ) +} diff --git a/src/components/Home/helpers/frequent-artists.tsx b/src/components/Home/helpers/frequent-artists.tsx index 68e6f628..af3f55e4 100644 --- a/src/components/Home/helpers/frequent-artists.tsx +++ b/src/components/Home/helpers/frequent-artists.tsx @@ -11,7 +11,7 @@ import { RootStackParamList } from '../../../screens/types' import { useFrequentlyPlayedArtists } from '../../../api/queries/frequents' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client' import { pickFirstGenre } from '../../../utils/formatting/genres' -import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated' +import AnimatedRow from '../../Global/helpers/animated-row' export default function FrequentArtists(): React.JSX.Element { const navigation = useNavigation>() @@ -41,14 +41,7 @@ export default function FrequentArtists(): React.JSX.Element { ) return frequentArtistsInfiniteQuery.data ? ( - + { @@ -63,7 +56,7 @@ export default function FrequentArtists(): React.JSX.Element { data={frequentArtistsInfiniteQuery.data.slice(0, horizontalItems) ?? []} renderItem={renderItem} /> - + ) : ( <> ) diff --git a/src/components/Home/helpers/frequent-tracks.tsx b/src/components/Home/helpers/frequent-tracks.tsx index 981e7537..9d7ec559 100644 --- a/src/components/Home/helpers/frequent-tracks.tsx +++ b/src/components/Home/helpers/frequent-tracks.tsx @@ -10,7 +10,7 @@ import HomeStackParamList from '../../../screens/Home/types' import { useNavigation } from '@react-navigation/native' import { RootStackParamList } from '../../../screens/types' import { useFrequentlyPlayedTracks } from '../../../api/queries/frequents' -import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated' +import AnimatedRow from '../../Global/helpers/animated-row' export default function FrequentlyPlayedTracks(): React.JSX.Element { const tracksInfiniteQuery = useFrequentlyPlayedTracks() @@ -23,14 +23,7 @@ export default function FrequentlyPlayedTracks(): React.JSX.Element { const { horizontalItems } = useDisplayContext() return tracksInfiniteQuery.data ? ( - + { @@ -75,7 +68,7 @@ export default function FrequentlyPlayedTracks(): React.JSX.Element { /> )} /> - + ) : ( <> ) diff --git a/src/components/Home/helpers/recent-artists.tsx b/src/components/Home/helpers/recent-artists.tsx index b66b80d2..63b108dc 100644 --- a/src/components/Home/helpers/recent-artists.tsx +++ b/src/components/Home/helpers/recent-artists.tsx @@ -11,7 +11,7 @@ import HomeStackParamList from '../../../screens/Home/types' import { useRecentArtists } from '../../../api/queries/recents' import { pickFirstGenre } from '../../../utils/formatting/genres' import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models/base-item-dto' -import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated' +import AnimatedRow from '../../Global/helpers/animated-row' export default function RecentArtists(): React.JSX.Element { const recentArtistsInfiniteQuery = useRecentArtists() @@ -47,14 +47,7 @@ export default function RecentArtists(): React.JSX.Element { ) return recentArtistsInfiniteQuery.data ? ( - +
Recent Artists
@@ -64,7 +57,7 @@ export default function RecentArtists(): React.JSX.Element { data={recentArtistsInfiniteQuery.data.slice(0, horizontalItems)} renderItem={renderItem} /> -
+ ) : ( <> ) diff --git a/src/components/Home/helpers/recently-played.tsx b/src/components/Home/helpers/recently-played.tsx index 514b22a2..d1d2c86d 100644 --- a/src/components/Home/helpers/recently-played.tsx +++ b/src/components/Home/helpers/recently-played.tsx @@ -11,8 +11,8 @@ import { useDisplayContext } from '../../../providers/Display/display-provider' import { useNavigation } from '@react-navigation/native' import HomeStackParamList from '../../../screens/Home/types' import { useRecentlyPlayedTracks } from '../../../api/queries/recents' -import Animated, { Easing, FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated' import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client' +import AnimatedRow from '../../Global/helpers/animated-row' export default function RecentlyPlayed(): React.JSX.Element { const navigation = useNavigation>() @@ -44,14 +44,7 @@ export default function RecentlyPlayed(): React.JSX.Element { } return tracksInfiniteQuery.data ? ( - + { @@ -88,7 +81,7 @@ export default function RecentlyPlayed(): React.JSX.Element { /> )} /> - + ) : ( <> ) From c01d1958768a4464a6d3f392cff50f4c68fabc00 Mon Sep 17 00:00:00 2001 From: anultravioletaurora Date: Sun, 22 Feb 2026 21:34:37 +0000 Subject: [PATCH 2/3] [skip actions] version bump --- android/app/build.gradle | 4 ++-- ios/Jellify.xcodeproj/project.pbxproj | 12 ++++++------ package.json | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index df004d7c..360d058e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -88,8 +88,8 @@ android { applicationId "com.cosmonautical.jellify" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 176 - versionName "1.0.17" + versionCode 177 + versionName "1.0.18" } signingConfigs { diff --git a/ios/Jellify.xcodeproj/project.pbxproj b/ios/Jellify.xcodeproj/project.pbxproj index 403f1806..3afa1adc 100644 --- a/ios/Jellify.xcodeproj/project.pbxproj +++ b/ios/Jellify.xcodeproj/project.pbxproj @@ -543,7 +543,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 284; + CURRENT_PROJECT_VERSION = 285; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG; ENABLE_BITCODE = NO; @@ -554,7 +554,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.17; + MARKETING_VERSION = 1.0.18; NEW_SETTING = ""; OTHER_LDFLAGS = ( "$(inherited)", @@ -585,7 +585,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 284; + CURRENT_PROJECT_VERSION = 285; DEVELOPMENT_TEAM = WAH9CZ8BPG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -595,7 +595,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.17; + MARKETING_VERSION = 1.0.18; NEW_SETTING = ""; OTHER_LDFLAGS = ( "$(inherited)", @@ -823,7 +823,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 284; + CURRENT_PROJECT_VERSION = 285; DEVELOPMENT_TEAM = WAH9CZ8BPG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = WAH9CZ8BPG; ENABLE_USER_SCRIPT_SANDBOXING = NO; @@ -834,7 +834,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.17; + MARKETING_VERSION = 1.0.18; NEW_SETTING = ""; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index afa0f272..716ef974 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jellify", - "version": "1.0.17", + "version": "1.0.18", "private": true, "scripts": { "init-android": "bun i", From 038f0d071544fdd56379097273ed0c4ea4fd2e49 Mon Sep 17 00:00:00 2001 From: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:15:24 -0600 Subject: [PATCH 3/3] refactor: search query hook (#1014) * refactor: search query hook update staleTime and gcTime for search query move query into queries folder alongside function call update usage in search component * debounce searches properly --- src/api/queries/search/index.ts | 19 ++++++++ .../{search.ts => search/utils/index.ts} | 10 ++-- src/components/Search/index.tsx | 48 ++++++++----------- 3 files changed, 43 insertions(+), 34 deletions(-) create mode 100644 src/api/queries/search/index.ts rename src/api/queries/{search.ts => search/utils/index.ts} (87%) diff --git a/src/api/queries/search/index.ts b/src/api/queries/search/index.ts new file mode 100644 index 00000000..db0866d8 --- /dev/null +++ b/src/api/queries/search/index.ts @@ -0,0 +1,19 @@ +import { QueryKeys } from '../../../enums/query-keys' +import { useQuery } from '@tanstack/react-query' +import { fetchSearchResults } from './utils' +import { ONE_MINUTE } from '../../../constants/query-client' +import { useJellifyLibrary } from '../../../stores' + +const useSearchResults = (searchString: string | undefined) => { + const [library] = useJellifyLibrary() + + return useQuery({ + queryKey: [QueryKeys.Search, library?.musicLibraryId, searchString], + queryFn: () => fetchSearchResults(library?.musicLibraryId, searchString), + staleTime: ONE_MINUTE * 10, // Cache results for 10 minutes + gcTime: ONE_MINUTE * 15, // Garbage collect after 15 minutes + enabled: !!library?.musicLibraryId && !!searchString, // Only run if we have a library ID and a search string + }) +} + +export default useSearchResults diff --git a/src/api/queries/search.ts b/src/api/queries/search/utils/index.ts similarity index 87% rename from src/api/queries/search.ts rename to src/api/queries/search/utils/index.ts index 693d4984..b8b43280 100644 --- a/src/api/queries/search.ts +++ b/src/api/queries/search/utils/index.ts @@ -1,9 +1,8 @@ import { BaseItemDto } from '@jellyfin/sdk/lib/generated-client/models' import { getItemsApi } from '@jellyfin/sdk/lib/utils/api' import { isEmpty, isUndefined, trim } from 'lodash' -import QueryConfig from '../../configs/query.config' -import { Api } from '@jellyfin/sdk' -import { JellifyUser } from '../../types/JellifyUser' +import QueryConfig from '../../../../configs/query.config' +import { getApi, getUser } from '../../../../stores' /** * Performs a search for items against the Jellyfin server, trimming whitespace * around the search term for the best possible results. @@ -11,12 +10,13 @@ import { JellifyUser } from '../../types/JellifyUser' * @returns A promise of a BaseItemDto array, be it empty or not */ export async function fetchSearchResults( - api: Api | undefined, - user: JellifyUser | undefined, libraryId: string | undefined, searchString: string | undefined, ): Promise { return new Promise((resolve, reject) => { + const api = getApi() + const user = getUser() + if (isEmpty(searchString)) resolve([]) if (isUndefined(api)) return reject('Client instance not set') diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 2f7c3f21..371c8a5e 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -1,11 +1,8 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import Input from '../Global/helpers/input' import { H5, Text } from '../Global/helpers/text' import ItemRow from '../Global/components/item-row' import { NativeStackNavigationProp } from '@react-navigation/native-stack' -import { QueryKeys } from '../../enums/query-keys' -import { fetchSearchResults } from '../../api/queries/search' -import { useQuery } from '@tanstack/react-query' import { getToken, H3, Spinner, YStack } from 'tamagui' import Suggestions from './suggestions' import { isEmpty } from 'lodash' @@ -13,7 +10,6 @@ import HorizontalCardList from '../Global/components/horizontal-list' import ItemCard from '../Global/components/item-card' import SearchParamList from '../../screens/Search/types' import { closeAllSwipeableRows } from '../Global/components/swipeable-row-registry' -import { getApi, getUser, useJellifyLibrary } from '../../stores' import { FlashList } from '@shopify/flash-list' import navigationRef from '../../../navigation' import { StackActions } from '@react-navigation/native' @@ -22,41 +18,35 @@ import Track from '../Global/components/Track' import { pickRandomItemFromArray } from '../../utils/parsing/random' import { SEARCH_PLACEHOLDERS } from '../../configs/placeholder.config' import { formatArtistName } from '../../utils/formatting/artist-names' +import useSearchResults from '../../api/queries/search' export default function Search({ navigation, }: { navigation: NativeStackNavigationProp }): React.JSX.Element { - const api = getApi() - const user = getUser() - const [library] = useJellifyLibrary() + /** + * Raw text input value from the user, updates immediately as they type + */ + const [inputValue, setInputValue] = useState(undefined) + /** + * Debounced search string that updates 500ms after the user stops typing, used to trigger the search query + * which is keyed off of this value for caching. + */ const [searchString, setSearchString] = useState(undefined) - const { - data: items, - refetch, - isFetching: fetchingResults, - } = useQuery({ - queryKey: [QueryKeys.Search, library?.musicLibraryId, searchString], - queryFn: () => fetchSearchResults(api, user, library?.musicLibraryId, searchString), - }) + useEffect(() => { + const timeout = setTimeout(() => { + setSearchString(inputValue || undefined) + }, 500) + return () => clearTimeout(timeout) + }, [inputValue]) - const search = () => { - let timeout: ReturnType - - return () => { - clearTimeout(timeout) - timeout = setTimeout(() => { - refetch() - }, 1000) - } - } + const { data: items, isFetching: fetchingResults } = useSearchResults(searchString) const handleSearchStringUpdate = (value: string | undefined) => { - setSearchString(value) - search() + setInputValue(value || undefined) } const handleScrollBeginDrag = () => { @@ -90,7 +80,7 @@ export default function Search({