react-native-elements#Input TypeScript Examples

The following examples show how to use react-native-elements#Input. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: HeaderListItem.tsx    From react-native-network-client with Apache License 2.0 6 votes vote down vote up
HeaderListItem = ({
    index,
    header,
    updateHeader,
    disabled,
    testID,
}: HeaderListItemProps) => {
    const updateHeaderKey = (key: string) =>
        updateHeader && updateHeader({ ...header, key }, index);
    const updateHeaderValue = (value: string) =>
        updateHeader && updateHeader({ ...header, value }, index);

    return (
        <ListItem>
            <Input
                containerStyle={{ flex: 1 }}
                onChangeText={updateHeaderKey}
                placeholder="key"
                value={header.key}
                disabled={disabled}
                testID={`${testID}.${index}.key.input`}
            />
            <Input
                containerStyle={{ flex: 1 }}
                onChangeText={updateHeaderValue}
                placeholder="value"
                value={header.value}
                disabled={disabled}
                testID={`${testID}.${index}.value.input`}
            />
        </ListItem>
    );
}
Example #2
Source File: ListHeaders.tsx    From react-native-network-client with Apache License 2.0 6 votes vote down vote up
ListHeaders = ({ headers }: ListHeadersProps) => (
    <>
        <View
            style={{
                flexDirection: "row",
                alignItems: "center",
                justifyContent: "space-between",
            }}
        >
            <Input
                placeholder="Headers"
                disabled={true}
                style={{ fontWeight: "bold", fontSize: 17, opacity: 1 }}
                containerStyle={{ height: 50 }}
                inputContainerStyle={{
                    borderColor: "rgba(255,255,255,0)",
                }}
            />
        </View>
        <View style={{ paddingHorizontal: 10, paddingBottom: 20 }}>
            {headers.map((header, index) => (
                <HeaderListItem
                    key={`header-${index}`}
                    header={header}
                    index={index}
                    disabled={true}
                    testID="list_headers.header_list_item"
                />
            ))}
        </View>
    </>
)
Example #3
Source File: NumericInput.tsx    From react-native-network-client with Apache License 2.0 6 votes vote down vote up
ClientNumericInput = (props: ClientNumericInputProps) => {
    const rightIcon = (
        <NumericInput
            value={props.value}
            onChange={props.onChange}
            totalHeight={35}
            minValue={props.minValue}
            valueType={props.valueType}
            step={props.step}
            extraTextInputProps={{testID: props.testID}}
        />
    );

    return (
        <Input
            placeholder={props.title}
            disabled={true}
            style={{ fontWeight: "bold", fontSize: 17, opacity: 1 }}
            containerStyle={{ height: 50 }}
            inputContainerStyle={{
                borderColor: "rgba(255,255,255,0)",
            }}
            rightIcon={rightIcon}
            labelStyle={{ flex: 12, flexWrap: "wrap", height: 100 }}
        />
    );
}
Example #4
Source File: LoginScreen.tsx    From magento_react_native_graphql with MIT License 5 votes vote down vote up
LoginScreen = ({ navigation }: Props): React.ReactElement => {
  const {
    values,
    loading,
    data,
    error,
    handleChange,
    handleSubmit,
  } = useLogin();

  useEffect(() => {
    if (data?.generateCustomerToken?.token) {
      showMessage({
        message: translate('common.success'),
        description: translate('loginScreen.successMessage'),
        type: 'success',
      });
      navigation.navigate(Routes.NAVIGATION_TO_HOME_SCREEN);
    }
  }, [data]);

  useEffect(() => {
    if (error) {
      showMessage({
        message: translate('common.error'),
        description: error.message ?? translate('errors.genericError'),
        type: 'danger',
      });
    }
  }, [error]);

  return (
    <GenericTemplate style={styles.container}>
      <Input
        value={values.email}
        editable={!loading}
        leftIcon={{ name: 'email' }}
        placeholder="[email protected]"
        label={translate('common.email')}
        onChangeText={handleChange('email')}
      />
      <Input
        secureTextEntry={values.secureTextEntry}
        editable={!loading}
        value={values.password}
        leftIcon={{ name: 'lock' }}
        rightIcon={{
          name: values.secureTextEntry ? 'eye' : 'eye-off',
          type: 'material-community',
          onPress: handleChange('secureTextEntry').bind(
            {},
            !values.secureTextEntry,
          ),
        }}
        label={translate('common.password')}
        onChangeText={handleChange('password')}
        placeholder={translate('common.password')}
      />
      <Button
        loading={loading}
        onPress={handleSubmit}
        title={translate('common.login')}
        containerStyle={styles.submitButton}
      />
      <Button
        type="clear"
        disabled={loading}
        title={translate('loginScreen.noAccount')}
        onPress={() => navigation.navigate(Routes.NAVIGATION_TO_SIGNUP_SCREEN)}
      />
    </GenericTemplate>
  );
}
Example #5
Source File: AddHeaders.tsx    From react-native-network-client with Apache License 2.0 5 votes vote down vote up
AddHeaders = ({ onHeadersChanged }: AddHeadersProps) => {
    const [headers, setHeaders] = useState<Header[]>([{ key: "", value: "" }]);
    const addEmptyHeader = () =>
        setHeaders([...headers, { key: "", value: "" }]);
    const updateHeader = (header: Header, index: number) => {
        const updatedHeaders = [...headers];
        updatedHeaders[index] = header;
        setHeaders(updatedHeaders);
        onHeadersChanged(updatedHeaders);
    };

    const rightIcon = (
        <Button
            type="clear"
            icon={<Icon name="add-circle" size={24} />}
            onPress={addEmptyHeader}
            testID="add_empty_header.button"
        />
    );

    return (
        <>
            <View
                style={{
                    flexDirection: "row",
                    alignItems: "center",
                    justifyContent: "space-between",
                }}
            >
                <Input
                    placeholder="Headers"
                    disabled={true}
                    style={{ fontWeight: "bold", fontSize: 17, opacity: 1 }}
                    containerStyle={{ height: 50 }}
                    inputContainerStyle={{
                        borderColor: "rgba(255,255,255,0)",
                    }}
                    rightIcon={rightIcon}
                />
            </View>
            <View style={{ paddingHorizontal: 10, paddingBottom: 20 }}>
                {headers.map((header, index) => (
                    <HeaderListItem
                        key={`header-${index}`}
                        header={header}
                        index={index}
                        updateHeader={updateHeader}
                        testID="add_headers.header_list_item"
                    />
                ))}
            </View>
        </>
    );
}
Example #6
Source File: AddMultipart.tsx    From react-native-network-client with Apache License 2.0 5 votes vote down vote up
AddMultiparts = (props: AddMultipartsProps) => {
    const [multiparts, setMultiparts] = useState<Multipart[]>([
        { key: "", value: "" },
    ]);

    const removeMultipart = (index: number) => {
        const multis = [...multiparts];
        multis.splice(index, 1);
        setMultiparts(multis);
    };

    const addEmptyMultipart = () =>
        setMultiparts([...multiparts, { key: "", value: "" }]);

    const updateMultipart = (multipart: Multipart, index: number) => {
        const updatedMultiparts = [...multiparts];
        updatedMultiparts[index] = multipart;
        setMultiparts(updatedMultiparts);

        const mdata = updatedMultiparts.reduce<Record<string, string>>(
            (prev, cur) => {
                prev[cur.key] = cur.value;
                return prev;
            },
            {}
        );

        props.onMultipartsChanged(mdata);
    };

    const rightIcon = (
        <Button
            type="clear"
            icon={<Icon name="add-circle" size={24} />}
            onPress={addEmptyMultipart}
            testID="add_empty_multipart.button"
        />
    );

    return (
        <>
            <View
                style={{
                    flexDirection: "row",
                    alignItems: "center",
                    justifyContent: "space-between",
                }}
            >
                <Input
                    placeholder="Multiparts"
                    disabled={true}
                    style={{ fontWeight: "bold", fontSize: 17, opacity: 1 }}
                    containerStyle={{ height: 50 }}
                    inputContainerStyle={{
                        borderColor: "rgba(255,255,255,0)",
                    }}
                    rightIcon={rightIcon}
                />
            </View>
            <View style={{ paddingHorizontal: 10, paddingBottom: 20 }}>
                {multiparts.map((multipart, index) => (
                    <MultipartListItem
                        key={`multipart-${index}`}
                        multipart={multipart}
                        index={index}
                        updateMultipart={updateMultipart}
                        removeMultipart={removeMultipart}
                        testID="add_multiparts.multipart_list_item"
                    />
                ))}
            </View>
        </>
    );
}
Example #7
Source File: MultipartListItem.tsx    From react-native-network-client with Apache License 2.0 5 votes vote down vote up
MultipartListItem = ({
    index,
    multipart,
    updateMultipart,
    removeMultipart,
    disabled,
    testID,
}: MultipartListItemProps) => {
    const updateMultipartKey = (key: string) =>
        updateMultipart && updateMultipart({ ...multipart, key }, index);
    const updateMultipartValue = (value: string) =>
        updateMultipart && updateMultipart({ ...multipart, value }, index);

    return (
        <ListItem>
            <Input
                containerStyle={{ flex: 1 }}
                onChangeText={updateMultipartKey}
                placeholder="key"
                value={multipart.key}
                disabled={disabled}
                testID={`${testID}.${index}.key.input`}
            />
            <Input
                containerStyle={{ flex: 1 }}
                onChangeText={updateMultipartValue}
                placeholder="value"
                value={multipart.value}
                disabled={disabled}
                testID={`${testID}.${index}.value.input`}
            />
            <Button
                type="clear"
                icon={<Icon name="trash" size={24} />}
                onPress={() => removeMultipart(index)}
                testID="remove_multipart.button"
            />
        </ListItem>
    );
}
Example #8
Source File: APIClientFastImageScreen.tsx    From react-native-network-client with Apache License 2.0 5 votes vote down vote up
APIClientFastImageScreen = ({ route }: APIClientFastImageScreenProps) => {
    const {
        item: { client },
    } = route.params;
    const [imageUrl, setImageUrl] = useState(
        `${client.baseUrl}/api/v4/files/xjiid3qaa38kjxxwr9mxbfxyco`
    );
    const [loading, setLoading] = useState(false);
    const [errored, setErrored] = useState(false);

    const onLoadStart = () => setLoading(true);
    const onLoadEnd = () => setLoading(false);
    const onLoad = () => setErrored(false);
    const onError = () => setErrored(true);

    return (
        <SafeAreaView style={{ flex: 1 }}>
            <Input
                label="Image URL"
                placeholder={`${client.baseUrl}/image.png`}
                value={imageUrl}
                onChangeText={setImageUrl}
                autoCapitalize="none"
                testID="api_client_fast_image.image_url.input"
            />
            <View
                style={{
                    flex: 1,
                    justifyContent: "center",
                    alignItems: "center",
                }}
            >
                {loading && <ActivityIndicator />}
                {errored && (
                    <Icon
                        name="image-not-supported"
                        size={100}
                        testID="api_client_fast_image.image_not_supported.icon"
                    />
                )}
                <View style={{ flex: 1 }}>
                    <FastImage
                        source={{ uri: imageUrl }}
                        onLoadStart={onLoadStart}
                        onLoadEnd={onLoadEnd}
                        onLoad={onLoad}
                        onError={onError}
                        resizeMode={FastImage.resizeMode.contain}
                        style={{ height: 400, width: 400 }}
                        testID="api_client_fast_image.fast_image"
                    />
                </View>
            </View>
        </SafeAreaView>
    );
}
Example #9
Source File: APIClientDownloadScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
APIClientDownloadScreen = ({ route }: APIClientDownloadScreenProps) => {
    const {
        item: { client },
    } = route.params;

    const [state, setState] = useState<DownloadState>({
        filePath: "",
        endpoint: "",
        progress: 0,
    });

    const setRequest = (request?: ProgressPromise<ClientResponse>) =>
        setState((state) => ({ ...state, request }));
    const setFilePath = (filePath: string) =>
        setState((state) => ({ ...state, filePath }));
    const setEndpoint = (endpoint: string) => setState({ ...state, endpoint });
    const setProgress = (progress: number) =>
        setState((state) => ({ ...state, progress }));
    const setStatus = (status?: DownloadStatus) => {
        setState((state) => ({ ...state, status }));
    };
    const [response, setResponse] = useState<ClientResponse>();
    const [responseSuccessVisible, setResponseSuccessVisible] = useState(false);
    const [error, setError] = useState<ClientResponseError>();
    const [responseErrorVisible, setResponseErrorVisible] = useState(false);

    const setStateFromResponse = (response: ClientResponse) => {
        if (response.ok) {
            resetState();
        } else {
            setState((state) => ({
                ...state,
                request: undefined,
                progress: state.progress === 1 ? 0 : state.progress,
                status: undefined,
            }));
        }
    };

    const resetState = () =>
        setState((state) => ({
            ...state,
            request: undefined,
            file: undefined,
            progress: 0,
            status: undefined,
        }));

    const download = async () => {
        setStatus(DownloadStatus.DOWNLOADING);
        setRequest(undefined);

        const reqOptions: RequestOptions = {};

        const request = client.download(
            state.endpoint,
            state.filePath,
            reqOptions
        );
        setRequest(request);

        request.progress!((fractionCompleted: number) => {
            setProgress(fractionCompleted);
        })
            .then((response: ClientResponse) => {
                setStateFromResponse(response);
                setResponse(response);
                setError(undefined);
                setResponseSuccessVisible(true);
                setResponseErrorVisible(false);
            })
            .catch((error: ClientResponseError) => {
                setStatus(DownloadStatus.FAILED);
                setResponse(undefined);
                setError(error);
                setResponseSuccessVisible(false);
                setResponseErrorVisible(true);
            });
    };

    const cancelDownload = () => {
        if (state.request) {
            state.request.cancel!();
        }
    };

    const setDefaultFilePath = () => {
        setFilePath(RFNS.DocumentDirectoryPath);
    };

    return (
        <SafeAreaView style={{ flex: 1 }}>
            <ScrollView testID="api_client_dowload.scroll_view">
                <Input
                    label="Endpoint"
                    placeholder="/download"
                    value={state.endpoint}
                    onChangeText={setEndpoint}
                    autoCapitalize="none"
                    testID="api_client_download.endpoint.input"
                />

                <Input
                    label="File path"
                    placeholder="file://some/file/path"
                    value={state.filePath}
                    onFocus={setDefaultFilePath}
                    onChangeText={setFilePath}
                    autoCapitalize="none"
                    testID="api_client_download.file_path.input"
                />

                <ProgressBar
                    progress={state.progress}
                    width={200}
                    style={{ alignSelf: "center" }}
                    testID="progress_file_download.progress_bar"
                />

                <ResponseSuccessOverlay
                    response={response}
                    visible={responseSuccessVisible}
                    setVisible={setResponseSuccessVisible}
                />
                <ResponseErrorOverlay
                    error={error}
                    visible={responseErrorVisible}
                    setVisible={setResponseErrorVisible}
                />

                <DownloadButton
                    filePath={state.filePath}
                    endpoint={state.endpoint}
                    status={state.status}
                    download={download}
                    cancelDownload={cancelDownload}
                    resetState={resetState}
                />
            </ScrollView>
        </SafeAreaView>
    );
}
Example #10
Source File: SignupScreen.tsx    From magento_react_native_graphql with MIT License 4 votes vote down vote up
SignupScreen = ({ navigation }: Props): React.ReactElement => {
  const {
    values,
    loading,
    data,
    error,
    handleChange,
    handleSubmit,
  } = useSignup();

  useEffect(() => {
    if (data?.createCustomerV2?.customer?.email) {
      showMessage({
        message: translate('common.success'),
        description: translate('signupScreen.successMessage'),
        type: 'success',
      });
      navigation.replace(Routes.NAVIGATION_TO_LOGIN_SCREEN);
    }
  }, [data]);

  useEffect(() => {
    if (error) {
      showMessage({
        message: translate('common.error'),
        description: error.message ?? translate('errors.genericError'),
        type: 'danger',
      });
    }
  }, [error]);

  return (
    <GenericTemplate scrollable style={styles.container}>
      <Input
        placeholder="Joe"
        value={values.firstName}
        editable={!loading}
        label={translate('common.firstName')}
        onChangeText={handleChange('firstName')}
      />
      <Input
        placeholder="Ranger"
        value={values.lastName}
        editable={!loading}
        label={translate('common.lastName')}
        onChangeText={handleChange('lastName')}
      />
      <Input
        value={values.email}
        editable={!loading}
        leftIcon={{ name: 'email' }}
        placeholder="[email protected]"
        label={translate('common.email')}
        onChangeText={handleChange('email')}
      />
      <Input
        secureTextEntry={values.secureTextEntry}
        editable={!loading}
        value={values.password}
        leftIcon={{ name: 'lock' }}
        rightIcon={{
          name: values.secureTextEntry ? 'eye' : 'eye-off',
          type: 'material-community',
          onPress: handleChange('secureTextEntry').bind(
            {},
            !values.secureTextEntry,
          ),
        }}
        label={translate('common.password')}
        onChangeText={handleChange('password')}
        placeholder={translate('common.password')}
      />
      <Button
        loading={loading}
        onPress={handleSubmit}
        title={translate('common.register')}
        containerStyle={styles.submitButton}
      />
      <Button
        type="clear"
        disabled={loading}
        title={translate('signupScreen.haveAccount')}
        onPress={() => navigation.navigate(Routes.NAVIGATION_TO_LOGIN_SCREEN)}
      />
    </GenericTemplate>
  );
}
Example #11
Source File: WebSocketClientScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
export default function WebSocketClientScreen({
    route,
}: WebSocketClientScreenProps) {
    const {
        item: { client },
    } = route.params;

    const [connected, setConnected] = useState(false);
    const [message, setMessage] = useState(
        JSON.stringify({
            seq: 1,
            action: "user_typing",
            data: { channel_id: "4dtzmswn93f68fkd97eeafm6xc" },
        })
    );
    const [event, setEvent] = useState<string>("");

    useEffect(() => {
        client.onOpen((event: WebSocketEvent) => {
            setConnected(true);
            parseAndSetEvent(event);
        });
        client.onClose((event: WebSocketEvent) => {
            setConnected(false);
            parseAndSetEvent(event);
        });
        client.onError((event: WebSocketEvent) => {
            parseAndSetEvent(event);
        });
        client.onMessage((event: WebSocketEvent) => {
            parseAndSetEvent(event);
        });
    }, []);

    const parseAndSetEvent = (event: WebSocketEvent) => {
        setEvent(JSON.stringify(event, null, 2));
    };

    const send = () => {
        client.send(message);
    };

    return (
        <SafeAreaView style={{ flex: 1 }}>
            <Input
                label="Message"
                multiline={true}
                numberOfLines={10}
                placeholder={JSON.stringify({
                    action: "",
                    seq: 2,
                    data: { channel_id: "" },
                })}
                onChangeText={setMessage}
                testID="websocket_client.message.input"
                value={message}
            />
            <Button
                title="Send"
                onPress={send}
                style={{ padding: 10 }}
                disabled={!Boolean(message) || !connected}
            />
            <Button
                title={connected ? "Disconnect" : "Connect"}
                onPress={connected ? client.close : client.open}
                style={{ padding: 10 }}
            />
            <Input
                placeholder="Received"
                disabled={true}
                style={{ fontWeight: "bold", fontSize: 17, opacity: 1 }}
                containerStyle={{ height: 50 }}
                inputContainerStyle={{
                    borderColor: "rgba(255,255,255,0)",
                }}
            />
            <ScrollView style={{ marginHorizontal: 20 }}>
                <Text testID="websocket_client.event.text">{event}</Text>
            </ScrollView>
        </SafeAreaView>
    );
}
Example #12
Source File: MattermostClientUploadScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
MattermostClientUploadScreen = ({
    route,
}: APIClientUploadScreenProps) => {
    const {
        item: { client },
    } = route.params;

    const [state, setState] = useState<UploadState>({
        channelId: DEFAULT_CHANNEL_ID,
        progress: 0,
    });

    const setRequest = (request?: ProgressPromise<ClientResponse>) =>
        setState((state) => ({ ...state, request }));
    const setChannelId = (channelId: string) =>
        setState((state) => ({ ...state, channelId }));
    const setSessionId = (sessionId: string) =>
        setState((state) => ({ ...state, sessionId }));
    const setFile = (file: File) => setState((state) => ({ ...state, file }));
    const setProgress = (progress: number) =>
        setState((state) => ({ ...state, progress }));
    const setStatus = (status?: UploadStatus) => {
        setState((state) => ({ ...state, status }));
    };
    const setStatedFileId = (uploadedFileId: number) =>
        setState((state) => ({ ...state, uploadedFileId }));

    const resetState = () =>
        setState({
            request: undefined,
            channelId: DEFAULT_CHANNEL_ID,
            sessionId: "",
            file: undefined,
            progress: 0,
            status: undefined,
        });

    const createUploadSession = async () => {
        const options: RequestOptions = {
            body: {
                channel_id: state.channelId,
                filename: state.file!.name,
                file_size: state.file!.size,
            },
        };

        try {
            const response = await client.post("/api/v4/uploads", options);
            setSessionId(response.data!.id as string);
        } catch (e) {
            const error = e as Error;
            Alert.alert("Session creation error", error.message);
        }
    };

    const upload = async (resume?: boolean) => {
        setStatus(UploadStatus.UPLOADING);
        setRequest(undefined);

        let options: UploadRequestOptions = {};
        if (resume) {
            try {
                const { data } = await client.get(
                    `/api/v4/uploads/${state.sessionId}`
                );
                options.skipBytes = data!.file_offset as number;
            } catch (e) {
                const error = e as Error;
                Alert.alert("Resume error", error.message);
            }
        }

        const request = client.upload(
            `/api/v4/uploads/${state.sessionId}`,
            state.file!.uri!,
            options
        );
        setRequest(request);

        request.progress!((fractionCompleted: number) => {
            setProgress(fractionCompleted);
        })
            .then((response: ClientResponse) => {
                if (response.ok) {
                    setStatedFileId(response.data!.id as number);
                    setStatus(UploadStatus.COMPLETED);
                } else {
                    setStatus(UploadStatus.FAILED);
                }
            })
            .catch((error: ClientResponseError) => {
                Alert.alert("Upload error", error.message);
                setStatus(UploadStatus.FAILED);
            });
    };

    const resumeUpload = () => upload(true);

    const post = async () => {
        try {
            const requestOptions: RequestOptions = {
                body: {
                    channel_id: state.channelId,
                    message: "Upload test",
                    file_ids: [state.uploadedFileId],
                },
            };
            const response = await client.post("/api/v4/posts", requestOptions);
            if (response.code === 201) {
                resetState();
            } else {
                setStatus(UploadStatus.POST_FAILED);
                Alert.alert("Post error", `Status Code: ${response.code}`);
            }
        } catch (e) {
            const error = e as Error;
            setStatus(UploadStatus.POST_FAILED);
            Alert.alert("Post error", error.message);
        }
    };

    const cancelUpload = () => {
        if (state.request) {
            state.request.cancel!();
        }
    };

    const SessionId = () => {
        if (Boolean(state.sessionId)) {
            return (
                <Input
                    label="Session ID"
                    value={state.sessionId}
                    disabled={true}
                />
            );
        }

        return null;
    };

    return (
        <SafeAreaView style={{ flex: 1 }}>
            <ScrollView>
                <Input
                    label="Channel ID"
                    placeholder="pspxu7bu17yttmtnzsjnqu78fe"
                    value={state.channelId}
                    onChangeText={setChannelId}
                    autoCapitalize="none"
                />
                <SessionId />
                <FilePickerButtonGroup
                    onFilePicked={setFile}
                    disabled={Boolean(state.status)}
                />
                <ProgressiveFileUpload
                    file={state.file}
                    progress={state.progress}
                />
            </ScrollView>
            <UploadButton
                channelId={state.channelId}
                sessionId={state.sessionId}
                fileUri={state.file?.uri}
                status={state.status}
                createUploadSession={createUploadSession}
                upload={upload}
                cancelUpload={cancelUpload}
                resumeUpload={resumeUpload}
                resetState={resetState}
                post={post}
            />
        </SafeAreaView>
    );
}
Example #13
Source File: GenericClientRequestScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
GenericClientRequestScreen = ({
    route,
}: GenericClientRequestScreenProps) => {
    const {
        item: { client },
    } = route.params;

    const [url, setUrl] = useState("");
    const [selectedMethodIndex, setSelectedMethodIndex] = useState(0);
    const [timeoutInterval, setTimeoutInterval] = useState(30000);
    const [body, setBody] = useState('{"login_id":"","password":""}');
    const [requestHeaders, setRequestHeaders] = useState<Header[]>([]);
    const [response, setResponse] = useState<ClientResponse>();
    const [responseSuccessVisible, setResponseSuccessVisible] = useState(false);
    const [error, setError] = useState<ClientResponseError>();
    const [responseErrorVisible, setResponseErrorVisible] = useState(false);
    const [
        retryPolicyConfiguration,
        setRetryPolicyType,
        setRetryLimit,
        setRetryInterval,
        setExponentialBackoffBase,
        setExponentialBackoffScale,
        setStatusCodes,
        setRetryMethods,
    ] = useRetryPolicyConfiguration();

    const methods = Object.keys(METHODS);

    const makeRequest = async () => {
        const method = methods[selectedMethodIndex];
        const headers = parseHeaders(requestHeaders);
        let options: RequestOptions = {
            headers,
            timeoutInterval,
            retryPolicyConfiguration,
        };

        const canIncludeBody = ![METHODS.HEAD, METHODS.GET].includes(
            method as METHODS
        );
        if (canIncludeBody && body.length) {
            try {
                options.body = JSON.parse(body);
            } catch (e) {
                const error = e as Error;
                Alert.alert("Error parsing Body", error.message, [{ text: "OK" }], {
                    cancelable: false,
                });
                return;
            }
        }

        try {
            let clientMethod;
            switch (method) {
                case METHODS.HEAD:
                    clientMethod = client.head;
                    break;
                case METHODS.GET:
                    clientMethod = client.get;
                    break;
                case METHODS.PUT:
                    clientMethod = client.put;
                    break;
                case METHODS.POST:
                    clientMethod = client.post;
                    break;
                case METHODS.PATCH:
                    clientMethod = client.patch;
                    break;
                case METHODS.DELETE:
                    clientMethod = client.delete;
                    break;
                default:
                    throw new Error("Invalid request method");
            }
            var response = await clientMethod(url, options);
            setResponse(response);
            setError(undefined);
            setResponseSuccessVisible(true);
            setResponseErrorVisible(false);
        } catch (error) {
            setResponse(undefined);
            setError(error as ClientResponseError);
            setResponseSuccessVisible(false);
            setResponseErrorVisible(true);
        }
    };

    return (
        <SafeAreaView style={{ flex: 1 }}>
            <ScrollView
                style={{
                    backgroundColor: "#fff",
                    borderRadius: 5,
                    margin: 10,
                }}
                testID="generic_client_request.scroll_view"
            >
                <ButtonGroup
                    onPress={setSelectedMethodIndex}
                    selectedIndex={selectedMethodIndex}
                    buttons={methods}
                    containerStyle={{ height: 50 }}
                />
                <Input
                    label="URL"
                    placeholder="https://google.com"
                    value={url}
                    onChangeText={setUrl}
                    autoCapitalize="none"
                    testID="generic_client_request.url.input"
                />
                <AddHeaders onHeadersChanged={setRequestHeaders} />
                {methods[selectedMethodIndex] !== METHODS.GET && (
                    <Input
                        label="Body"
                        placeholder='{"username": "johndoe"}'
                        value={body}
                        onChangeText={setBody}
                        autoCapitalize="none"
                        testID="generic_client_request.body.input"
                    />
                )}
                <NumericInput
                    title="Timeout Interval (ms)"
                    value={timeoutInterval}
                    onChange={setTimeoutInterval}
                    minValue={0}
                    step={5000}
                    testID="generic_client_request.timeout_interval.input"
                />
                <RetryPolicyConfiguration
                    policyType={retryPolicyConfiguration.type}
                    onTypeSelected={setRetryPolicyType}
                    retryLimit={retryPolicyConfiguration.retryLimit}
                    setRetryLimit={setRetryLimit}
                    retryInterval={retryPolicyConfiguration.retryInterval}
                    setRetryInterval={setRetryInterval}
                    exponentialBackoffBase={
                        retryPolicyConfiguration.exponentialBackoffBase
                    }
                    setExponentialBackoffBase={setExponentialBackoffBase}
                    exponentialBackoffScale={
                        retryPolicyConfiguration.exponentialBackoffScale
                    }
                    setExponentialBackoffScale={setExponentialBackoffScale}
                    statusCodes={retryPolicyConfiguration.statusCodes}
                    setStatusCodes={setStatusCodes}
                    retryMethods={retryPolicyConfiguration.retryMethods}
                    setRetryMethods={setRetryMethods}
                />
                <ResponseSuccessOverlay
                    response={response}
                    visible={responseSuccessVisible}
                    setVisible={setResponseSuccessVisible}
                />
                <ResponseErrorOverlay
                    error={error}
                    visible={responseErrorVisible}
                    setVisible={setResponseErrorVisible}
                />
            </ScrollView>
            <Button
                title="Request"
                onPress={makeRequest}
                disabled={!url.length}
                containerStyle={{ padding: 5 }}
            />
        </SafeAreaView>
    );
}
Example #14
Source File: CreateWebSocketClientScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
export default function CreateWebSocketClientScreen({
    navigation,
}: CreateWebSocketClientScreenProps) {
    const [name, setName] = useState("");
    const [url, setUrl] = useState("");
    const [alertOnClientError, setAlertOnClientError] = useState(true);
    const toggleAlertOnClientError = () =>
        setAlertOnClientError((alertOnClientError) => !alertOnClientError);

    const [
        configuration,
        setConfiguration,
    ] = useState<WebSocketClientConfiguration>({
        timeoutInterval: 5000,
        enableCompression: false,
        trustSelfSignedServerCertificate: false,
    });
    const [
        clientP12Configuration,
        setClientP12Path,
        setClientP12Password,
    ] = useClientP12Configuration();

    const setHeaders = (headers: Header[]) => {
        setConfiguration((configuration) => ({
            ...configuration,
            headers: parseHeaders(headers),
        }));
    };

    const setTimeoutInterval = (timeoutInterval: number) => {
        setConfiguration((configuration) => ({
            ...configuration,
            timeoutInterval,
        }));
    };

    const toggleEnableCompression = () => {
        setConfiguration((configuration) => ({
            ...configuration,
            enableCompression: !configuration.enableCompression,
        }));
    };

    const toggleTrustSelfSignedServerCertificate = () => {
        setConfiguration((configuration) => ({
            ...configuration,
            trustSelfSignedServerCertificate: !configuration.trustSelfSignedServerCertificate,
        }));
    };

    const createClient = async () => {
        const wsConfiguration = {
            ...configuration,
        };
        if (clientP12Configuration.path) {
            wsConfiguration["clientP12Configuration"] = clientP12Configuration;
        }

        const clientErrorEventHandler = alertOnClientError
            ? webSocketClientErrorEventHandler
            : undefined;

        try {
            const { client, created } = await getOrCreateWebSocketClient(
                url,
                wsConfiguration,
                clientErrorEventHandler
            );

            if (!created) {
                Alert.alert(
                    "Error",
                    `A client for ${url} already exists`,
                    [{ text: "OK" }],
                    { cancelable: false }
                );
                return;
            }
            const createdClient: WebSocketClientItem = {
                name,
                client,
                type: ClientType.WEBSOCKET,
            };

            navigation.navigate("ClientList", { createdClient });
        } catch (e) {
            const error = e as Error;
            Alert.alert("Error", error.message, [{ text: "OK" }], {
                cancelable: false,
            });
        }
    };

    return (
        <SafeAreaView>
            <ScrollView testID="create_websocket_client.scroll_view">
                <Input
                    label="Name"
                    onChangeText={setName}
                    testID="create_websocket_client.name.input"
                />
                <Input
                    label="URL"
                    onChangeText={setUrl}
                    autoCapitalize="none"
                    testID="create_websocket_client.url.input"
                />

                <AddHeaders onHeadersChanged={setHeaders} />

                <P12Inputs
                    title="Client PKCS12"
                    path={clientP12Configuration.path}
                    password={clientP12Configuration.password}
                    onSelectP12={setClientP12Path}
                    onPasswordChange={setClientP12Password}
                />

                <NumericInput
                    title="Timeout Interval (ms)"
                    value={configuration.timeoutInterval}
                    onChange={setTimeoutInterval}
                    minValue={0}
                    step={5000}
                    testID="create_websocket_client.timeout_interval.input"
                />

                <CheckBox
                    title={`Alert on client error? [${alertOnClientError}]`}
                    checked={alertOnClientError}
                    onPress={toggleAlertOnClientError}
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={styles.checkboxText}
                />

                <CheckBox
                    title={`Enable Compression? [${configuration.enableCompression!}]`}
                    checked={configuration.enableCompression!}
                    onPress={toggleEnableCompression}
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={styles.checkboxText}
                />

                <CheckBox
                    title={`Trust Self-Signed Server Certificate? [${configuration.trustSelfSignedServerCertificate}]`}
                    checked={
                        configuration.trustSelfSignedServerCertificate as boolean
                    }
                    onPress={toggleTrustSelfSignedServerCertificate}
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={styles.checkboxText}
                />

                <Button
                    title="Create"
                    onPress={createClient}
                    disabled={!name || !url}
                    style={styles.createButton}
                />
            </ScrollView>
        </SafeAreaView>
    );
}
Example #15
Source File: CreateAPIClientScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
export default function CreateAPIClientScreen({
    navigation,
}: CreateAPIClientScreenProps) {
    const [name, setName] = useState("");
    const [baseUrl, setBaseUrl] = useState("");
    const [clientHeaders, setClientHeaders] = useState<Header[]>([]);
    const [alertOnClientError, setAlertOnClientError] = useState(true);
    const toggleAlertOnClientError = () =>
        setAlertOnClientError((alertOnClientError) => !alertOnClientError);

    const [
        sessionConfiguration,
        toggleAllowsCellularAccess,
        toggleWaitsForConnectivity,
        toggleCancelRequestsOnUnauthorized,
        toggleTrustSelfSignedServerCertificate,
        setTimeoutIntervalForRequest,
        setTimeoutIntervalForResource,
        setHttpMaximumConnectionsPerHost,
    ] = useSessionConfiguration();

    const [
        retryPolicyConfiguration,
        setRetryPolicyType,
        setRetryLimit,
        setRetryInterval,
        setExponentialBackoffBase,
        setExponentialBackoffScale,
        setStatusCodes,
        setRetryMethods,
    ] = useRetryPolicyConfiguration();

    const [
        clientP12Configuration,
        setClientP12Path,
        setClientP12Password,
    ] = useClientP12Configuration();

    const [
        requestAdapterConfiguration,
        setRequestAdapterConfiguration,
    ] = useState<RequestAdapterConfiguration>({
        bearerAuthTokenResponseHeader: "",
    });

    const setBearerAuthTokenResponseHeader = (
        bearerAuthTokenResponseHeader: string
    ) =>
        setRequestAdapterConfiguration({
            ...requestAdapterConfiguration,
            bearerAuthTokenResponseHeader,
        });

    const createClient = async () => {
        const headers = parseHeaders(clientHeaders);
        const options: APIClientConfiguration = {
            headers,
            sessionConfiguration,
            retryPolicyConfiguration,
            requestAdapterConfiguration,
        };

        if (clientP12Configuration.path && clientP12Configuration.path !== "") {
            options["clientP12Configuration"] = clientP12Configuration;
        }

        const clientErrorEventHandler = alertOnClientError
            ? apiClientErrorEventHandler
            : undefined;

        const { client, created } = await getOrCreateAPIClient(
            baseUrl,
            options,
            clientErrorEventHandler
        );
        if (!created) {
            Alert.alert(
                "Error",
                `A client for ${baseUrl} already exists`,
                [{ text: "OK" }],
                { cancelable: false }
            );
            return;
        }
        const createdClient: APIClientItem = {
            name,
            client,
            type: ClientType.API,
        };

        navigation.navigate("ClientList", { createdClient });
    };

    return (
        <SafeAreaView>
            <ScrollView testID="create_api_client.scroll_view">
                <Input
                    label="Name"
                    onChangeText={setName}
                    autoCorrect={false}
                    testID="create_api_client.name.input"
                />
                <Input
                    label="Base URL"
                    onChangeText={setBaseUrl}
                    autoCapitalize="none"
                    autoCorrect={false}
                    testID="create_api_client.base_url.input"
                />

                <AddHeaders onHeadersChanged={setClientHeaders} />

                <Input
                    label="Bearer Auth Token Response Header"
                    onChangeText={setBearerAuthTokenResponseHeader}
                    placeholder="token"
                    autoCapitalize="none"
                    value=""
                    testID="create_api_client.bearer_auth_token.input"
                />

                <P12Inputs
                    title="Client PKCS12"
                    path={clientP12Configuration.path}
                    password={clientP12Configuration.password}
                    onSelectP12={setClientP12Path}
                    onPasswordChange={setClientP12Password}
                />

                <NumericInput
                    title="Request Timeout Interval (ms)"
                    value={sessionConfiguration.timeoutIntervalForRequest}
                    onChange={setTimeoutIntervalForRequest}
                    minValue={0}
                    step={5000}
                    testID="create_api_client.request_timeout_interval.input"
                />

                <NumericInput
                    title="Resource Timeout Interval (ms)"
                    value={sessionConfiguration.timeoutIntervalForResource}
                    onChange={setTimeoutIntervalForResource}
                    minValue={0}
                    step={5000}
                    testID="create_api_client.resource_timeout_interval.input"
                />

                <NumericInput
                    title="Max Connections"
                    value={sessionConfiguration.httpMaximumConnectionsPerHost}
                    onChange={setHttpMaximumConnectionsPerHost}
                    minValue={1}
                    testID="create_api_client.max_connections.input"
                />

                <RetryPolicyConfiguration
                    policyType={retryPolicyConfiguration.type}
                    onTypeSelected={setRetryPolicyType}
                    retryLimit={retryPolicyConfiguration.retryLimit}
                    setRetryLimit={setRetryLimit}
                    retryInterval={retryPolicyConfiguration.retryInterval}
                    setRetryInterval={setRetryInterval}
                    exponentialBackoffBase={
                        retryPolicyConfiguration.exponentialBackoffBase
                    }
                    setExponentialBackoffBase={setExponentialBackoffBase}
                    exponentialBackoffScale={
                        retryPolicyConfiguration.exponentialBackoffScale
                    }
                    setExponentialBackoffScale={setExponentialBackoffScale}
                    statusCodes={retryPolicyConfiguration.statusCodes}
                    setStatusCodes={setStatusCodes}
                    retryMethods={retryPolicyConfiguration.retryMethods}
                    setRetryMethods={setRetryMethods}
                />

                <CheckBox
                    title={`Alert on client error? [${alertOnClientError}]`}
                    checked={alertOnClientError}
                    onPress={toggleAlertOnClientError}
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={styles.checkboxText}
                />

                <CheckBox
                    title={`Allow Cellular Access? [${sessionConfiguration.allowsCellularAccess}]`}
                    checked={
                        sessionConfiguration.allowsCellularAccess as boolean
                    }
                    onPress={toggleAllowsCellularAccess}
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={styles.checkboxText}
                />

                <CheckBox
                    title={`Waits For Connectivity? [${sessionConfiguration.waitsForConnectivity}]`}
                    checked={
                        sessionConfiguration.waitsForConnectivity as boolean
                    }
                    onPress={toggleWaitsForConnectivity}
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={styles.checkboxText}
                />

                <CheckBox
                    title={`Cancel Requests On 401? [${sessionConfiguration.cancelRequestsOnUnauthorized}]`}
                    checked={
                        sessionConfiguration.cancelRequestsOnUnauthorized as boolean
                    }
                    onPress={toggleCancelRequestsOnUnauthorized}
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={styles.checkboxText}
                />

                <CheckBox
                    title={`Trust Self-Signed Server Certificate? [${sessionConfiguration.trustSelfSignedServerCertificate}]`}
                    checked={
                        sessionConfiguration.trustSelfSignedServerCertificate as boolean
                    }
                    onPress={toggleTrustSelfSignedServerCertificate}
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={styles.checkboxText}
                />

                <Button
                    title="Create"
                    onPress={createClient}
                    disabled={!name || !baseUrl}
                    style={styles.createButton}
                />
            </ScrollView>
        </SafeAreaView>
    );
}
Example #16
Source File: APIClientUploadScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
APIClientUploadScreen = ({ route }: APIClientUploadScreenProps) => {
    const {
        item: { client },
    } = route.params;

    const [state, setState] = useState<UploadState>({
        endpoint: "/files",
        progress: 0,
    });

    const methods = ["POST", "PUT", "PATCH"];
    const [methodIndex, setMethodIndex] = useState<number>(0);
    const [multipart, setMultipart] = useState<boolean>(false);
    const [multipartFileKey, setMultipartFileKey] = useState("");
    const [multipartData, setMultipartData] = useState<Record<string, string>>(
        {}
    );
    const [skipBytes, setSkipBytes] = useState<number>(0);

    const setRequest = (request?: ProgressPromise<ClientResponse>) =>
        setState((state) => ({ ...state, request }));
    const setEndpoint = (endpoint: string) => setState({ ...state, endpoint });
    const setFile = (file: File) => setState((state) => ({ ...state, file }));
    const setProgress = (progress: number) =>
        setState((state) => ({ ...state, progress }));
    const setStatus = (status?: UploadStatus) => {
        setState((state) => ({ ...state, status }));
    };
    const [response, setResponse] = useState<ClientResponse>();
    const [responseSuccessVisible, setResponseSuccessVisible] = useState(false);
    const [error, setError] = useState<ClientResponseError>();
    const [responseErrorVisible, setResponseErrorVisible] = useState(false);

    const setStateFromResponse = (response: ClientResponse) => {
        if (response.ok) {
            resetState();
        } else {
            setState((state) => ({
                ...state,
                request: undefined,
                progress: state.progress === 1 ? 0 : state.progress,
                status: undefined,
            }));
        }
    };

    useEffect(() => {
        if (multipart) {
            setEndpoint("/api/files/multipart");
        }
    }, [multipart]);

    const resetState = () =>
        setState((state) => ({
            ...state,
            request: undefined,
            file: undefined,
            progress: 0,
            status: undefined,
        }));

    const upload = async () => {
        setStatus(UploadStatus.UPLOADING);
        setRequest(undefined);

        const reqOptions: UploadRequestOptions = {};

        if (multipart) {
            // Multipart should always send the file key
            reqOptions["multipart"] = {
                fileKey: multipartFileKey,
            };

            // If there is additional data, add it
            if (Object.keys(multipartData).length) {
                reqOptions["multipart"]["data"] = multipartData;
            }
        }

        // Add the following if they're not the defaults
        if (skipBytes > 0) reqOptions["skipBytes"] = skipBytes;
        if (methodIndex > 0) reqOptions["method"] = methods[methodIndex];

        const request = client.upload(
            state.endpoint,
            state.file!.uri!,
            reqOptions
        );
        setRequest(request);

        request.progress!((fractionCompleted: number) => {
            setProgress(fractionCompleted);
        })
            .then((response: ClientResponse) => {
                setStateFromResponse(response);
                setResponse(response);
                setError(undefined);
                setResponseSuccessVisible(true);
                setResponseErrorVisible(false);
            })
            .catch((error: ClientResponseError) => {
                setStatus(UploadStatus.FAILED);
                setResponse(undefined);
                setError(error);
                setResponseSuccessVisible(false);
                setResponseErrorVisible(true);
            });
    };

    const cancelUpload = () => {
        if (state.request) {
            state.request.cancel!();
        }
    };

    return (
        <SafeAreaView style={{ flex: 1 }}>
            <ScrollView testID="api_client_upload.scroll_view">
                <Input
                    label="Endpoint"
                    placeholder="/upload"
                    value={state.endpoint}
                    onChangeText={setEndpoint}
                    autoCapitalize="none"
                    testID="api_client_upload.endpoint.input"
                />
                <ButtonGroup
                    onPress={setMethodIndex}
                    selectedIndex={methodIndex}
                    buttons={methods}
                    containerStyle={{ flex: 1 }}
                />
                <FilePickerButtonGroup
                    onFilePicked={setFile}
                    disabled={Boolean(state.status)}
                />
                <CheckBox
                    title={`Send as Multi-part? [${multipart}]`}
                    checked={multipart}
                    onPress={() =>
                        multipart ? setMultipart(false) : setMultipart(true)
                    }
                    iconType="ionicon"
                    checkedIcon="ios-checkmark-circle"
                    uncheckedIcon="ios-checkmark-circle"
                    iconRight
                    textStyle={{ flex: 1 }}
                />
                {multipart && (
                    <>
                        <Input
                            label="Multi-part file key"
                            placeholder="file"
                            value={multipartFileKey}
                            onChangeText={setMultipartFileKey}
                            autoCapitalize="none"
                            testID="api_client_upload.multipart_key.input"
                        />
                        <AddMultipart onMultipartsChanged={setMultipartData} />
                    </>
                )}
                {!multipart && (
                    <NumericInput
                        title="Skip Bytes: "
                        value={skipBytes}
                        onChange={setSkipBytes}
                        minValue={0}
                        testID="api_client_request.skip_bytes.input"
                    />
                )}

                <ProgressiveFileUpload
                    file={state.file}
                    progress={state.progress}
                />

                <ResponseSuccessOverlay
                    response={response}
                    visible={responseSuccessVisible}
                    setVisible={setResponseSuccessVisible}
                />
                <ResponseErrorOverlay
                    error={error}
                    visible={responseErrorVisible}
                    setVisible={setResponseErrorVisible}
                />

                <UploadButton
                    fileUri={state.file?.uri}
                    endpoint={state.endpoint}
                    status={state.status}
                    upload={upload}
                    cancelUpload={cancelUpload}
                    resetState={resetState}
                />
            </ScrollView>
        </SafeAreaView>
    );
}
Example #17
Source File: APIClientScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
export default function APIClientScreen({
    navigation,
    route,
}: APIClientScreenProps) {
    const { item } = route.params;
    const { client, name } = item;

    const [headers, setHeaders] = useState<Header[]>([]);

    const getRequest = () =>
        navigation.navigate("APIClientRequest", {
            item,
            method: METHODS.GET,
        });
    const putRequest = () =>
        navigation.navigate("APIClientRequest", {
            item,
            method: METHODS.PUT,
        });
    const postRequest = () =>
        navigation.navigate("APIClientRequest", {
            item,
            method: METHODS.POST,
        });
    const patchRequest = () =>
        navigation.navigate("APIClientRequest", {
            item,
            method: METHODS.PATCH,
        });
    const deleteRequest = () =>
        navigation.navigate("APIClientRequest", {
            item,
            method: METHODS.DELETE,
        });
    const uploadRequest = () =>
        navigation.navigate("APIClientUpload", { item });
    const downloadRequest = () =>
        navigation.navigate("APIClientDownload", { item });
    const mattermostUploadRequest = () =>
        navigation.navigate("MattermostClientUpload", { item });
    const fastImageRequest = () =>
        navigation.navigate("APIClientFastImage", { item });
    const importP12 = () => navigation.navigate("APIClientImportP12", { item });

    const Buttons = () => {
        const buttons = [
            { title: METHODS.GET, onPress: getRequest },
            { title: METHODS.PUT, onPress: putRequest },
            { title: METHODS.POST, onPress: postRequest },
            { title: METHODS.PATCH, onPress: patchRequest },
            { title: METHODS.DELETE, onPress: deleteRequest },
            { title: "UPLOAD", onPress: uploadRequest },
            { title: "DOWNLOAD", onPress: downloadRequest },
            { title: "FAST IMAGE", onPress: fastImageRequest },
            { title: "IMPORT P12", onPress: importP12 },
        ];
        if (item.isMattermostClient) {
            buttons.push({
                title: "MATTERMOST UPLOAD",
                onPress: mattermostUploadRequest,
            });
        }

        return (
            <>
                {buttons.map(({ title, onPress }) => (
                    <Button
                        key={`button-${title}`}
                        title={title}
                        onPress={onPress}
                        containerStyle={{ margin: 5 }}
                    />
                ))}
            </>
        );
    };

    useEffect(() => {
        client.getHeaders().then((clientHeaders: ClientHeaders) => {
            const ordered = Object.keys(clientHeaders)
                .sort()
                .reduce((result: Record<string, string>, key) => {
                    result[key] = clientHeaders[key];
                    return result;
                }, {});
            const orderedHeaders = Object.entries(
                ordered
            ).map(([key, value]) => ({ key, value }));
            setHeaders(orderedHeaders);
        });
    }, []);

    return (
        <SafeAreaView>
            <ScrollView testID="api_client.scroll_view">
                <Input
                    label="Name"
                    value={name}
                    disabled={true}
                    testID="api_client.name.input"
                />
                <Input
                    label="Base URL"
                    value={client.baseUrl}
                    disabled={true}
                    testID="api_client.base_url.input"
                />
                <ListHeaders headers={headers} />
                <Buttons />
            </ScrollView>
        </SafeAreaView>
    );
}
Example #18
Source File: APIClientRequestScreen.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
APIClientRequestScreen = ({ route }: APIClientRequestScreenProps) => {
    const {
        item: { client },
        method,
    } = route.params;
    const [endpoint, setEndpoint] = useState(
        method === METHODS.POST ? "/api/v4/users/login" : "/api/v4/users/me"
    );
    const [timeoutInterval, setTimeoutInterval] = useState(30000);
    const [body, setBody] = useState<string|undefined>();
    const [requestHeaders, setRequestHeaders] = useState<Header[]>([]);
    const [response, setResponse] = useState<ClientResponse>();
    const [responseSuccessVisible, setResponseSuccessVisible] = useState(false);
    const [error, setError] = useState<ClientResponseError>();
    const [responseErrorVisible, setResponseErrorVisible] = useState(false);
    const [
        retryPolicyConfiguration,
        setRetryPolicyType,
        setRetryLimit,
        setRetryInterval,
        setExponentialBackoffBase,
        setExponentialBackoffScale,
        setStatusCodes,
    ] = useRetryPolicyConfiguration();

    const makeRequest = async () => {
        const headers = parseHeaders(requestHeaders);
        let options: RequestOptions = {
            headers,
            timeoutInterval,
            retryPolicyConfiguration,
        };

        const canIncludeBody = ![METHODS.HEAD, METHODS.GET].includes(
            method as METHODS
        );
        if (canIncludeBody && body?.length) {
            try {
                options.body = JSON.parse(body);
            } catch (e) {
                const error = e as Error;
                Alert.alert("Error parsing Body", error.message, [{ text: "OK" }], {
                    cancelable: false,
                });
                return;
            }
        }

        try {
            let clientMethod;
            switch (method) {
                case METHODS.HEAD:
                    clientMethod = client.head;
                    break;
                case METHODS.GET:
                    clientMethod = client.get;
                    break;
                case METHODS.POST:
                    clientMethod = client.post;
                    break;
                case METHODS.PUT:
                    clientMethod = client.put;
                    break;
                case METHODS.PATCH:
                    clientMethod = client.patch;
                    break;
                case METHODS.DELETE:
                    clientMethod = client.delete;
                    break;
                default:
                    throw new Error("Invalid request method");
            }
            var response = await clientMethod(endpoint, options);
            setResponse(response);
            setError(undefined);
            setResponseSuccessVisible(true);
            setResponseErrorVisible(false);
        } catch (error) {
            setResponse(undefined);
            setError(error as ClientResponseError);
            setResponseSuccessVisible(false);
            setResponseErrorVisible(true);
        }
    };

    return (
        <SafeAreaView style={{ flex: 1 }}>
            <ScrollView
                style={{
                    backgroundColor: "#fff",
                    borderRadius: 5,
                    margin: 10,
                }}
                testID="api_client_request.scroll_view"
                persistentScrollbar={true}
                showsVerticalScrollIndicator={true}
            >
                <Input
                    label={`${method}\n\n${client.baseUrl}`}
                    placeholder="/api/v4/system/ping"
                    value={endpoint}
                    onChangeText={setEndpoint}
                    autoCapitalize="none"
                    testID="api_client_request.path.input"
                />
                <AddHeaders onHeadersChanged={setRequestHeaders} />
                {method !== METHODS.GET && (
                    <Input
                        label="Body"
                        placeholder='{"username": "johndoe"}'
                        value={body}
                        onChangeText={setBody}
                        autoCapitalize="none"
                        testID="api_client_request.body.input"
                    />
                )}
                <NumericInput
                    title="Timeout Interval (ms)"
                    value={timeoutInterval}
                    onChange={setTimeoutInterval}
                    minValue={0}
                    step={5000}
                    testID="api_client_request.timeout_interval.input"
                />
                <RetryPolicyConfiguration
                    policyType={retryPolicyConfiguration.type}
                    onTypeSelected={setRetryPolicyType}
                    retryLimit={retryPolicyConfiguration.retryLimit}
                    setRetryLimit={setRetryLimit}
                    retryInterval={retryPolicyConfiguration.retryInterval}
                    setRetryInterval={setRetryInterval}
                    exponentialBackoffBase={
                        retryPolicyConfiguration.exponentialBackoffBase
                    }
                    setExponentialBackoffBase={setExponentialBackoffBase}
                    exponentialBackoffScale={
                        retryPolicyConfiguration.exponentialBackoffScale
                    }
                    setExponentialBackoffScale={setExponentialBackoffScale}
                    statusCodes={retryPolicyConfiguration.statusCodes}
                    setStatusCodes={setStatusCodes}
                />
                <ResponseSuccessOverlay
                    response={response}
                    visible={responseSuccessVisible}
                    setVisible={setResponseSuccessVisible}
                />
                <ResponseErrorOverlay
                    error={error}
                    visible={responseErrorVisible}
                    setVisible={setResponseErrorVisible}
                />
            </ScrollView>
            <Button
                title="Request"
                onPress={makeRequest}
                disabled={!endpoint.length}
                containerStyle={{ padding: 5 }}
            />
        </SafeAreaView>
    );
}
Example #19
Source File: dev.tsx    From bext with MIT License 4 votes vote down vote up
DevScreen: FC = () => {
  const [loading, setLoading] = useState(true);
  const { params } = useRoute<any>();
  const { id, modify } = params || {};
  const { data: draft, mutate } = useRequest(
    async () => {
      try {
        return await getDraft(id);
      } catch (error) {}
    },
    {
      ready: !!id,
    },
  );
  useUpdateEffect(() => {
    if (draft?.id) {
      updateUrl(draft.id, draft.url || '');
    }
  }, [draft?.url, draft?.id]);

  const navigation = useNavigation();
  useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (loading ? <Text>加载中...</Text> : null),
      ...(draft?.name ? { title: draft.name } : null),
    });
  }, [navigation, loading, draft?.name]);
  const [modalVisible, setModalVisible] = useState(false);
  const { script } = useDebug();
  const webView = useRef<WebView>(null);

  const onMessage = (msg: string = '{}') => {
    try {
      const data = JSON.parse(msg);
      switch (data.type) {
        case 'save':
          updateJson(id, JSON.stringify(data.payload));
          break;
        case 'ready':
          webView.current?.injectJavaScript(`
            if (window.injectDraft) {
              window.injectDraft(decodeURIComponent("${encodeURIComponent(
                draft?.json || '{}',
              )}"));
            }
            true;
          `);
          break;
        case 'debug':
          setModalVisible(true);
          script.current = data.payload;
          break;
        default:
          break;
      }
    } catch (error) {}
  };

  const navigateToDebug = () => {
    navigation.navigate(
      'debug' as never,
      {
        url: draft?.url,
      } as never,
    );
    setModalVisible(false);
  };

  return (
    <>
      <Overlay
        transparent
        isVisible={modalVisible}
        onBackdropPress={() => setModalVisible(false)}
        overlayStyle={styles.overlay}
      >
        <Input
          label="输入窗口链接"
          value={draft?.url}
          onChangeText={(url) => mutate((old) => ({ ...old, url } as any))}
        />
        <Button title="确定" disabled={!draft?.url} onPress={navigateToDebug} />
      </Overlay>
      <WebView
        ref={webView}
        originWhitelist={['*']}
        source={{
          uri: `${BEXT_ORIGIN}${
            modify
              ? '/meta?from=dev&devPath=%2Fdev%2Fscript-m'
              : '/dev/script-m'
          }`,
        }}
        onLoad={() => setLoading(false)}
        onMessage={(e) => onMessage(e.nativeEvent.data)}
      />
    </>
  );
}
Example #20
Source File: RetryPolicyConfiguration.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
RetryPolicyConfiguration = (props: RetryPolicyConfigurationProps) => {
    const [statusCodesText, setStatusCodesText] = useState(
        props.statusCodes?.join(",") || ""
    );

    const onLinearPress = () =>
        onCheckBoxPress(
            props.policyType === Constants.RETRY_TYPES.LINEAR_RETRY
                ? undefined
                : Constants.RETRY_TYPES.LINEAR_RETRY
        );
    const onExponentialPress = () =>
        onCheckBoxPress(
            props.policyType === Constants.RETRY_TYPES.EXPONENTIAL_RETRY
                ? undefined
                : Constants.RETRY_TYPES.EXPONENTIAL_RETRY
        );
    const onCheckBoxPress = (policyType?: RetryTypes) => {
        props.onTypeSelected(policyType);
    };
    const linearRetryChecked =
        props.policyType === Constants.RETRY_TYPES.LINEAR_RETRY;
    const exponentialRetryChecked =
        props.policyType === Constants.RETRY_TYPES.EXPONENTIAL_RETRY;

    const buttonMethods = ["get", "post", "put", "patch", "delete"];
    const [selectedMethodsIndex, setSelectedMethodsIndex] = useState<number[]>([
        0,
        1,
    ]);

    if (props.setRetryMethods !== undefined) {
        useEffect(() => {
            const selectedMethods = selectedMethodsIndex.map(
                (index) => buttonMethods[index]
            );
            props.setRetryMethods!(selectedMethods);
        }, [selectedMethodsIndex]);
    }

    const PolicyTypeCheckbox = () => (
        <View style={{ flex: 1, flexDirection: "row" }}>
            <CheckBox
                title={`Linear [${linearRetryChecked}]`}
                checked={linearRetryChecked}
                onPress={onLinearPress}
                iconType="ionicon"
                checkedIcon="ios-checkmark-circle"
                uncheckedIcon="ios-checkmark-circle"
                iconRight
                containerStyle={{
                    padding: 0,
                    borderWidth: 0,
                    backgroundColor: "transparent",
                }}
            />
            <CheckBox
                title={`Exponential [${exponentialRetryChecked}]`}
                checked={exponentialRetryChecked}
                onPress={onExponentialPress}
                iconType="ionicon"
                checkedIcon="ios-checkmark-circle"
                uncheckedIcon="ios-checkmark-circle"
                iconRight
                containerStyle={{
                    padding: 0,
                    borderWidth: 0,
                    backgroundColor: "transparent",
                }}
            />
        </View>
    );

    const updateStatusCodes = () => {
        const statusCodes = statusCodesText
            .split(",")
            .map((code) => {
                return parseInt(code.trim());
            })
            .filter((code) => !isNaN(code));

        props.setStatusCodes(statusCodes);
    };

    return (
        <>
            <Input
                placeholder="Retry Policy"
                disabled={true}
                style={{ fontWeight: "bold", fontSize: 17, opacity: 1 }}
                containerStyle={{ height: 50 }}
                inputContainerStyle={{
                    borderColor: "rgba(255,255,255,0)",
                }}
                labelStyle={{ flex: 12, flexWrap: "wrap", height: 100 }}
            />

            <PolicyTypeCheckbox />

            {props.policyType && (
                <NumericInput
                    title="Retry limit"
                    value={props.retryLimit}
                    onChange={props.setRetryLimit}
                    minValue={0}
                    testID="retry_policy_configuration.retry_limit.input"
                />
            )}

            {props.policyType === Constants.RETRY_TYPES.LINEAR_RETRY && (
                <NumericInput
                    title="Retry interval"
                    value={props.retryInterval}
                    onChange={props.setRetryInterval}
                    minValue={1}
                    testID="retry_policy_configuration.retry_interval.input"
                />
            )}
            {props.policyType === Constants.RETRY_TYPES.EXPONENTIAL_RETRY && (
                <>
                    <NumericInput
                        title="Exponential backoff base"
                        value={props.exponentialBackoffBase}
                        onChange={props.setExponentialBackoffBase}
                        minValue={2}
                        testID="retry_policy_configuration.exponential_backoff_base.input"
                    />
                    <NumericInput
                        title="Exponential backoff scale"
                        value={props.exponentialBackoffScale}
                        onChange={props.setExponentialBackoffScale}
                        minValue={0}
                        valueType="real"
                        step={0.1}
                        testID="retry_policy_configuration.exponential_backoff_scale.input"
                    />
                </>
            )}

            {props.policyType && (
                <>
                    <Input
                        placeholder="Retry Status Codes"
                        disabled={true}
                        style={{ fontWeight: "bold", fontSize: 17, opacity: 1 }}
                        containerStyle={{ height: 50 }}
                        inputContainerStyle={{
                            borderColor: "rgba(255,255,255,0)",
                        }}
                    />

                    <Input
                        value={statusCodesText}
                        onChangeText={setStatusCodesText}
                        onBlur={updateStatusCodes}
                    />

                    {props.setRetryMethods && (
                        <>
                            <Input
                                placeholder="Retry Methods"
                                disabled={true}
                                style={{
                                    fontWeight: "bold",
                                    fontSize: 17,
                                    opacity: 1,
                                }}
                                containerStyle={{ height: 50 }}
                                inputContainerStyle={{
                                    borderColor: "rgba(255,255,255,0)",
                                }}
                            />

                            <ButtonGroup
                                selectedIndexes={selectedMethodsIndex}
                                // @ts-ignore
                                onPress={setSelectedMethodsIndex}
                                buttons={buttonMethods}
                                selectMultiple
                            />
                        </>
                    )}
                </>
            )}
        </>
    );
}
Example #21
Source File: P12Inputs.tsx    From react-native-network-client with Apache License 2.0 4 votes vote down vote up
P12Inputs = (props: P12InputsProps) => {
    const [url, setUrl] = useState("");

    const hasPhotoLibraryPermissions = async () => {
        const permission =
            Platform.OS === "ios"
                ? PERMISSIONS.IOS.PHOTO_LIBRARY
                : PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;
        let result = await check(permission);
        if (result === RESULTS.GRANTED || result === RESULTS.LIMITED) {
            return true;
        } else if (
            result !== RESULTS.BLOCKED &&
            result !== RESULTS.UNAVAILABLE
        ) {
            await request(permission);
            hasPhotoLibraryPermissions();
        }

        console.log(result);

        return false;
    };

    const pickCertificate = async () => {
        const hasPermission = await hasPhotoLibraryPermissions();
        if (hasPermission) {
            try {
                const result = await DocumentPicker.pickSingle({
                    type: [DocumentPicker.types.allFiles],
                });

                if (result.fileCopyUri) {
                    props.onSelectP12(result.fileCopyUri);
                }
            } catch (err) {
                if (DocumentPicker.isCancel(err as Error)) {
                    // User cancelled the picker, exit any dialogs or menus and move on
                } else {
                    throw err;
                }
            }
        }
    };

    const downloadCertificate = async () => {
        const file: File = await downloadToNativeFile(url, clientCertP12);
        props.onSelectP12(`${file.uri}`);
    };

    const clearP12Configuration = () => {
        props.onSelectP12("");
        props.onPasswordChange(undefined);
    };

    const buttons = [
        { title: "Select", onPress: pickCertificate },
        { title: "Download", onPress: downloadCertificate },
    ];

    const onButtonPress = (index: number) => buttons[index].onPress();

    const rightIcon = (
        <View style={{ width: 250 }}>
            {Boolean(props.path) ? (
                <View style={{ flexDirection: "column", height: 50 }}>
                    <View style={{ flexDirection: "row" }}>
                        <View style={{ flex: 1 }}>
                            <Text
                                numberOfLines={2}
                                ellipsizeMode="middle"
                                testID="p12_inputs.path.text"
                            >
                                {props.path}
                            </Text>
                        </View>
                        <Button
                            type="clear"
                            icon={<Icon name="cancel" size={24} />}
                            onPress={clearP12Configuration}
                        />
                    </View>
                    <Input
                        placeholder="password"
                        value={props.password}
                        onChangeText={props.onPasswordChange}
                        autoCapitalize="none"
                        testID="p12_inputs.password.input"
                    />
                </View>
            ) : (
                <Input
                    placeholder="Download URL"
                    onChangeText={setUrl}
                    autoCapitalize="none"
                    testID="p12_inputs.download_url.input"
                />
            )}
        </View>
    );

    return (
        <>
            <Input
                placeholder={props.title}
                disabled={true}
                style={{
                    fontWeight: "bold",
                    fontSize: 17,
                    opacity: 1,
                }}
                inputContainerStyle={{
                    borderColor: "rgba(255,255,255,0)",
                }}
                rightIcon={rightIcon}
                labelStyle={{ flex: 12, flexWrap: "wrap" }}
            />
            {Boolean(!props.path) ? (
                <View
                    style={{
                        flex: 1,
                        alignItems: "flex-end",
                        marginTop: -30,
                        paddingRight: 4,
                        paddingBottom: 10,
                    }}
                >
                    <ButtonGroup
                        containerStyle={{ width: 200 }}
                        buttons={buttons.map((button) => button.title)}
                        onPress={onButtonPress}
                    />
                </View>
            ) : (
                <View style={{ height: 20 }} />
            )}
        </>
    );
}
Example #22
Source File: Checkout.component.tsx    From react-native-woocommerce with MIT License 4 votes vote down vote up
_renderForm = ({ handleCheckoutSubmit }: Props): JSX.Element => (
  <Formik
    initialValues={{
      first_name: '',
      last_name: '',
      address_1: '',
      address_2: '',
      city: '',
      state: '',
      postcode: '',
      country: '',
      email: '',
      phone: '',
    }}
    onSubmit={(values): void => handleCheckoutSubmit(values)}
  >
    {({
      handleChange,
      handleBlur,
      handleSubmit,
      values
    }): JSX.Element => (
      <>
        <Input
          label="First Name"
          placeholder="James"
          onChangeText={handleChange('first_name')}
          onBlur={handleBlur('first_name')}
          value={values.first_name}/>
        <Input
          label="Last Name"
          placeholder="Moriarty"
          onChangeText={handleChange('last_name')}
          onBlur={handleBlur('last_name')}
          value={values.last_name}/>
        <Input
          label="Address 1"
          placeholder="221B Baker Street"
          onChangeText={handleChange('address_1')}
          onBlur={handleBlur('address_1')}
          value={values.address_1}/>
        <Input
          label="Address 2"
          onChangeText={handleChange('address_2')}
          onBlur={handleBlur('address_2')}
          value={values.address_2}/>
        <Input
          label="City"
          placeholder="Birmingham"
          onChangeText={handleChange('city')}
          onBlur={handleBlur('city')}
          value={values.city}/>
        <Input
          label="State"
          placeholder="West Midlands"
          onChangeText={handleChange('state')}
          onBlur={handleBlur('state')}
          value={values.state}/>
        <Input
          label="Post Code"
          placeholder="NW1 6XE"
          onChangeText={handleChange('postcode')}
          onBlur={handleBlur('postcode')}
          value={values.postcode}/>
        <Input
          label="Country"
          placeholder="United Kingdom"
          onChangeText={handleChange('country')}
          onBlur={handleBlur('country')}
          value={values.country}/>
        <Input
          label="Email"
          placeholder="[email protected]"
          onChangeText={handleChange('email')}
          onBlur={handleBlur('email')}
          value={values.email}/>
        <Input
          label="Phone"
          placeholder="+628123456789"
          onChangeText={handleChange('phone')}
          onBlur={handleBlur('phone')}
          value={values.phone}/>
        <Button
          // @ts-ignore
          onPress={handleSubmit}
          title="Proceed to payment"
        />
      </>
    )}
  </Formik>
)
Example #23
Source File: Checkout.component.tsx    From RNWCShop with GNU General Public License v3.0 4 votes vote down vote up
_renderForm = ({ handleCheckoutSubmit }: Props): JSX.Element => (
  <Formik
    initialValues={{
      first_name: '',
      last_name: '',
      address_1: '',
      address_2: '',
      city: '',
      state: '',
      postcode: '',
      country: '',
      email: '',
      phone: ''
    }}
    onSubmit={(values): void => handleCheckoutSubmit(values)}>
    {({ handleChange, handleBlur, handleSubmit, values }): JSX.Element => (
      <>
        <Input
          label="First Name"
          placeholder="James"
          onChangeText={handleChange('first_name')}
          onBlur={handleBlur('first_name')}
          value={values.first_name}
        />
        <Input
          label="Last Name"
          placeholder="Moriarty"
          onChangeText={handleChange('last_name')}
          onBlur={handleBlur('last_name')}
          value={values.last_name}
        />
        <Input
          label="Address 1"
          placeholder="221B Baker Street"
          onChangeText={handleChange('address_1')}
          onBlur={handleBlur('address_1')}
          value={values.address_1}
        />
        <Input
          label="Address 2"
          onChangeText={handleChange('address_2')}
          onBlur={handleBlur('address_2')}
          value={values.address_2}
        />
        <Input
          label="City"
          placeholder="Birmingham"
          onChangeText={handleChange('city')}
          onBlur={handleBlur('city')}
          value={values.city}
        />
        <Input
          label="State"
          placeholder="West Midlands"
          onChangeText={handleChange('state')}
          onBlur={handleBlur('state')}
          value={values.state}
        />
        <Input
          label="Post Code"
          placeholder="NW1 6XE"
          onChangeText={handleChange('postcode')}
          onBlur={handleBlur('postcode')}
          value={values.postcode}
        />
        <Input
          label="Country"
          placeholder="United Kingdom"
          onChangeText={handleChange('country')}
          onBlur={handleBlur('country')}
          value={values.country}
        />
        <Input
          label="Email"
          placeholder="[email protected]"
          onChangeText={handleChange('email')}
          onBlur={handleBlur('email')}
          value={values.email}
        />
        <Input
          label="Phone"
          placeholder="+628123456789"
          onChangeText={handleChange('phone')}
          onBlur={handleBlur('phone')}
          value={values.phone}
        />
        <Button
          // @ts-ignore
          onPress={handleSubmit}
          title="Proceed to payment"
        />
      </>
    )}
  </Formik>
)
Example #24
Source File: home.tsx    From bext with MIT License 4 votes vote down vote up
HomeScreen: FC = () => {
  const navigation = useNavigation();
  const [modalVisible, setModalVisible] = useState(false);
  useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Icon name="add" onPress={() => setModalVisible(true)} />
      ),
    });
  }, [navigation]);
  const db = useDb();
  const { loading, data, run } = useRequest(getDrafts, {
    ready: db.ready,
  });
  const [name, setName] = useState('');
  useFocusEffect(useMemoizedFn(run));

  const onDelete = useMemoizedFn(async (id: number) => {
    await deleteDraft(id);
    run();
  });

  const onPress = useMemoizedFn((id: number) => {
    navigation.navigate(
      'dev' as never,
      {
        id,
      } as never,
    );
  });

  const createDraft = useMemoizedFn(async (empty: boolean) => {
    const id = await addDraft(name);
    setName('');
    run();
    setModalVisible(false);
    if (id) {
      navigation.navigate(
        'dev' as never,
        {
          id,
          modify: empty ? undefined : true,
        } as never,
      );
    }
  });

  return (
    <View style={{ flex: 1 }}>
      <Overlay
        transparent
        isVisible={modalVisible}
        onBackdropPress={() => setModalVisible(false)}
        overlayStyle={styles.overlay}
      >
        <Input
          label="输入草稿名称(不是脚本名称)"
          value={name}
          onChangeText={setName}
        />
        <View style={styles.buttons}>
          <Button
            title="创建空白草稿"
            disabled={!name.length}
            onPress={() => createDraft(true)}
            containerStyle={styles.button}
          />
          <View style={styles.space} />
          <Button
            title="从现有脚本创建"
            disabled={!name.length}
            onPress={() => createDraft(false)}
            containerStyle={styles.button}
          />
        </View>
      </Overlay>
      <FlatList
        data={data || []}
        keyExtractor={(item) => String(item.id)}
        renderItem={({ item }) => (
          <DraftItem
            draft={item}
            onDelete={() => onDelete(item.id)}
            onPress={() => onPress(item.id)}
          />
        )}
        onRefresh={run}
        refreshing={loading}
      />
    </View>
  );
}