Merge pull request #2794 from ecency/sa/login-screen-update

[Improve] Login Screen UI Update
This commit is contained in:
Feruz M 2023-12-11 20:59:06 +05:30 committed by GitHub
commit 11585d50fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 386 additions and 243 deletions

View File

@ -0,0 +1,50 @@
import React from 'react';
import { SvgXml } from 'react-native-svg';
const xml = `
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg639"
version="1.1"
viewBox="0 0 110 131"
height="131px"
width="110px">
<metadata
id="metadata645">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>steemconnect</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs643" />
<title
id="title631">hivesigner</title>
<g
id="g637"
fillRule="evenodd"
fill="none"
strokeWidth="1"
stroke="none">
<g
style="fill:#e31337;fill-opacity:1"
id="g635"
fill="#FFFFFF">
<path
style="fill:#e31337;fill-opacity:1"
id="path633"
d="M98.5416667,69.9233063 L98.5416667,45.8870172 L0,45.8870172 L0,0 L110,0 L110,22.9435086 L98.5416667,22.9435086 L98.5416667,11.4717543 L11.4583333,11.4717543 L11.4583333,34.4152629 L110,34.4152629 L110,71.7557407 C109.814829,89.9952801 99.6177682,104.956632 82.7527664,116.860728 C76.7961987,121.065148 70.4063912,124.587232 64.0012261,127.457262 C61.7434474,128.468929 59.6383537,129.328634 57.7397369,130.039339 C56.5694641,130.477405 55.6341636,130.79975 55.217168,130.930175 L54.9939202,131 L54.770964,130.929247 C54.3038614,130.781016 53.4195598,130.475943 52.2525224,130.038871 C50.3537481,129.327755 48.2495815,128.46815 45.9926252,127.45661 C39.5881896,124.586219 33.1997658,121.06434 27.2442254,116.860148 C10.5407495,105.06867 0.379911508,90.2779437 0.0104347976,72.2720521 L0,72.2720521 L0,57.3587715 L11.4583333,57.3587715 L11.4583333,69.9233063 C11.4583333,83.7531618 18.6794796,95.3214487 31.0665488,104.779305 C35.6407321,108.271816 40.6776939,111.281674 45.8998806,113.824407 C49.3619815,115.510138 52.4791717,116.790221 55.00154,117.681377 C57.521117,116.791558 60.639999,115.510854 64.1022301,113.825035 C69.3240535,111.28244 74.3609709,108.272494 78.9351124,104.77987 C91.3219921,95.3217663 98.5416667,83.7531543 98.5416667,69.9233063 Z" />
</g>
</g>
</svg>
`;
export default () => <SvgXml xml={xml} width={20} height={20} />;

1
src/assets/svgs/index.ts Normal file
View File

@ -0,0 +1 @@
export { default as HiveSignerIcon } from './hive-signer-icon';

View File

