Skip to content
97 changes: 0 additions & 97 deletions src/common/SmartUrlInput.js

This file was deleted.

168 changes: 101 additions & 67 deletions src/start/RealmInputScreen.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
/* @flow strict-local */
import React, { PureComponent } from 'react';
import React, { useCallback } from 'react';
import type { Node } from 'react';
import { Keyboard } from 'react-native';
import { Keyboard, View, TextInput } from 'react-native';
import { useFocusEffect } from '@react-navigation/native';

import type { RouteProp } from '../react-navigation';
import type { AppNavigationProp } from '../nav/AppNavigator';
import type { ApiResponseServerSettings } from '../api/settings/getServerSettings';
import ErrorMsg from '../common/ErrorMsg';
import ZulipTextIntl from '../common/ZulipTextIntl';
import SmartUrlInput from '../common/SmartUrlInput';
import Screen from '../common/Screen';
import ZulipButton from '../common/ZulipButton';
import { tryParseUrl } from '../utils/url';
import * as api from '../api';
import { ThemeContext } from '../styles/theme';
import { createStyleSheet, HALF_COLOR } from '../styles';

type Props = $ReadOnly<{|
navigation: AppNavigationProp<'realm-input'>,
route: RouteProp<'realm-input', {| initial: boolean | void |}>,
|}>;

type State = {|
realmInputValue: string,
error: string | null,
progress: boolean,
|};

const urlFromInputValue = (realmInputValue: string): URL | void => {
const withScheme = /^https?:\/\//.test(realmInputValue)
? realmInputValue
Expand All @@ -33,88 +29,126 @@ const urlFromInputValue = (realmInputValue: string): URL | void => {
return tryParseUrl(withScheme);
};

export default class RealmInputScreen extends PureComponent<Props, State> {
state: State = {
progress: false,
realmInputValue: '',
error: null,
};
export default function RealmInputScreen(props: Props): Node {
const { navigation, route } = props;

const themeContext = React.useContext(ThemeContext);

const [progress, setProgress] = React.useState(false);
const [realmInputValue, setRealmInputValue] = React.useState('');
const [error, setError] = React.useState(null);

const textInputRef = React.useRef<React$ElementRef<typeof TextInput> | null>(null);

tryRealm: () => Promise<void> = async () => {
const { realmInputValue } = this.state;
// When the route is focused in the navigation, focus the input.
// Otherwise, if you go back to this screen from the auth screen, the
// input won't be focused.
useFocusEffect(
useCallback(() => {
if (textInputRef.current) {
// Sometimes the effect of this `.focus()` is immediately undone
// (the keyboard is closed) by a Keyboard.dismiss() from React
// Navigation's internals. Seems like a complex bug, but the symptom
// isn't terrible, it just means that on back-navigating to this
// screen, sometimes the keyboard flicks open then closed, instead
// of just opening. Shrug. See
// https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/realm-input/near/1346690
textInputRef.current.focus();
}
}, []),
);

const tryRealm = React.useCallback(async () => {
const parsedRealm = urlFromInputValue(realmInputValue);
if (!parsedRealm) {
this.setState({ error: 'Please enter a valid URL' });
setError('Please enter a valid URL');
return;
}
if (parsedRealm.username !== '') {
this.setState({ error: 'Please enter the server URL, not your email' });
setError('Please enter the server URL, not your email');
return;
}

this.setState({
progress: true,
error: null,
});
setProgress(true);
setError(null);
try {
const serverSettings: ApiResponseServerSettings = await api.getServerSettings(parsedRealm);
this.props.navigation.push('auth', { serverSettings });
navigation.push('auth', { serverSettings });
Keyboard.dismiss();
} catch (errorIllTyped) {
const err: mixed = errorIllTyped; // https://github.com/facebook/flow/issues/2470
this.setState({ error: 'Cannot connect to server' });
setError('Cannot connect to server');
/* eslint-disable no-console */
console.warn('RealmInputScreen: failed to connect to server:', err);
// $FlowFixMe[incompatible-cast]: assuming caught exception was Error
console.warn((err: Error).stack);
} finally {
this.setState({ progress: false });
setProgress(false);
}
};
}, [navigation, realmInputValue]);

handleRealmChange: string => void = value => this.setState({ realmInputValue: value });
const styles = React.useMemo(
() =>
createStyleSheet({
inputWrapper: {
flexDirection: 'row',
opacity: 0.8,
marginTop: 16,
marginBottom: 8,
},
input: {
flex: 1,
padding: 0,
fontSize: 20,
color: themeContext.color,
},
hintText: { paddingLeft: 2, fontSize: 12 },
button: { marginTop: 8 },
}),
[themeContext],
);

render(): Node {
const { navigation } = this.props;
const { progress, error, realmInputValue } = this.state;

const styles = {
input: { marginTop: 16, marginBottom: 8 },
hintText: { paddingLeft: 2, fontSize: 12 },
button: { marginTop: 8 },
};

return (
<Screen
title="Welcome"
canGoBack={!this.props.route.params.initial}
padding
centerContent
keyboardShouldPersistTaps="always"
shouldShowLoadingBanner={false}
>
<ZulipTextIntl text="Enter your Zulip server URL:" />
<SmartUrlInput
return (
<Screen
title="Welcome"
canGoBack={!route.params.initial}
padding
centerContent
keyboardShouldPersistTaps="always"
shouldShowLoadingBanner={false}
>
<ZulipTextIntl text="Enter your Zulip server URL:" />
<View style={styles.inputWrapper}>
<TextInput
value={realmInputValue}
placeholder="your-org.zulipchat.com"
placeholderTextColor={HALF_COLOR}
style={styles.input}
navigation={navigation}
onChangeText={this.handleRealmChange}
onSubmitEditing={this.tryRealm}
autoFocus
autoCorrect={false}
autoCapitalize="none"
returnKeyType="go"
onChangeText={setRealmInputValue}
blurOnSubmit={false}
keyboardType="url"
underlineColorAndroid="transparent"
onSubmitEditing={tryRealm}
enablesReturnKeyAutomatically
ref={textInputRef}
/>
{error !== null ? (
<ErrorMsg error={error} />
) : (
<ZulipTextIntl text="e.g. zulip.example.com" style={styles.hintText} />
)}
<ZulipButton
style={styles.button}
text="Enter"
progress={progress}
onPress={this.tryRealm}
disabled={urlFromInputValue(realmInputValue) === undefined}
/>
</Screen>
);
}
</View>
{error !== null ? (
<ErrorMsg error={error} />
) : (
<ZulipTextIntl text="e.g. zulip.example.com" style={styles.hintText} />
)}
<ZulipButton
style={styles.button}
text="Enter"
progress={progress}
onPress={tryRealm}
disabled={urlFromInputValue(realmInputValue) === undefined}
/>
</Screen>
);
}