Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 20 additions & 25 deletions packages/react-native/Libraries/Blob/URL.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
*/

import type Blob from './Blob';

import NativeBlobModule from './NativeBlobModule';

let BLOB_URL_PREFIX = null;
Expand All @@ -18,9 +17,9 @@ if (
NativeBlobModule &&
typeof NativeBlobModule.getConstants().BLOB_URI_SCHEME === 'string'
) {
const constants = NativeBlobModule.getConstants();
// $FlowFixMe[incompatible-type] asserted above
// $FlowFixMe[unsafe-addition]
const constants = NativeBlobModule.getConstants();
BLOB_URL_PREFIX = constants.BLOB_URI_SCHEME + ':';
if (typeof constants.BLOB_URI_HOST === 'string') {
BLOB_URL_PREFIX += `//${constants.BLOB_URI_HOST}/`;
Expand Down Expand Up @@ -51,19 +50,17 @@ if (
* </resources>
* ```
*/

export {URLSearchParams} from './URLSearchParams';
export { URLSearchParams } from './URLSearchParams';

function validateBaseUrl(url: string) {
// from this MIT-licensed gist: https://gist.github.com/dperini/729294
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this

return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test(
url,
);
return /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)*(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/.test(url);
}

export class URL {
_url: string;
_searchParamsInstance: ?URLSearchParams = null;
_parsedUrl: URL;

static createObjectURL(blob: Blob): string {
if (BLOB_URL_PREFIX === null) {
Expand All @@ -76,7 +73,6 @@ export class URL {
// Do nothing.
}

// $FlowFixMe[missing-local-annot]
constructor(url: string, base: string | URL) {
let baseUrl = null;
if (!base || validateBaseUrl(url)) {
Expand Down Expand Up @@ -104,51 +100,54 @@ export class URL {
}
this._url = `${baseUrl}${url}`;
}

// Parsing the URL to use for accessors
this._parsedUrl = new globalThis.URL(this._url);
Comment on lines +104 to +105
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which JSVM was this tested with?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node version: v20.18.1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be tested with Hermes, not Node.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay will run it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v0.13.0

}

get hash(): string {
throw new Error('URL.hash is not implemented');
return this._parsedUrl.hash;
}

get host(): string {
throw new Error('URL.host is not implemented');
return this._parsedUrl.host;
}

get hostname(): string {
throw new Error('URL.hostname is not implemented');
return this._parsedUrl.hostname;
}

get href(): string {
return this.toString();
}

get origin(): string {
throw new Error('URL.origin is not implemented');
return this._parsedUrl.origin;
}

get password(): string {
throw new Error('URL.password is not implemented');
return this._parsedUrl.password;
}

get pathname(): string {
throw new Error('URL.pathname not implemented');
return this._parsedUrl.pathname;
}

get port(): string {
throw new Error('URL.port is not implemented');
return this._parsedUrl.port;
}

get protocol(): string {
throw new Error('URL.protocol is not implemented');
return this._parsedUrl.protocol;
}

get search(): string {
throw new Error('URL.search is not implemented');
return this._parsedUrl.search;
}

get searchParams(): URLSearchParams {
if (this._searchParamsInstance == null) {
this._searchParamsInstance = new URLSearchParams();
this._searchParamsInstance = new URLSearchParams(this._parsedUrl.search);
}
return this._searchParamsInstance;
}
Expand All @@ -158,16 +157,12 @@ export class URL {
}

toString(): string {
if (this._searchParamsInstance === null) {
return this._url;
}
// $FlowFixMe[incompatible-use]
const instanceString = this._searchParamsInstance.toString();
const instanceString = this._searchParamsInstance ? this._searchParamsInstance.toString() : '';
const separator = this._url.indexOf('?') > -1 ? '&' : '?';
return this._url + separator + instanceString;
return this._url + (instanceString ? separator + instanceString : '');
}

get username(): string {
throw new Error('URL.username is not implemented');
return this._parsedUrl.username;
}
}
16 changes: 16 additions & 0 deletions packages/react-native/Libraries/Blob/__tests__/URL-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,20 @@ describe('URL', function () {
const k = new URL('en-US/docs', 'https://developer.mozilla.org');
expect(k.href).toBe('https://developer.mozilla.org/en-US/docs');
});

it('should implement host, hostname, username, and password accessors correctly', () => {
const url = new URL('https://username:[email protected]:8080/en-US/docs?query=test#fragment');

// Test host
expect(url.host).toBe('developer.mozilla.org:8080');

// Test hostname
expect(url.hostname).toBe('developer.mozilla.org');

// Test username
expect(url.username).toBe('username');

// Test password
expect(url.password).toBe('password');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ import PullToRefreshViewNativeComponent, {

const Platform = require('../../Utilities/Platform');
const React = require('react');
const {useEffect, useRef} = React;

type IOSProps = $ReadOnly<{
/**
* Type definitions for iOS-specific properties
*/
type IOSProps = $ReadOnly<{|
/**
* The color of the refresh indicator.
*/
Expand All @@ -36,7 +40,10 @@ type IOSProps = $ReadOnly<{
title?: ?string,
}>;

type AndroidProps = $ReadOnly<{
/**
* Type definitions for Android-specific properties
*/
type AndroidProps = $ReadOnly<{|
/**
* Whether the pull to refresh functionality is enabled.
*/
Expand All @@ -55,7 +62,10 @@ type AndroidProps = $ReadOnly<{
size?: ?('default' | 'large'),
}>;

export type RefreshControlProps = $ReadOnly<{
/**
* The main RefreshControlProps type definition
*/
export type RefreshControlProps = $ReadOnly<{|
...ViewProps,
...IOSProps,
...AndroidProps,
Expand Down Expand Up @@ -120,6 +130,7 @@ export type RefreshControlProps = $ReadOnly<{
*
* __Note:__ `refreshing` is a controlled prop, this is why it needs to be set to true
* in the `onRefresh` function otherwise the refresh indicator will stop immediately.
* RefreshControl Component
*/
class RefreshControl extends React.Component<RefreshControlProps> {
_nativeRef: ?React.ElementRef<
Expand Down Expand Up @@ -175,6 +186,8 @@ class RefreshControl extends React.Component<RefreshControlProps> {
{...props}
ref={this._setNativeRef}
onRefresh={this._onRefresh}
onTouchStart={this._handleTouchStart}
onTouchMove={this._handleTouchMove}
/>
);
}
Expand All @@ -199,6 +212,29 @@ class RefreshControl extends React.Component<RefreshControlProps> {
) => {
this._nativeRef = ref;
};

/**
* Horizontal Gesture Handling for Android
*/
_handleTouchStart = (event) => {
if (Platform.OS === 'android' && event.nativeEvent.touches.length === 1) {
this._nativeRef?.prevTouchX = event.nativeEvent.touches[0].pageX;
}
};

_handleTouchMove = (event) => {
if (Platform.OS === 'android' && event.nativeEvent.touches.length === 1) {
const touchX = event.nativeEvent.touches[0].pageX;
const prevTouchX = this._nativeRef?.prevTouchX || 0;
const xDiff = Math.abs(touchX - prevTouchX);

if (xDiff > 5 && this._nativeRef) {
AndroidSwipeRefreshLayoutCommands.cancelRefreshGesture(
this._nativeRef,
);
}
}
};
}

module.exports = RefreshControl;
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,13 @@ - (void)refreshControlValueChanged
}
}

- (void)setTintColor:(UIColor *)tintColor {
if (_tintColor != tintColor) {
_tintColor = tintColor;
dispatch_async(dispatch_get_main_queue(), ^{
self.refreshControl.tintColor = tintColor;
});
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.events.NativeGestureUtil
import kotlin.math.abs

/** Basic extension of [SwipeRefreshLayout] with ReactNative-specific functionality. */
public class ReactSwipeRefreshLayout(reactContext: ReactContext) :
SwipeRefreshLayout(reactContext) {
SwipeRefreshLayout(reactContext) {

private var didLayout: Boolean = false
private var refreshing: Boolean = false
Expand All @@ -25,6 +26,7 @@ public class ReactSwipeRefreshLayout(reactContext: ReactContext) :
private var prevTouchX: Float = 0f
private var intercepted: Boolean = false
private var nativeGestureStarted: Boolean = false
private var isBeingDraggedHorizontally: Boolean = false

public override fun setRefreshing(refreshing: Boolean) {
this.refreshing = refreshing
Expand Down Expand Up @@ -111,12 +113,17 @@ public class ReactSwipeRefreshLayout(reactContext: ReactContext) :
MotionEvent.ACTION_DOWN -> {
prevTouchX = ev.x
intercepted = false
isBeingDraggedHorizontally = false
}
MotionEvent.ACTION_MOVE -> {
val eventX = ev.x
val xDiff = Math.abs(eventX - prevTouchX)
val xDiff = abs(eventX - prevTouchX)

if (intercepted || xDiff > touchSlop) {
if (xDiff > touchSlop) {
isBeingDraggedHorizontally = true
}

if (intercepted || isBeingDraggedHorizontally) {
intercepted = true
return false
}
Expand Down
Loading