Skip to content

Commit cc8b569

Browse files
Chris Bobbechrisbobbe
Chris Bobbe
authored andcommitted
auth ui: Add iOS-compliant "Sign in with Apple" button component.
In Apple's Human Interface Guidelines doc about the button to be used for "Sign in with Apple", they recommend using a standard component from the iOS SDK if possible, but that you can use a custom button, subject to design constraints, otherwise. That doc is https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/. So, make a component, only to be used on iOS, that will check if the standard button is available, and use it, if so. The fallback custom button will show on iOS <13. Since Apple doesn't review our Google Play Store submissions, we shouldn't even have to use the custom button on Android; we can just use the same ZulipButton as the other social auth buttons use. I think it looks better when they all look uniform like this.
1 parent 8d056ed commit cc8b569

File tree

4 files changed

+155
-0
lines changed

4 files changed

+155
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/* @flow strict-local */
2+
import React, { PureComponent } from 'react';
3+
import { StyleSheet, Text, View, Image, TouchableWithoutFeedback } from 'react-native';
4+
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
5+
6+
import type { ThemeName } from '../../reduxTypes';
7+
import TranslatedText from '../../common/TranslatedText';
8+
import appleLogoBlackImg from '../../../static/img/apple-logo-black.png';
9+
import appleLogoWhiteImg from '../../../static/img/apple-logo-white.png';
10+
11+
const styles = StyleSheet.create({
12+
buttonContent: {
13+
flexDirection: 'row',
14+
alignItems: 'center',
15+
justifyContent: 'center',
16+
height: 44,
17+
},
18+
frame: {
19+
height: 44,
20+
justifyContent: 'center',
21+
borderRadius: 22,
22+
overflow: 'hidden',
23+
},
24+
nightFrame: {
25+
backgroundColor: 'black',
26+
},
27+
dayFrame: {
28+
backgroundColor: 'white',
29+
borderWidth: 1.5,
30+
borderColor: 'black',
31+
},
32+
text: {
33+
fontSize: 16,
34+
},
35+
nightText: {
36+
color: 'white',
37+
},
38+
dayText: {
39+
color: 'black',
40+
},
41+
});
42+
43+
type Props = $ReadOnly<{|
44+
style?: ViewStyleProp,
45+
onPress: () => void | Promise<void>,
46+
theme: ThemeName,
47+
|}>;
48+
49+
/**
50+
* The custom "Sign in with Apple" button that follows the rules.
51+
*
52+
* Do not reuse this component; it is only meant to be rendered by the
53+
* IosCompliantAppleAuthButton, which controls whether the custom
54+
* button should be used.
55+
*/
56+
export default class Custom extends PureComponent<Props> {
57+
render() {
58+
const { style, onPress, theme } = this.props;
59+
const logoSource = theme === 'default' ? appleLogoBlackImg : appleLogoWhiteImg;
60+
const frameStyle = [
61+
styles.frame,
62+
theme === 'default' ? styles.dayFrame : styles.nightFrame,
63+
style,
64+
];
65+
const textStyle = [styles.text, theme === 'default' ? styles.dayText : styles.nightText];
66+
67+
return (
68+
<View style={frameStyle}>
69+
<TouchableWithoutFeedback onPress={onPress}>
70+
<View style={styles.buttonContent}>
71+
<Image source={logoSource} />
72+
<Text style={textStyle}>
73+
<TranslatedText text="Sign in with Apple" />
74+
</Text>
75+
</View>
76+
</TouchableWithoutFeedback>
77+
</View>
78+
);
79+
}
80+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* @flow strict-local */
2+
import React, { PureComponent } from 'react';
3+
import { View } from 'react-native';
4+
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
5+
import * as AppleAuthentication from 'expo-apple-authentication';
6+
import { connect } from '../../react-redux';
7+
8+
import Custom from './Custom';
9+
import type { ThemeName } from '../../reduxTypes';
10+
import type { Dispatch } from '../../types';
11+
import { getSettings } from '../../selectors';
12+
13+
type SelectorProps = $ReadOnly<{|
14+
theme: ThemeName,
15+
|}>;
16+
17+
type Props = $ReadOnly<{|
18+
style?: ViewStyleProp,
19+
onPress: () => void | Promise<void>,
20+
21+
dispatch: Dispatch,
22+
...SelectorProps,
23+
|}>;
24+
25+
type State = $ReadOnly<{|
26+
isNativeButtonAvailable: boolean | void,
27+
|}>;
28+
29+
/**
30+
* A "Sign in with Apple" button (iOS only) that follows the rules.
31+
*
32+
* These official guidelines from Apple are at
33+
* https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple/overview/buttons/.
34+
*
35+
* Not to be used on Android. There, we also offer "Sign in with
36+
* Apple", but without marking it with a different style from the
37+
* other buttons.
38+
*/
39+
class IosCompliantAppleAuthButton extends PureComponent<Props, State> {
40+
state = {
41+
isNativeButtonAvailable: undefined,
42+
};
43+
44+
async componentDidMount() {
45+
this.setState({ isNativeButtonAvailable: await AppleAuthentication.isAvailableAsync() });
46+
}
47+
48+
render() {
49+
const { style, onPress, theme } = this.props;
50+
const { isNativeButtonAvailable } = this.state;
51+
if (isNativeButtonAvailable === undefined) {
52+
return <View style={[{ height: 44 }, style]} />;
53+
} else if (isNativeButtonAvailable) {
54+
return (
55+
<AppleAuthentication.AppleAuthenticationButton
56+
buttonType={AppleAuthentication.AppleAuthenticationButtonType.SIGN_IN}
57+
buttonStyle={
58+
theme === 'default'
59+
? AppleAuthentication.AppleAuthenticationButtonStyle.WHITE_OUTLINE
60+
: AppleAuthentication.AppleAuthenticationButtonStyle.BLACK
61+
}
62+
cornerRadius={22}
63+
style={[{ height: 44 }, style]}
64+
onPress={onPress}
65+
/>
66+
);
67+
} else {
68+
return <Custom style={style} onPress={onPress} theme={theme} />;
69+
}
70+
}
71+
}
72+
73+
export default connect(state => ({
74+
theme: getSettings(state).theme,
75+
}))(IosCompliantAppleAuthButton);

static/img/apple-logo-black.png

846 Bytes
Loading

static/img/apple-logo-white.png

813 Bytes
Loading

0 commit comments

Comments
 (0)