Skip to content

Commit 89d0a1c

Browse files
committed
layout: Handle safe areas for screen-spanning "row" elements.
Each of these UI elements is a row that has meaningful content that we need to keep within the safe areas, but the rest of the row (its distinct background color, its touchable area, etc.) is meant to extend through the insets to the extreme left and right of the screen. See example screenshots at zulip#4683 (comment). So, make that happen. We do so by giving the rows left and right padding. This is easy and makes the row elements' styles pretty intuitive, but it does mean we'll have to take care, in the next few commits, not to add any padding to the elements that contain the rows -- we don't want to double up the padding by mistake. (This requirement ends up being kind of annoying on screens that have a mix of these kinds of rows and regular elements. But this is unfortunately the workable design we've found; see our architecture doc on handling safe areas.) Related: zulip#3066
1 parent 7f2be59 commit 89d0a1c

37 files changed

+223
-27
lines changed

src/account-info/AwayStatusSwitch.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type Props = $ReadOnly<{||}>;
1313
* This is a stand-alone component that:
1414
* * retrieves the current user's `user status` data and presents it
1515
* * allows by switching it to control the `away` status
16+
*
17+
* Needs to occupy the horizontal insets because `SwitchRow` does.
1618
*/
1719
export default function AwayStatusSwitch(props: Props): Node {
1820
const awayStatus = useSelector(getSelfUserAwayStatus);

src/account-info/ProfileScreen.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ type Props = $ReadOnly<{|
102102
* It does not have a "Send private message" but has "Switch account" and "Log out" buttons.
103103
*
104104
* The user can still open `AccountDetails` on themselves via the (i) icon in a chat screen.
105+
*
106+
* Needs to occupy the horizontal insets because the away-status switch
107+
* does.
105108
*/
106109
export default function ProfileScreen(props: Props): Node {
107110
const ownUser = useSelector(getOwnUser);

src/autocomplete/EmojiAutocomplete.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ export default function EmojiAutocomplete(props: Props): Node {
3434
data={emojiNames.slice(0, MAX_CHOICES)}
3535
keyExtractor={item => item.name}
3636
renderItem={({ item }) => (
37+
// TODO: Make and use a new emoji-item component with no padding
38+
// for the insets. The rows' content should be bounded by the
39+
// popup, which renders within the safe area.
3740
<EmojiRow
3841
type={item.emoji_type}
3942
code={item.code}

src/autocomplete/PeopleAutocomplete.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ export default function PeopleAutocomplete(props: Props): Node {
8181
// synthetic "users" to represent @all and @everyone.
8282
// TODO display those in a UI that makes more sense for them,
8383
// and drop the fake "users" and use the normal UserItem.
84+
// TODO: Make and use a new user-item component with no padding for
85+
// the insets. The rows' content should be bounded by the popup,
86+
// which renders within the safe area.
8487
<UserItemRaw
8588
key={item.user_id}
8689
user={item}

src/autocomplete/StreamAutocomplete.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export default function StreamAutocomplete(props: Props): Node {
4242
data={matchingSubscriptions}
4343
keyExtractor={item => item.stream_id.toString()}
4444
renderItem={({ item }) => (
45+
// TODO: Make and use a new stream-item component with no padding
46+
// for the insets. The rows' content should be bounded by the
47+
// popup, which renders within the safe area.
4548
<StreamItem
4649
name={item.name}
4750
isMuted={!item.in_home_view}

src/common/NestedNavRow.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import React, { useContext } from 'react';
33
import type { Node } from 'react';
44
import { View } from 'react-native';
5+
import { SafeAreaView } from 'react-native-safe-area-context';
56

67
import Label from './Label';
78
import Touchable from './Touchable';
@@ -22,6 +23,8 @@ type Props = $ReadOnly<{|
2223
*
2324
* Shows a right-facing arrow to indicate its purpose. If you need a
2425
* selectable option row instead, use `SelectableOptionRow`.
26+
*
27+
* Pads the horizontal insets with its background.
2528
*/
2629
export default function NestedNavRow(props: Props): Node {
2730
const { label, onPress, Icon } = props;
@@ -30,13 +33,13 @@ export default function NestedNavRow(props: Props): Node {
3033

3134
return (
3235
<Touchable onPress={onPress}>
33-
<View style={styles.listItem}>
36+
<SafeAreaView mode="padding" edges={['right', 'left']} style={styles.listItem}>
3437
{!!Icon && <Icon size={24} style={[styles.settingsIcon, { color: themeContext.color }]} />}
3538
<Label text={label} />
3639
<View style={styles.rightItem}>
3740
<IconRight size={24} style={[styles.settingsIcon, { color: themeContext.color }]} />
3841
</View>
39-
</View>
42+
</SafeAreaView>
4043
</Touchable>
4144
);
4245
}

src/common/SectionHeader.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @flow strict-local */
22
import React, { PureComponent } from 'react';
33
import type { Node, Context } from 'react';
4-
import { View } from 'react-native';
4+
import { SafeAreaView } from 'react-native-safe-area-context';
55

66
import type { ThemeData } from '../styles';
77
import { ThemeContext, createStyleSheet } from '../styles';
@@ -18,16 +18,25 @@ type Props = $ReadOnly<{|
1818
text: string,
1919
|}>;
2020

21+
/**
22+
* (TODO: Is this meant to be reusable? How?)
23+
*
24+
* Pads the horizontal insets with its background.
25+
*/
2126
export default class SectionHeader extends PureComponent<Props> {
2227
static contextType: Context<ThemeData> = ThemeContext;
2328
context: ThemeData;
2429

2530
render(): Node {
2631
const { text } = this.props;
2732
return (
28-
<View style={[styles.header, { backgroundColor: this.context.backgroundColor }]}>
33+
<SafeAreaView
34+
mode="padding"
35+
edges={['right', 'left']}
36+
style={[styles.header, { backgroundColor: this.context.backgroundColor }]}
37+
>
2938
<Label text={text} />
30-
</View>
39+
</SafeAreaView>
3140
);
3241
}
3342
}

src/common/SelectableOptionRow.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import React from 'react';
33
import type { Node } from 'react';
44
import { View } from 'react-native';
5+
import { SafeAreaView } from 'react-native-safe-area-context';
56

67
import { RawLabel, Touchable } from '.';
78
import { BRAND_COLOR, createStyleSheet } from '../styles';
@@ -56,6 +57,8 @@ type Props<TItemKey: string | number> = $ReadOnly<{|
5657
* it is either active or not. The event handler shouldn't do random
5758
* things that aren't related to that state, like navigating to a
5859
* different screen.
60+
*
61+
* Pads the horizontal insets with its background.
5962
*/
6063
export default function SelectableOptionRow<TItemKey: string | number>(
6164
props: Props<TItemKey>,
@@ -64,13 +67,13 @@ export default function SelectableOptionRow<TItemKey: string | number>(
6467

6568
return (
6669
<Touchable onPress={() => onRequestSelectionChange(itemKey, !selected)}>
67-
<View style={styles.listItem}>
70+
<SafeAreaView mode="padding" edges={['right', 'left']} style={styles.listItem}>
6871
<View style={styles.wrapper}>
6972
<RawLabel text={title} />
7073
{subtitle !== undefined && <RawLabel text={subtitle} style={styles.subtitle} />}
7174
</View>
7275
<View>{selected && <IconDone size={16} color={BRAND_COLOR} />}</View>
73-
</View>
76+
</SafeAreaView>
7477
</Touchable>
7578
);
7679
}

src/common/SwitchRow.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, { useContext } from 'react';
33
import type { Node } from 'react';
44
import { View } from 'react-native';
55
import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet';
6+
import { SafeAreaView } from 'react-native-safe-area-context';
67

78
import type { SpecificIconType } from './Icons';
89
import Label from './Label';
@@ -25,19 +26,28 @@ const componentStyles = createStyleSheet({
2526

2627
/**
2728
* A row with a label and a switch component.
29+
*
30+
* Pads the horizontal insets with its background. (A parent component
31+
* could probably do this instead, if desired. The choice to do it here is
32+
* just in line with our other "row" components, like `SelectableOptionRow`,
33+
* which do need to pad the insets.)
2834
*/
2935
export default function SwitchRow(props: Props): Node {
3036
const { label, value, onValueChange, style, Icon } = props;
3137

3238
const themeContext = useContext(ThemeContext);
3339

3440
return (
35-
<View style={[componentStyles.container, styles.listItem, style]}>
41+
<SafeAreaView
42+
mode="padding"
43+
edges={['right', 'left']}
44+
style={[componentStyles.container, styles.listItem, style]}
45+
>
3646
{!!Icon && <Icon size={24} style={[styles.settingsIcon, { color: themeContext.color }]} />}
3747
<Label text={label} style={styles.flexed} />
3848
<View style={styles.rightItem}>
3949
<ZulipSwitch value={value} onValueChange={onValueChange} />
4050
</View>
41-
</View>
51+
</SafeAreaView>
4252
);
4353
}

src/emoji/EmojiPickerScreen.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ type State = {|
3232
filter: string,
3333
|};
3434

35+
/**
36+
* A screen to choose an emoji reaction.
37+
*
38+
* Needs to occupy the horizontal insets because its descendents (the
39+
* `EmojiRow`s) do.
40+
*/
3541
class EmojiPickerScreen extends PureComponent<Props, State> {
3642
state = {
3743
filter: '',

0 commit comments

Comments
 (0)