diff --git a/.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch b/.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch deleted file mode 100644 index 13886e9be1f..00000000000 --- a/.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch +++ /dev/null @@ -1,62 +0,0 @@ -diff --git a/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java b/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -index a5bb95eec3337b93a2338a2869a2bda176c91cae..87817688eb280c2f702c26dc35558c6a0a4db1ea 100644 ---- a/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -+++ b/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -@@ -42,12 +42,20 @@ public class ReactSliderManager extends SimpleViewManager implement - public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { - ReactSlider slider = (ReactSlider)seekbar; - -- if(progress < slider.getLowerLimit()) { -- progress = slider.getLowerLimit(); -- seekbar.setProgress(progress); -- } else if (progress > slider.getUpperLimit()) { -- progress = slider.getUpperLimit(); -- seekbar.setProgress(progress); -+ // During initialization, lowerLimit can be greater than upperLimit. -+ // -+ // If a change event is received during this, we need a check to prevent -+ // infinite recursion. -+ // -+ // Issue: https://github.com/callstack/react-native-slider/issues/571 -+ if (slider.getLowerLimit() <= slider.getUpperLimit()) { -+ if(progress < slider.getLowerLimit()) { -+ progress = slider.getLowerLimit(); -+ seekbar.setProgress(progress); -+ } else if (progress > slider.getUpperLimit()) { -+ progress = slider.getUpperLimit(); -+ seekbar.setProgress(progress); -+ } - } - - ReactContext reactContext = (ReactContext) seekbar.getContext(); -diff --git a/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java b/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -index 3ff5930f85a3cd92c2549925f41058abb188a57e..ab3681fdfe0b736c97020e1434e450c8183e6f18 100644 ---- a/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -+++ b/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java -@@ -30,12 +30,20 @@ public class ReactSliderManager extends SimpleViewManager { - public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { - ReactSlider slider = (ReactSlider)seekbar; - -- if(progress < slider.getLowerLimit()) { -- progress = slider.getLowerLimit(); -- seekbar.setProgress(progress); -- } else if(progress > slider.getUpperLimit()) { -- progress = slider.getUpperLimit(); -- seekbar.setProgress(progress); -+ // During initialization, lowerLimit can be greater than upperLimit. -+ // -+ // If a change event is received during this, we need a check to prevent -+ // infinite recursion. -+ // -+ // Issue: https://github.com/callstack/react-native-slider/issues/571 -+ if (slider.getLowerLimit() <= slider.getUpperLimit()) { -+ if(progress < slider.getLowerLimit()) { -+ progress = slider.getLowerLimit(); -+ seekbar.setProgress(progress); -+ } else if (progress > slider.getUpperLimit()) { -+ progress = slider.getUpperLimit(); -+ seekbar.setProgress(progress); -+ } - } - - ReactContext reactContext = (ReactContext) seekbar.getContext(); diff --git a/package.json b/package.json index 91b5823fc57..29c357728bf 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,6 @@ "app-builder-lib@24.4.0": "patch:app-builder-lib@npm%3A24.4.0#./.yarn/patches/app-builder-lib-npm-24.4.0-05322ff057.patch", "nanoid": "patch:nanoid@npm%3A3.3.7#./.yarn/patches/nanoid-npm-3.3.7-98824ba130.patch", "pdfjs-dist": "patch:pdfjs-dist@npm%3A3.11.174#./.yarn/patches/pdfjs-dist-npm-3.11.174-67f2fee6d6.patch", - "@react-native-community/slider": "patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch", "husky": "patch:husky@npm%3A3.1.0#./.yarn/patches/husky-npm-3.1.0-5cc13e4e34.patch", "chokidar@^2.0.0": "3.5.3", "react-native@0.74.1": "patch:react-native@npm%3A0.74.1#./.yarn/patches/react-native-npm-0.74.1-754c02ae9e.patch", diff --git a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx index 3a5f9783b4d..9d12c6bcbbb 100644 --- a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx @@ -141,18 +141,24 @@ class ConfigScreenComponent extends React.Component { public async switchSection(name: string) { const section = this.sectionByName(name); let screenName = ''; - if (section.isScreen) { - screenName = section.name; + if (section.isScreen || name === 'plugins') { + if (section.isScreen) { + screenName = section.name; + } if (this.hasChanges()) { const ok = await shim.showConfirmationDialog(_('This will open a new screen. Save your current changes?')); if (ok) { - await shared.saveSettings(this); + const result = await shared.saveSettings(this); + if (result === false) return false; + } else { + return false; } } } this.setState({ selectedSectionName: section.name, screenName: screenName }); + return true; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied diff --git a/packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx b/packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx index 875a361941a..cdd732327cf 100644 --- a/packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx +++ b/packages/app-desktop/gui/ConfigScreen/controls/SettingComponent.tsx @@ -1,7 +1,7 @@ import Setting, { AppType, SettingItemSubType } from '@joplin/lib/models/Setting'; import { themeStyle } from '@joplin/lib/theme'; import * as React from 'react'; -import { useCallback, useId } from 'react'; +import { useCallback, useId, useState } from 'react'; import control_PluginsStates from './plugins/PluginsStates'; import bridge from '../../../services/bridge'; import { _ } from '@joplin/lib/locale'; @@ -70,6 +70,7 @@ const SettingComponent: React.FC = props => { const inputId = useId(); const descriptionId = useId(); const descriptionComp = ; + const [valueState, setValueState] = useState(props.value?.toString()); if (key in settingKeyToControl) { const CustomSettingComponent = settingKeyToControl[key]; @@ -321,10 +322,9 @@ const SettingComponent: React.FC = props => { ); } } else if (md.type === Setting.TYPE_INT) { - const value = props.value as number; - const onNumChange: React.ChangeEventHandler = (event) => { - updateSettingValue(key, event.target.value); + setValueState(event.target.value); + updateSettingValue(key, Number.isInteger(Number(event.target.value)) ? event.target.value : ''); // Prevent invalid values being mapped to 0 }; const label = [md.label()]; @@ -336,7 +336,7 @@ const SettingComponent: React.FC = props => { { @@ -263,22 +265,21 @@ class ConfigScreenComponent extends BaseScreenComponent 0; } - private async promptSaveChanges(): Promise { + private async promptSaveChanges(): Promise { if (this.hasUnsavedChanges()) { const response = await shim.showMessageBox(_('There are unsaved changes.'), { buttons: [_('Save changes'), _('Discard changes')], }); if (response === 0) { - await this.saveButton_press(); + return await this.saveButton_press(); } } + + return true; } private handleNavigateToNewScreen = async (): Promise => { - await this.promptSaveChanges(); - - // Continue navigation - return false; + return !(await this.promptSaveChanges()); }; private handleBackButtonPress = (): boolean => { @@ -301,8 +302,10 @@ class ConfigScreenComponent extends BaseScreenComponent { - await this.promptSaveChanges(); - await goBack(); + const result = await this.promptSaveChanges(); + if (result) { + await goBack(); + } })(); return true; } diff --git a/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx b/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx index 748ffdaace1..3d19b988f70 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx @@ -5,12 +5,11 @@ import { View, Text, TextInput } from 'react-native'; import Setting, { AppType } from '@joplin/lib/models/Setting'; import Dropdown from '../../Dropdown'; import { ConfigScreenStyles } from './configScreenStyles'; -import Slider from '@react-native-community/slider'; import SettingsToggle from './SettingsToggle'; import FileSystemPathSelector from './FileSystemPathSelector'; import shim from '@joplin/lib/shim'; import { themeStyle } from '../../global-style'; -import { useId } from 'react'; +import { useId, useState } from 'react'; interface Props { settingId: string; @@ -41,6 +40,7 @@ const SettingComponent: React.FunctionComponent = props => { const containerStyles = props.styles.getContainerStyle(!!settingDescription); const labelId = useId(); + const [valueState, setValueState] = useState(props.value?.toString()); if (md.isEnum) { const value = props.value?.toString(); @@ -92,33 +92,35 @@ const SettingComponent: React.FunctionComponent = props => { /> ); } else if (md.type === Setting.TYPE_INT) { - const unitLabel = md.unitLabel ? md.unitLabel(props.value) : props.value; - const minimum = 'minimum' in md ? md.minimum : 0; - const maximum = 'maximum' in md ? md.maximum : 10; - const label = md.label(); + const label = md.unitLabel?.toString() !== undefined ? `${md.label()} (${md.unitLabel(md.value)})` : `${md.label()}`; - // Note: Do NOT add the minimumTrackTintColor and maximumTrackTintColor props - // on the Slider as they are buggy and can crash the app on certain devices. - // https://github.com/laurent22/joplin/issues/2733 - // https://github.com/react-native-community/react-native-slider/issues/161 return ( - - - {label} - - - {unitLabel} - + + + {label} + + void props.updateSettingValue(props.settingId, newValue)} - accessibilityHint={label} + style={styleSheet.settingControl} + defaultValue={valueState} + onChangeText={newValue => { + setValueState(newValue); + void props.updateSettingValue(props.settingId, Number.isInteger(Number(newValue)) ? newValue : ''); // Prevent invalid values being mapped to 0 + }} + maxLength={15} + secureTextEntry={!!md.secure} + aria-labelledby={labelId} + disableFullscreenUI={true} /> + {descriptionComp} ); } else if (md.type === Setting.TYPE_STRING) { @@ -149,7 +151,7 @@ const SettingComponent: React.FunctionComponent = props => { autoCapitalize="none" key="control" style={styleSheet.settingControl} - value={props.value} + defaultValue={props.value} onChangeText={(newValue: string) => void props.updateSettingValue(props.settingId, newValue)} secureTextEntry={!!md.secure} aria-labelledby={labelId} diff --git a/packages/app-mobile/ios/Podfile.lock b/packages/app-mobile/ios/Podfile.lock index 235701c7b8f..bc0aaa5fa82 100644 --- a/packages/app-mobile/ios/Podfile.lock +++ b/packages/app-mobile/ios/Podfile.lock @@ -1060,27 +1060,6 @@ PODS: - React-Core - react-native-safe-area-context (4.10.8): - React-Core - - react-native-slider (4.4.4): - - DoubleConversion - - glog - - hermes-engine - - RCT-Folly (= 2024.01.01.00) - - RCTRequired - - RCTTypeSafety - - React-Codegen - - React-Core - - React-debug - - React-Fabric - - React-featureflags - - React-graphics - - React-ImageManager - - React-NativeModulesApple - - React-RCTFabric - - React-rendererdebug - - React-utils - - ReactCommon/turbomodule/bridging - - ReactCommon/turbomodule/core - - Yoga - react-native-sqlite-storage (6.0.1): - React-Core - react-native-version-info (1.1.1): @@ -1442,7 +1421,6 @@ DEPENDENCIES: - react-native-rsa-native (from `../node_modules/react-native-rsa-native`) - "react-native-saf-x (from `../node_modules/@joplin/react-native-saf-x`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) - - "react-native-slider (from `../node_modules/@react-native-community/slider`)" - react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`) - react-native-version-info (from `../node_modules/react-native-version-info`) - react-native-webview (from `../node_modules/react-native-webview`) @@ -1594,8 +1572,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@joplin/react-native-saf-x" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" - react-native-slider: - :path: "../node_modules/@react-native-community/slider" react-native-sqlite-storage: :path: "../node_modules/react-native-sqlite-storage" react-native-version-info: @@ -1730,7 +1706,6 @@ SPEC CHECKSUMS: react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a react-native-saf-x: 2b561a9a31413dc594cf2b56a3a8736836b0cf9f react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371 - react-native-slider: 03f213d3ffbf919b16298c7896c1b60101d8e137 react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261 react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9 react-native-webview: 553abd09f58e340fdc7746c9e2ae096839e99911 diff --git a/packages/app-mobile/package.json b/packages/app-mobile/package.json index 70596fde41f..4d1a39f51c6 100644 --- a/packages/app-mobile/package.json +++ b/packages/app-mobile/package.json @@ -32,7 +32,6 @@ "@react-native-community/geolocation": "3.3.0", "@react-native-community/netinfo": "11.3.3", "@react-native-community/push-notification-ios": "1.11.0", - "@react-native-community/slider": "4.5.5", "assert-browserify": "2.0.0", "buffer": "6.0.3", "color": "3.2.1", diff --git a/packages/lib/components/shared/config/config-shared.ts b/packages/lib/components/shared/config/config-shared.ts index bcc81fb0b1b..2a0eb02af85 100644 --- a/packages/lib/components/shared/config/config-shared.ts +++ b/packages/lib/components/shared/config/config-shared.ts @@ -4,7 +4,7 @@ const ObjectUtils = require('../../../ObjectUtils'); const { _ } = require('../../../locale'); import { createSelector } from 'reselect'; import Logger from '@joplin/utils/Logger'; - +import shim, { MessageBoxType } from '../../../shim'; import { type ReactNode } from 'react'; import { type Registry } from '../../../registry'; import settingValidations from '../../../models/settings/settingValidations'; @@ -155,7 +155,11 @@ export const saveSettings = async (comp: ConfigScreenComponent) => { const validationMessage = await settingValidations(savedSettingKeys, comp.state.settings); if (validationMessage) { - alert(validationMessage); + await shim.showMessageBox(validationMessage, { + type: MessageBoxType.Error, + buttons: [_('OK')], + }); + return false; } diff --git a/packages/lib/models/Setting.test.ts b/packages/lib/models/Setting.test.ts index f9085bf93ef..772cdbdf2e7 100644 --- a/packages/lib/models/Setting.test.ts +++ b/packages/lib/models/Setting.test.ts @@ -456,4 +456,19 @@ describe('models/Setting', () => { await Setting.saveAll(); } }); + + test.each([ + ['1', 1], + ['-1', -1], + ['+1', 1], + ['', null], + [null, 0], + [undefined, 0], + ['1e3', 1000], + ['1e20', 1e20], + ['99999999999999999999', 1e20], // Value exceeds integer limit, output exhibits a rounding issue but still retains integer type + ])('should format integer type setting as an integer or null', (async (input, expectedOutput) => { + const value = Setting.formatValue('revisionService.ttlDays', input); + expect(value).toBe(expectedOutput); + })); }); diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 5c3352cf939..0e1928470e7 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -810,7 +810,14 @@ class Setting extends BaseModel { public static formatValue(key: string | SettingItemType, value: any) { const type = typeof key === 'string' ? this.settingMetadata(key).type : key; - if (type === SettingItemType.Int) return !value ? 0 : Math.floor(Number(value)); + if (type === SettingItemType.Int) { + if (value === '') { + // Set empty string as null instead of zero, so that validations will fail + return null; + } else { + return !value ? 0 : Math.floor(Number(value)); + } + } if (type === SettingItemType.Bool) { if (typeof value === 'string') { diff --git a/packages/lib/models/settings/settingValidations.test.ts b/packages/lib/models/settings/settingValidations.test.ts index 359edbd66b0..b6f52221303 100644 --- a/packages/lib/models/settings/settingValidations.test.ts +++ b/packages/lib/models/settings/settingValidations.test.ts @@ -32,4 +32,42 @@ describe('settingValidations', () => { } expect(await settingValidations(['sync.target'], { 'sync.target': newSyncTargetId })).toBe(''); }); + + test('should return error message for null value for setting without range', async () => { + const value = await settingValidations(['style.editor.contentMaxWidth'], { 'style.editor.contentMaxWidth': null }); + expect(value).toBe('Editor maximum width must be a valid whole number'); + }); + + test('should return error message for null value for setting with range', async () => { + const value = await settingValidations(['revisionService.ttlDays'], { 'revisionService.ttlDays': null }); + expect(value).toBe('Keep note history for must be a valid whole number'); + }); + + test.each( + [0, -1], + )('should return error message for too low integer values', async (input) => { + const value = await settingValidations(['revisionService.ttlDays'], { 'revisionService.ttlDays': input }); + expect(value).toBe('Keep note history for cannot be less than 1'); + }); + + test.each( + [731, 1e20], + )('should return error message for too high integer values', async (input) => { + const value = await settingValidations(['revisionService.ttlDays'], { 'revisionService.ttlDays': input }); + expect(value).toBe('Keep note history for cannot be greater than 730'); + }); + + test.each( + [-999999999999999, 0, 999999999999999], + )('should return empty string for valid integer values for setting without range', async (input) => { + const value = await settingValidations(['style.editor.contentMaxWidth'], { 'style.editor.contentMaxWidth': input }); + expect(value).toBe(''); + }); + + test.each( + [1, 300, 730], + )('should return empty string for valid integer values for setting with range', async (input) => { + const value = await settingValidations(['revisionService.ttlDays'], { 'revisionService.ttlDays': input }); + expect(value).toBe(''); + }); }); diff --git a/packages/lib/models/settings/settingValidations.ts b/packages/lib/models/settings/settingValidations.ts index 23cca5fcb58..7728b5b33ab 100644 --- a/packages/lib/models/settings/settingValidations.ts +++ b/packages/lib/models/settings/settingValidations.ts @@ -3,6 +3,7 @@ import shim from '../../shim'; import BaseItem from '../BaseItem'; import Resource from '../Resource'; import Setting from '../Setting'; +import { SettingItemType } from './types'; // Should return an error message if there's a problem, and an empty string if not. // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied @@ -37,24 +38,52 @@ const validations: Record = { return ''; }, +}; + +const validateInteger = async (key: string, newValue: number) => { + const md = Setting.settingMetadata(key); + const minimum = 'minimum' in md ? md.minimum : null; + const maximum = 'maximum' in md ? md.maximum : null; + + if (newValue === null || isNaN(newValue)) { + return _('%s must be a valid whole number', md.label()); + } + + if (maximum !== null && newValue > maximum) { + return _('%s cannot be greater than %s', md.label(), maximum); + } + + if (minimum !== null && newValue < minimum) { + return _('%s cannot be less than %s', md.label(), minimum); + } + return ''; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied -const validateSetting = async (settingName: string, oldValue: any, newValue: any) => { +const validateSetting = async (settingName: string, newValues: Record) => { + const md = Setting.settingMetadata(settingName); + const oldValue = Setting.value(settingName); // Needs to be set this way, rather than from Setting.settingMetadata + const newValue = newValues[settingName]; if (oldValue === newValue) return ''; - if (!validations[settingName]) return ''; + // Type based validations + if (md.type === SettingItemType.Int && !md.isEnum) { + const message = await validateInteger(settingName, newValue as number); + if (message !== '') return message; + } + + // Custom validations + if (!validations[settingName]) return ''; return await validations[settingName](oldValue, newValue); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied export default async (settingKeys: string[], newValues: Record) => { for (const key of settingKeys) { - const oldValue = Setting.value(key); - const newValue = newValues[key]; - const message = await validateSetting(key, oldValue, newValue); - return message; + const message = await validateSetting(key, newValues); + if (message !== '') return message; } + return ''; }; diff --git a/readme/licenses.md b/readme/licenses.md index 70f7ec197b7..3102f3096cc 100644 --- a/readme/licenses.md +++ b/readme/licenses.md @@ -3201,36 +3201,6 @@ From https://github.com/react-native-community/push-notification-ios. **MIT**: -``` -MIT License - -Copyright (c) 2020 react-native-community - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE.% -``` - -### @react-native-community/slider@4.4.4 - -From https://github.com/callstack/react-native-slider. - -**MIT**: - Copyright: Copyright (c) 2019 react-native-community See [Appendix B](#appendix-b-the-mit-license) for the full MIT license. diff --git a/yarn.lock b/yarn.lock index 2c460a54be8..97c9b6ad305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8368,7 +8368,6 @@ __metadata: "@react-native-community/geolocation": 3.3.0 "@react-native-community/netinfo": 11.3.3 "@react-native-community/push-notification-ios": 1.11.0 - "@react-native-community/slider": 4.5.5 "@react-native/babel-preset": 0.74.86 "@react-native/metro-config": 0.74.87 "@sqlite.org/sqlite-wasm": 3.46.0-build2 @@ -11190,20 +11189,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/slider@npm:4.4.4": - version: 4.4.4 - resolution: "@react-native-community/slider@npm:4.4.4" - checksum: 65d79b72d100aab75e9051315798935a2419202e157f5e35dedb4e2843ccdd93816b553c9a7a41642f5140e5a05e20326a27ef65e9ed9c53efc59d8755a5d91f - languageName: node - linkType: hard - -"@react-native-community/slider@patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch::locator=root%40workspace%3A.": - version: 4.4.4 - resolution: "@react-native-community/slider@patch:@react-native-community/slider@npm%3A4.4.4#./.yarn/patches/@react-native-community-slider-npm-4.4.4-d78e472f48.patch::version=4.4.4&hash=1120f2&locator=root%40workspace%3A." - checksum: c4397dd2e914f52e3d9b4058d3cf850e67d99c85a59492835647513af85ba62ba182c5c7655fd35d6f768155d45c0c8b5eb0adaad803165d9fec508b77b19a2b - languageName: node - linkType: hard - "@react-native/assets-registry@npm:0.74.83": version: 0.74.83 resolution: "@react-native/assets-registry@npm:0.74.83"