@ -201,7 +201,7 @@ const FormInputView = ({
) : value && value.length > 0 ? (
<Icon
iconType={iconType || 'MaterialIcons'}
onPress={() => setValue('')}
onPress={() => _handleOnChange('')}
name={leftIconName}
style={styles.icon}
/>

View File

@ -103,6 +103,7 @@ import TransferAccountSelector from './transferAccountSelector/transferAccountSe
import TransferAmountInputSection from './transferAmountInputSection/transferAmountInputSection';
import TextBoxWithCopy from './textBoxWithCopy/textBoxWithCopy';
import WebViewModal from './webViewModal/webViewModal';
import OrDivider from './orDivider/orDividerView';
// Basic UI Elements
import {
@ -253,4 +254,5 @@ export {
TransferAmountInputSection,
TextBoxWithCopy,
WebViewModal,
OrDivider,
};

View File

@ -4,7 +4,6 @@ export default EStyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
height: '$deviceHeight / 3',
backgroundColor: '$primaryBackgroundColor',
},
safeArea: {
@ -15,7 +14,7 @@ export default EStyleSheet.create({
maxHeight: '$deviceHeight / 3',
overflow: 'hidden',
backgroundColor: '$primaryBackgroundColor',
height: '$deviceHeight / 3.9',
height: 120,
justifyContent: 'space-between',
alignItems: 'center',
},
@ -37,12 +36,10 @@ export default EStyleSheet.create({
alignItems: 'center',
},
mascot: {
width: '70%',
height: '70%',
width: '60%',
},
titleText: {
alignSelf: 'center',
marginTop: 20,
marginLeft: 32,
marginRight: 12,
flex: 1,
@ -54,9 +51,15 @@ export default EStyleSheet.create({
backgroundColor: '$primaryBackgroundColor',
paddingVertical: 8,
},
backIconContainer: {
marginLeft: 20,
},
backIcon: {
fontSize: 24,
color: '$iconColor',
},
logoContainer: {
paddingLeft: 32,
paddingRight: 8,
paddingRight: 32,
alignItems: 'center',
justifyContent: 'center',
},

View File

@ -4,11 +4,11 @@ import * as Animatable from 'react-native-animatable';
// Constants
// Components
import { TextButton } from '../../buttons';
import { LineBreak } from '../../basicUIElements';
// Styles
import styles from './loginHeaderStyles';
import getWindowDimensions from '../../../utils/getWindowDimensions';
import { IconButton } from '../..';
class LoginHeaderView extends PureComponent {
/* Props
@ -27,24 +27,18 @@ class LoginHeaderView extends PureComponent {
// Component Functions
render() {
const { description, isKeyboardOpen, onPress, rightButtonText, title } = this.props;
const { description, isKeyboardOpen, title, onBackPress } = this.props;
return (
<SafeAreaView style={styles.safeArea}>
<View styles={styles.container}>
<View style={styles.headerRow}>
<View style={styles.logoContainer}>
<Image
resizeMode="contain"
style={styles.logo}
source={require('../../../assets/ecency_logo_transparent.png')}
/>
</View>
<View style={styles.headerButton}>
<TextButton
onPress={onPress}
text={rightButtonText}
textStyle={{ color: '#357ce6' }}
<View style={styles.backIconContainer}>
<IconButton
iconStyle={styles.backIcon}
iconType="MaterialIcons"
name="close"
onPress={onBackPress}
/>
</View>
</View>
@ -61,7 +55,7 @@ class LoginHeaderView extends PureComponent {
<Image
resizeMode="contain"
style={styles.mascot}
source={require('../../../assets/love_mascot.png')}
source={require('../../../assets/ecency_logo_transparent.png')}
/>
</View>
</View>
@ -76,7 +70,7 @@ class LoginHeaderView extends PureComponent {
export default LoginHeaderView;
const { height } = getWindowDimensions();
const bodyHeight = height / 3.9;
const bodyHeight = 120;
const showAnimation = {
from: {
opacity: 0,

View File

@ -47,24 +47,28 @@ class MainButton extends Component {
iconType,
textStyle,
iconPosition,
iconStyle,
renderIcon,
} = this.props;
if (isLoading) {
this._getIndicator();
}
const iconComponent = source ? (
<Image source={source} style={styles.image} resizeMode="contain" />
) : (
iconName && (
<Icon
iconType={iconType || 'MaterialIcons'}
color={iconColor}
name={iconName}
style={styles.icon}
/>
)
);
const iconComponent =
renderIcon ||
(source ? (
<Image source={source} style={styles.image} resizeMode="contain" />
) : (
iconName && (
<Icon
iconType={iconType || 'MaterialIcons'}
color={iconColor}
name={iconName}
style={[styles.icon, iconStyle]}
/>
)
));
if (text) {
return (
@ -108,7 +112,7 @@ class MainButton extends Component {
}
render() {
const { wrapperStyle, children, height, style, isLoading } = this.props;
const { wrapperStyle, children, height, style, isLoading, bodyWrapperStyle } = this.props;
const { isDisable } = this.state;
return (
@ -123,7 +127,7 @@ class MainButton extends Component {
style && style,
]}
>
<View style={styles.body}>
<View style={[styles.body, bodyWrapperStyle]}>
{isLoading ? this._getIndicator() : children || this._getBody()}
</View>
</TouchableOpacity>

View File

@ -0,0 +1,25 @@
import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
dividerContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
divider: {
borderWidth: 0.5,
flex: 1,
borderColor: '$primaryDarkGray',
},
leftDivider: {
marginLeft: 20,
},
rightDivider: {
marginRight: 20,
},
orText: {
fontSize: 16,
color: '$primaryDarkGray',
marginHorizontal: 8,
},
});

View File

@ -0,0 +1,24 @@
import React from 'react';
import { View, Text, ViewStyle } from 'react-native';
import { useIntl } from 'react-intl';
import styles from './orDividerStyles';
interface OrDividerProps {
containerStyle?: ViewStyle;
}
const OrDivider = ({ containerStyle }: OrDividerProps) => {
const intl = useIntl();
return (
<View style={[styles.dividerContainer, containerStyle]}>
<View style={[styles.divider, styles.leftDivider]} />
<Text style={styles.orText}>
{intl.formatMessage({
id: 'login.or',
})}
</Text>
<View style={[styles.divider, styles.rightDivider]} />
</View>
);
};
export default OrDivider;

View File

@ -377,12 +377,13 @@
"no_user": "User is not found."
},
"login": {
"signin": "Sign in",
"signin": "Login",
"login_with_hs": "Login with hivesigner",
"signup": "JOIN NOW",
"signin_title": "To get all the benefits of using Ecency",
"username": "Username",
"password": "Password or WIF",
"description": "By signing in, you agree to our Terms of Services and Privacy Policies.",
"username": "username",
"password": "password / private key",
"description": "By logging in, you agree to our Terms of Services and Privacy Policies.",
"cancel": "cancel",
"login": "LOGIN",
"steemconnect_description": "If you don't want to keep your password encrypted and saved on your device, you can use Hivesigner.",
@ -394,7 +395,10 @@
"deep_login_alert_title": "Easy Login @{username}",
"deep_login_alert_body":"Verify direct login using access code",
"deep_login_url_expired":"Login url expired, please use private key or password to login",
"deep_login_malformed_url":"Malformed login url, please use private key or password to login"
"deep_login_malformed_url":"Malformed login url, please use private key or password to login",
"no_account_text": "Don't have an account?",
"signup_now": "Sign up now!",
"or": "OR"
},
"register": {
"modal_title":"Get Hive Account",

View File

@ -55,6 +55,7 @@ import { useUserActivityMutation } from '../../../providers/queries/pointQueries
import { PointActivityIds } from '../../../providers/ecency/ecency.types';
import { usePostsCachePrimer } from '../../../providers/queries/postQueries/postQueries';
import { PostTypes } from '../../../constants/postTypes';
import { speakQueries } from '../../../providers/queries';
import {
BENEFICIARY_SRC_ENCODER,

View File

@ -1,8 +1,7 @@
import React, { PureComponent } from 'react';
import { View, Platform, Keyboard } from 'react-native';
import React, { useEffect, useState } from 'react';
import { View, Platform, Keyboard, Text } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import ScrollableTabView from 'react-native-scrollable-tab-view';
import { injectIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import { debounce } from 'lodash';
// Actions
@ -15,213 +14,219 @@ import {
LoginHeader,
MainButton,
Modal,
TabBar,
TextButton,
OrDivider,
} from '../../../components';
// Constants
import { default as ROUTES } from '../../../constants/routeNames';
import { ECENCY_TERMS_URL } from '../../../config/ecencyApi';
// Styles
import styles from './loginStyles';
import globalStyles from '../../../globalStyles';
import { HiveSignerIcon } from '../../../assets/svgs';
import STEEM_CONNECT_LOGO from '../../../assets/steem_connect.png';
import { ECENCY_TERMS_URL } from '../../../config/ecencyApi';
const LoginScreen = ({
initialUsername,
getAccountsWithUsername,
navigation,
handleOnPressLogin,
handleSignUp,
isLoading,
}) => {
const intl = useIntl();
const [username, setUsername] = useState(initialUsername || '');
const [password, setPassword] = useState('');
const [isUsernameValid, setIsUsernameValid] = useState(true);
const [keyboardIsOpen, setKeyboardIsOpen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
class LoginScreen extends PureComponent {
constructor(props) {
super(props);
this.state = {
username: props.initialUsername || '',
password: '',
isUsernameValid: true,
keyboardIsOpen: false,
isModalOpen: false,
};
}
componentDidMount() {
if (this.props.initialUsername) {
this._handleUsernameChange(this.props.initialUsername);
useEffect(() => {
if (initialUsername) {
_handleUsernameChange(initialUsername);
}
}
}, []);
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', _keyboardDidShow);
const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', _keyboardDidHide);
_handleOnPasswordChange = (value) => {
this.setState({ password: value });
return () => {
keyboardDidShowListener.remove();
keyboardDidHideListener.remove();
};
}, []);
const debouncedCheckValidity = debounce((uname) => {
_checkUsernameIsValid(uname);
}, 1000);
useEffect(() => {
if (username) {
debouncedCheckValidity(username);
return () => debouncedCheckValidity.cancel();
}
}, [username]);
const _keyboardDidShow = () => {
setKeyboardIsOpen(true);
};
_handleUsernameChange = (username) => {
const { getAccountsWithUsername } = this.props;
const _keyboardDidHide = () => {
setKeyboardIsOpen(false);
};
this.setState({ username });
const _handleOnPasswordChange = (value) => {
setPassword(value);
};
getAccountsWithUsername(username).then((res) => {
const isValid = res.includes(username);
const _handleUsernameChange = (username) => {
const formattedUsername = username.trim().toLowerCase();
setUsername(formattedUsername);
};
this.setState({ isUsernameValid: isValid });
const _checkUsernameIsValid = (uname) => {
getAccountsWithUsername(uname).then((res) => {
const isValid = res.includes(uname);
setIsUsernameValid(isValid);
});
};
_handleOnModalToggle = () => {
const { isModalOpen } = this.state;
this.setState({ isModalOpen: !isModalOpen });
const _handleOnModalToggle = () => {
setIsModalOpen(!isModalOpen);
};
UNSAFE_componentWillMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', () =>
this.setState({ keyboardIsOpen: true }),
);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () =>
this.setState({ keyboardIsOpen: false }),
);
}
const _renderHiveicon = () => (
<View style={styles.hsLoginBtnIconStyle}>
<HiveSignerIcon />
</View>
);
render() {
const { navigation, intl, handleOnPressLogin, handleSignUp, isLoading } = this.props;
const { username, isUsernameValid, keyboardIsOpen, password, isModalOpen } = this.state;
return (
<View style={styles.container}>
<LoginHeader
isKeyboardOpen={keyboardIsOpen}
title={intl.formatMessage({
id: 'login.signin',
})}
description={intl.formatMessage({
id: 'login.signin_title',
})}
onPress={() => handleSignUp()}
rightButtonText={intl.formatMessage({
id: 'login.signup',
})}
onBackPress={() => {
navigation.navigate({
name: ROUTES.DRAWER.MAIN,
});
}}
/>
console.log('keyboardIsOpen : ', keyboardIsOpen);
return (
<View style={styles.container}>
<LoginHeader
isKeyboardOpen={keyboardIsOpen}
title={intl.formatMessage({
id: 'login.signin',
})}
description={intl.formatMessage({
id: 'login.signin_title',
})}
onPress={() => handleSignUp()}
rightButtonText={intl.formatMessage({
id: 'login.signup',
})}
/>
<ScrollableTabView
locked={isLoading}
style={globalStyles.tabView}
renderTabBar={() => (
<TabBar
style={styles.tabbar}
tabUnderlineDefaultWidth={100}
tabUnderlineScaleX={2} // default 3
activeColor="#357ce6"
inactiveColor="#222"
/>
)}
<View
tabLabel={intl.formatMessage({
id: 'login.signin',
})}
style={styles.tabbarItem}
>
<KeyboardAwareScrollView
enableAutoAutomaticScroll={Platform.OS === 'ios'}
contentContainerStyle={styles.formWrapper}
enableOnAndroid={true}
>
<View
tabLabel={intl.formatMessage({
id: 'login.signin',
<FormInput
rightIconName="at"
leftIconName="close"
iconType="MaterialCommunityIcons"
isValid={isUsernameValid}
onChange={_handleUsernameChange}
placeholder={intl.formatMessage({
id: 'login.username',
})}
style={styles.tabbarItem}
>
<KeyboardAwareScrollView
enableAutoAutomaticScroll={Platform.OS === 'ios'}
contentContainerStyle={styles.formWrapper}
enableOnAndroid={true}
>
<FormInput
rightIconName="at"
leftIconName="close"
iconType="MaterialCommunityIcons"
isValid={isUsernameValid}
onChange={debounce(this._handleUsernameChange, 1000)}
placeholder={intl.formatMessage({
id: 'login.username',
})}
isEditable
type="username"
isFirstImage
value={username}
inputStyle={styles.input}
/>
<FormInput
rightIconName="lock"
leftIconName="close"
isValid={isUsernameValid}
onChange={(value) => this._handleOnPasswordChange(value)}
placeholder={intl.formatMessage({
id: 'login.password',
})}
isEditable
secureTextEntry
type="password"
numberOfLines={1}
value={password}
inputStyle={styles.input}
/>
<InformationArea
description={intl.formatMessage({
id: 'login.description',
})}
link={ECENCY_TERMS_URL}
iconName="ios-information-circle-outline"
/>
</KeyboardAwareScrollView>
<View style={styles.footerButtons}>
<TextButton
style={styles.cancelButton}
onPress={() =>
navigation.navigate({
name: ROUTES.DRAWER.MAIN,
})
}
text={intl.formatMessage({
id: 'login.cancel',
})}
/>
<MainButton
onPress={() => handleOnPressLogin(username, password)}
iconName="person"
iconColor="white"
text={intl.formatMessage({
id: 'login.login',
})}
textStyle={styles.mainBtnText}
isDisable={!isUsernameValid || password.length < 2 || username.length < 2}
isLoading={isLoading}
/>
</View>
</View>
<View tabLabel="Hivesigner" style={styles.tabbarItem}>
<InformationArea
description={intl.formatMessage({
id: 'login.steemconnect_description',
})}
iconName="ios-information-circle-outline"
link="https://hivesigner.com"
/>
<MainButton
wrapperStyle={styles.mainButtonWrapper}
onPress={() => this._handleOnModalToggle()}
source={STEEM_CONNECT_LOGO}
text="hive"
secondText="signer"
/>
</View>
</ScrollableTabView>
<Modal
isOpen={isModalOpen}
isFullScreen
isCloseButton
handleOnModalClose={this._handleOnModalToggle}
title={intl.formatMessage({
id: 'login.signin',
})}
>
<HiveSigner handleOnModalClose={this._handleOnModalToggle} />
</Modal>
isEditable
type="username"
isFirstImage
value={username}
inputStyle={styles.input}
onBlur={() => _checkUsernameIsValid(username)}
/>
<FormInput
rightIconName="lock"
leftIconName="close"
isValid={isUsernameValid}
onChange={_handleOnPasswordChange}
placeholder={intl.formatMessage({
id: 'login.password',
})}
isEditable
secureTextEntry
type="password"
numberOfLines={1}
value={password}
inputStyle={styles.input}
/>
<InformationArea
description={intl.formatMessage({
id: 'login.description',
})}
link={ECENCY_TERMS_URL}
iconName="ios-information-circle-outline"
/>
<MainButton
onPress={() => handleOnPressLogin(username, password)}
iconName="person"
iconColor="white"
text={intl.formatMessage({
id: 'login.login',
})}
textStyle={styles.mainBtnText}
isDisable={!isUsernameValid || password.length < 2 || username.length < 2}
isLoading={isLoading}
wrapperStyle={styles.loginBtnWrapper}
bodyWrapperStyle={styles.loginBtnBodyWrapper}
height={50}
iconStyle={styles.loginBtnIconStyle}
/>
<OrDivider />
<MainButton
onPress={() => _handleOnModalToggle()}
renderIcon={_renderHiveicon()}
text={intl.formatMessage({
id: 'login.login_with_hs',
})}
textStyle={styles.hsLoginBtnText}
wrapperStyle={styles.loginBtnWrapper}
bodyWrapperStyle={styles.loginBtnBodyWrapper}
height={48}
style={styles.hsLoginBtnStyle}
/>
</KeyboardAwareScrollView>
<View style={styles.footerButtons}>
<Text style={styles.noAccountText}>
{intl.formatMessage({
id: 'login.no_account_text',
})}
</Text>
<Text style={styles.signUpNowText} onPress={() => handleSignUp()}>
{intl.formatMessage({
id: 'login.signup_now',
})}
</Text>
</View>
</View>
);
}
}
<Modal
isOpen={isModalOpen}
isFullScreen
isCloseButton
handleOnModalClose={_handleOnModalToggle}
title={intl.formatMessage({
id: 'login.signin',
})}
>
<HiveSigner handleOnModalClose={_handleOnModalToggle} />
</Modal>
</View>
);
};
export default injectIntl(LoginScreen);
export default LoginScreen;

View File

@ -3,7 +3,7 @@ import EStyleSheet from 'react-native-extended-stylesheet';
export default EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryLightBackground',
backgroundColor: '$primaryBackgroundColor',
},
tabbar: {
alignSelf: 'center',
@ -11,10 +11,9 @@ export default EStyleSheet.create({
backgroundColor: '$primaryBackgroundColor',
},
tabbarItem: {
flex: 1,
// flex: 1,
backgroundColor: '$primaryBackgroundColor',
minWidth: '$deviceWidth',
height: '$deviceHeight / 1.95',
},
mainButtonWrapper: {
position: 'absolute',
@ -22,20 +21,13 @@ export default EStyleSheet.create({
bottom: 24,
flexDirection: 'row',
},
footerButtons: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
alignSelf: 'flex-end',
paddingRight: 24,
paddingBottom: 24,
backgroundColor: '$primaryBackgroundColor',
},
cancelButton: {
marginRight: 10,
justifyContent: 'center',
alignItems: 'center',
},
formWrapper: {
flexGrow: 1,
marginHorizontal: 30,
marginVertical: 10,
},
@ -44,6 +36,44 @@ export default EStyleSheet.create({
flexGrow: 1,
},
mainBtnText: {
marginRight: 12,
flexGrow: 1,
},
loginBtnWrapper: {
marginVertical: 12,
},
loginBtnBodyWrapper: {
flex: 1,
},
loginBtnIconStyle: {
position: 'absolute',
left: 0,
},
hsLoginBtnStyle: {
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: '$primaryBlue',
},
hsLoginBtnText: {
flexGrow: 1,
color: '$primaryBlue',
},
hsLoginBtnIconStyle: {
marginLeft: 20,
},
footerButtons: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 24,
backgroundColor: '$primaryBackgroundColor',
},
noAccountText: {
color: '$primaryDarkGray',
fontSize: 16,
},
signUpNowText: {
color: '$primaryBlue',
marginLeft: 4,
fontSize: 16,
},
});