git ssb

1+

Daan Patchwork / manyverse



Commit 16ba51fb4a567b222522acdd26310439f216b10a

ux: feature: show biography preview in Profile screen

Andre Staltz committed on 9/30/2020, 2:16:33 PM
Parent: a3293cea8ff6a88f92b39f6f42b4f4d7314bf39d

Files changed

android/app/src/main/assets/translations/en.jsonchanged
src/frontend/screens/profile-edit/styles.tschanged
src/frontend/screens/profile-edit/view.tschanged
src/frontend/screens/profile/styles.tschanged
src/frontend/screens/profile/view.tschanged
android/app/src/main/assets/translations/en.jsonView
@@ -582,9 +582,9 @@
582582 "label": "Edit profile",
583583 "accessibility_label": "Edit Profile Button"
584584 },
585585 "open_biography": {
586- "label": "Bio",
586+ "label": "Read bio",
587587 "accessibility_label": "Show Biography"
588588 },
589589 "manage": {
590590 "accessibility_label": "Manage Contact"
src/frontend/screens/profile-edit/styles.tsView
@@ -15,8 +15,9 @@
1515 export const styles = StyleSheet.create({
1616 container: {
1717 flex: 1,
1818 alignSelf: 'stretch',
19+ flexDirection: 'column',
1920 backgroundColor: Palette.backgroundText,
2021 },
2122
2223 cover: {
@@ -77,12 +78,9 @@
7778 save: {
7879 position: 'absolute',
7980 top: Platform.select({
8081 ios: -44,
81- default:
82- Dimensions.toolbarHeight +
83- avatarSizeHalf +
84- Dimensions.verticalSpaceSmall,
82+ default: avatarSizeHalf + Dimensions.verticalSpaceSmall,
8583 }),
8684 right: Platform.select({
8785 ios: 0,
8886 default: Dimensions.horizontalSpaceBig,
src/frontend/screens/profile-edit/view.tsView
@@ -12,8 +12,9 @@
1212 TextInput,
1313 TouchableWithoutFeedback,
1414 KeyboardAvoidingView,
1515 Platform,
16+ ScrollView,
1617 } from 'react-native';
1718 import {t} from '../../drivers/localization';
1819 import {Palette} from '../../global-styles/palette';
1920 import Button from '../../components/Button';
@@ -28,103 +29,110 @@
2829
2930 return h(View, {style: styles.container}, [
3031 h(TopBar, {sel: 'topbar', title: t('profile_edit.title')}),
3132
32- h(View, {style: styles.cover}),
33+ h(ScrollView, {style: styles.container}, [
34+ h(View, {style: styles.cover}),
3335
34- h(
35- TouchableWithoutFeedback,
36- {
37- sel: 'avatar',
38- accessible: true,
39- accessibilityRole: 'button',
40- accessibilityLabel: t(
41- 'profile_edit.call_to_action.edit_picture.accessbility_label',
42- ),
43- },
44- [
45- h(View, {style: styles.avatarTouchable, pointerEvents: 'box-only'}, [
46- h(Avatar, {
47- size: avatarSize,
48- url: state.newAvatar
49- ? `file://${state.newAvatar}`
50- : state.about.imageUrl,
51- style: styles.avatar,
52- overlayIcon: 'camera',
53- }),
54- ]),
55- ],
56- ),
57-
58- Platform.OS === 'ios'
59- ? null
60- : h(Button, {
61- sel: 'save',
62- style: styles.save,
63- textStyle: styles.saveText,
64- strong: true,
65- text: t('profile_edit.call_to_action.save.label'),
36+ h(
37+ TouchableWithoutFeedback,
38+ {
39+ sel: 'avatar',
6640 accessible: true,
41+ accessibilityRole: 'button',
6742 accessibilityLabel: t(
68- 'profile_edit.call_to_action.save.accessibility_label',
43+ 'profile_edit.call_to_action.edit_picture.accessbility_label',
6944 ),
70- }),
45+ },
46+ [
47+ h(
48+ View,
49+ {style: styles.avatarTouchable, pointerEvents: 'box-only'},
50+ [
51+ h(Avatar, {
52+ size: avatarSize,
53+ url: state.newAvatar
54+ ? `file://${state.newAvatar}`
55+ : state.about.imageUrl,
56+ style: styles.avatar,
57+ overlayIcon: 'camera',
58+ }),
59+ ],
60+ ),
61+ ],
62+ ),
7163
72- h(
73- KeyboardAvoidingView,
74- {
75- style: styles.fields,
76- enabled: true,
77- ...Platform.select({ios: {behavior: 'padding' as const}}),
78- },
79- [
80- Platform.OS === 'ios'
81- ? h(Button, {
82- sel: 'save',
83- style: styles.save,
84- textStyle: styles.saveText,
85- strong: true,
86- text: t('profile_edit.call_to_action.save.label'),
87- accessible: true,
88- accessibilityLabel: t(
89- 'profile_edit.call_to_action.save.accessibility_label',
90- ),
91- })
92- : null,
64+ Platform.OS === 'ios'
65+ ? null
66+ : h(Button, {
67+ sel: 'save',
68+ style: styles.save,
69+ textStyle: styles.saveText,
70+ strong: true,
71+ text: t('profile_edit.call_to_action.save.label'),
72+ accessible: true,
73+ accessibilityLabel: t(
74+ 'profile_edit.call_to_action.save.accessibility_label',
75+ ),
76+ }),
9377
94- h(Text, {style: styles.label}, t('profile_edit.fields.name.label')),
95- h(TextInput, {
96- sel: 'name',
97- multiline: false,
98- autoFocus: true,
99- defaultValue: defaultName,
100- underlineColorAndroid: Palette.backgroundBrand,
101- accessible: true,
102- accessibilityLabel: t(
103- 'profile_edit.fields.name.accessibility_label',
104- ),
105- style: styles.textInput,
106- }),
78+ h(
79+ KeyboardAvoidingView,
80+ {
81+ style: styles.fields,
82+ enabled: true,
83+ ...Platform.select({ios: {behavior: 'padding' as const}}),
84+ },
85+ [
86+ Platform.OS === 'ios'
87+ ? h(Button, {
88+ sel: 'save',
89+ style: styles.save,
90+ textStyle: styles.saveText,
91+ strong: true,
92+ text: t('profile_edit.call_to_action.save.label'),
93+ accessible: true,
94+ accessibilityLabel: t(
95+ 'profile_edit.call_to_action.save.accessibility_label',
96+ ),
97+ })
98+ : null,
10799
108- h(
109- Text,
110- {style: styles.label},
111- t('profile_edit.fields.description.label'),
112- ),
113- h(TextInput, {
114- sel: 'description',
115- multiline: true,
116- autoFocus: false,
117- numberOfLines: 1,
118- defaultValue: state.about.description ?? '',
119- underlineColorAndroid: Palette.backgroundBrand,
120- accessible: true,
121- accessibilityLabel: t(
122- 'profile_edit.fields.description.accessibility_label',
100+ h(Text, {style: styles.label}, t('profile_edit.fields.name.label')),
101+ h(TextInput, {
102+ sel: 'name',
103+ multiline: false,
104+ autoFocus: true,
105+ defaultValue: defaultName,
106+ underlineColorAndroid: Palette.backgroundBrand,
107+ accessible: true,
108+ accessibilityLabel: t(
109+ 'profile_edit.fields.name.accessibility_label',
110+ ),
111+ style: styles.textInput,
112+ }),
113+
114+ h(
115+ Text,
116+ {style: styles.label},
117+ t('profile_edit.fields.description.label'),
123118 ),
124- style: styles.textInput,
125- }),
126- ],
127- ),
119+ h(TextInput, {
120+ sel: 'description',
121+ multiline: true,
122+ autoFocus: false,
123+ // numberOfLines: 1,
124+ scrollEnabled: false,
125+ defaultValue: state.about.description ?? '',
126+ underlineColorAndroid: Palette.backgroundBrand,
127+ accessible: true,
128+ accessibilityLabel: t(
129+ 'profile_edit.fields.description.accessibility_label',
130+ ),
131+ style: styles.textInput,
132+ }),
133+ ],
134+ ),
135+ ]),
128136 ]);
129137 });
130138 }
src/frontend/screens/profile/styles.tsView
@@ -8,14 +8,17 @@
88 import {Palette} from '../../global-styles/palette';
99 import {Dimensions} from '../../global-styles/dimens';
1010 import {Typography} from '../../global-styles/typography';
1111
12-export const avatarSize = Dimensions.avatarSizeBig;
13-const avatarSizeHalf = avatarSize * 0.5;
14-export const toolbarAvatarSize = Dimensions.avatarSizeSmall;
12+export const AVATAR_SIZE = Dimensions.avatarSizeBig;
13+const AVATAR_SIZE_HALF = AVATAR_SIZE * 0.5;
1514
16-export const coverHeight = avatarSizeHalf;
15+export const AVATAR_SIZE_TOOLBAR = Dimensions.avatarSizeSmall;
1716
17+export const COVER_HEIGHT = AVATAR_SIZE_HALF;
18+
19+export const BIO_MARKDOWN_MAX_HEIGHT = 54;
20+
1821 export const styles = StyleSheet.create({
1922 container: {
2023 flex: 1,
2124 alignSelf: 'stretch',
@@ -23,18 +26,18 @@
2326 },
2427
2528 cover: {
2629 backgroundColor: Palette.backgroundBrand,
27- height: coverHeight,
30+ height: COVER_HEIGHT,
2831 zIndex: 10,
2932 },
3033
3134 avatarTouchable: {
3235 position: 'absolute',
33- top: Dimensions.toolbarHeight + coverHeight - avatarSizeHalf,
36+ top: Dimensions.toolbarHeight + COVER_HEIGHT - AVATAR_SIZE_HALF,
3437 left: Dimensions.horizontalSpaceBig,
35- width: avatarSize,
36- height: avatarSize,
38+ width: AVATAR_SIZE,
39+ height: AVATAR_SIZE,
3740 zIndex: 19,
3841 },
3942
4043 avatar: {
@@ -44,12 +47,12 @@
4447 name: {
4548 position: 'absolute',
4649 color: 'white',
4750 top:
48- Dimensions.toolbarHeight + coverHeight - Typography.fontSizeLarge * 1.75,
51+ Dimensions.toolbarHeight + COVER_HEIGHT - Typography.fontSizeLarge * 1.75,
4952 left:
5053 Dimensions.horizontalSpaceBig +
51- avatarSize +
54+ AVATAR_SIZE +
5255 Dimensions.horizontalSpaceBig,
5356 right: Dimensions.horizontalSpaceBig + Dimensions.iconSizeNormal,
5457 fontSize: Typography.fontSizeLarge,
5558 lineHeight: Typography.lineHeightLarge,
@@ -65,9 +68,9 @@
6568 sub: {
6669 marginTop: Dimensions.verticalSpaceSmall,
6770 marginLeft:
6871 Dimensions.horizontalSpaceBig + // left margin to the avatar
69- avatarSize + // avatar
72+ AVATAR_SIZE + // avatar
7073 Dimensions.horizontalSpaceBig - // right margin to the avatar
7174 Dimensions.horizontalSpaceSmall, // minus follows-you-text margin
7275 marginRight: Dimensions.horizontalSpaceBig,
7376 zIndex: 30,
@@ -102,22 +105,30 @@
102105 marginLeft: Dimensions.horizontalSpaceNormal,
103106 },
104107
105108 descriptionArea: {
106- zIndex: 10,
107109 justifyContent: 'flex-start',
108110 flexDirection: 'row',
109- paddingTop: Dimensions.verticalSpaceNormal,
110- paddingBottom: Dimensions.verticalSpaceNormal,
111111 paddingLeft: Dimensions.horizontalSpaceBig,
112112 paddingRight: Dimensions.horizontalSpaceBig,
113113 backgroundColor: Palette.backgroundText,
114+ maxHeight: BIO_MARKDOWN_MAX_HEIGHT + 1,
114115 },
115116
116- bioButton: {
117- minWidth: avatarSize,
117+ readBio: {
118+ backgroundColor: Palette.backgroundText,
119+ minWidth: 50,
120+ position: 'absolute',
121+ bottom: 0,
122+ right: Dimensions.horizontalSpaceBig,
123+ zIndex: 20,
118124 },
119125
126+ headerMarginBottom: {
127+ backgroundColor: Palette.backgroundText,
128+ paddingBottom: Dimensions.verticalSpaceNormal,
129+ },
130+
120131 feed: {
121132 bottom: 0,
122133 backgroundColor: Palette.backgroundVoid,
123134 alignSelf: 'stretch',
src/frontend/screens/profile/view.tsView
@@ -12,8 +12,9 @@
1212 Text,
1313 TouchableOpacity,
1414 TouchableWithoutFeedback,
1515 Animated,
16+ ViewProps,
1617 } from 'react-native';
1718 import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
1819 import {FloatingAction} from 'react-native-floating-action';
1920 import {isRootPostMsg, isPublic} from 'ssb-typescript/utils';
@@ -29,39 +30,47 @@
2930 import EmptySection from '../../components/EmptySection';
3031 import Avatar from '../../components/Avatar';
3132 import TopBar from '../../components/TopBar';
3233 import {State} from './model';
33-import {styles, avatarSize, toolbarAvatarSize, coverHeight} from './styles';
34+import {
35+ styles,
36+ AVATAR_SIZE,
37+ AVATAR_SIZE_TOOLBAR,
38+ COVER_HEIGHT,
39+ BIO_MARKDOWN_MAX_HEIGHT,
40+} from './styles';
41+import {PureComponent} from 'react';
42+import Markdown from '../../components/Markdown';
3443
3544 function calcNameTransY(scrollY: Animated.Value): Animated.Animated {
3645 return scrollY.interpolate({
37- inputRange: [0, coverHeight],
38- outputRange: [0, -coverHeight - Typography.fontSizeLarge * 0.5],
46+ inputRange: [0, COVER_HEIGHT],
47+ outputRange: [0, -COVER_HEIGHT - Typography.fontSizeLarge * 0.5],
3948 extrapolate: 'clamp',
4049 });
4150 }
4251
4352 function calcAvatarTransX(scrollY: Animated.Value): Animated.Animated {
4453 return scrollY.interpolate({
45- inputRange: [0, coverHeight],
54+ inputRange: [0, COVER_HEIGHT],
4655 outputRange: [0, Dimensions.iconSizeNormal],
4756 extrapolate: 'clamp',
4857 });
4958 }
5059
5160 function calcAvatarTransY(scrollY: Animated.Value): Animated.Animated {
52- const margin = (Dimensions.toolbarHeight - toolbarAvatarSize) * 0.5;
61+ const margin = (Dimensions.toolbarHeight - AVATAR_SIZE_TOOLBAR) * 0.5;
5362 return scrollY.interpolate({
54- inputRange: [0, coverHeight],
55- outputRange: [0, -coverHeight - toolbarAvatarSize * 0.5 - margin],
63+ inputRange: [0, COVER_HEIGHT],
64+ outputRange: [0, -COVER_HEIGHT - AVATAR_SIZE_TOOLBAR * 0.5 - margin],
5665 extrapolate: 'clamp',
5766 });
5867 }
5968
6069 function calcAvatarScale(scrollY: Animated.Value): Animated.Animated {
6170 return scrollY.interpolate({
62- inputRange: [0, coverHeight],
63- outputRange: [1, toolbarAvatarSize / avatarSize],
71+ inputRange: [0, COVER_HEIGHT],
72+ outputRange: [1, AVATAR_SIZE_TOOLBAR / AVATAR_SIZE],
6473 extrapolate: 'clamp',
6574 });
6675 }
6776
@@ -116,9 +125,9 @@
116125 Animated.View,
117126 {style: [styles.avatarTouchable, animStyle], pointerEvents: 'box-only'},
118127 [
119128 h(Avatar, {
120- size: avatarSize,
129+ size: AVATAR_SIZE,
121130 url: state.about.imageUrl,
122131 style: styles.avatar,
123132 }),
124133 ],
@@ -149,75 +158,100 @@
149158 displayName(state.about.name, state.about.id),
150159 );
151160 }
152161
153-function ProfileHeader({state}: {state: State}) {
154- const followsYouTristate = state.about.followsYou;
155- const isSelfProfile = state.displayFeedId === state.selfFeedId;
156- const isBlocked = state.about.following === false;
162+class ProfileHeader extends PureComponent<{
163+ about: State['about'];
164+ isSelfProfile: boolean;
165+}> {
166+ public state = {
167+ showReadBio: false,
168+ };
157169
158- return h(View, {style: styles.header}, [
159- h(View, {style: styles.cover}),
170+ private onMarkdownMeasured: ViewProps['onLayout'] = (ev) => {
171+ if (ev.nativeEvent.layout.height > BIO_MARKDOWN_MAX_HEIGHT) {
172+ this.setState({showReadBio: true});
173+ }
174+ };
160175
161- h(View, {style: styles.sub}, [
162- followsYouTristate === true
163- ? h(View, {style: styles.followsYou}, [
164- h(
165- Text,
166- {style: styles.followsYouText},
167- t('profile.info.follows_you'),
168- ),
169- ])
170- : followsYouTristate === false
171- ? h(View, {style: styles.followsYou}, [
172- h(
173- Text,
174- {style: styles.followsYouText},
175- t('profile.info.blocks_you'),
176- ),
177- ])
178- : null,
176+ public render() {
177+ const {about, isSelfProfile} = this.props;
178+ const followsYouTristate = about.followsYou;
179+ const isBlocked = about.following === false;
179180
180- h(View, {style: styles.cta}, [
181- isSelfProfile
182- ? h(Button, {
183- sel: 'editProfile',
184- text: t('profile.call_to_action.edit_profile.label'),
185- accessible: true,
186- accessibilityLabel: t(
187- 'profile.call_to_action.edit_profile.accessibility_label',
181+ return h(View, {style: styles.header}, [
182+ h(View, {style: styles.cover}),
183+
184+ h(View, {style: styles.sub}, [
185+ followsYouTristate === true
186+ ? h(View, {style: styles.followsYou}, [
187+ h(
188+ Text,
189+ {style: styles.followsYouText},
190+ t('profile.info.follows_you'),
188191 ),
189- })
190- : isBlocked
191- ? null
192- : h(ToggleButton, {
193- sel: 'follow',
194- style: styles.follow,
195- text:
196- state.about.following === true
197- ? t('profile.info.following')
198- : t('profile.call_to_action.follow'),
199- toggled: state.about.following === true,
200- }),
192+ ])
193+ : followsYouTristate === false
194+ ? h(View, {style: styles.followsYou}, [
195+ h(
196+ Text,
197+ {style: styles.followsYouText},
198+ t('profile.info.blocks_you'),
199+ ),
200+ ])
201+ : null,
202+
203+ h(View, {style: styles.cta}, [
204+ isSelfProfile
205+ ? h(Button, {
206+ sel: 'editProfile',
207+ text: t('profile.call_to_action.edit_profile.label'),
208+ accessible: true,
209+ accessibilityLabel: t(
210+ 'profile.call_to_action.edit_profile.accessibility_label',
211+ ),
212+ })
213+ : isBlocked
214+ ? null
215+ : h(ToggleButton, {
216+ sel: 'follow',
217+ style: styles.follow,
218+ text:
219+ about.following === true
220+ ? t('profile.info.following')
221+ : t('profile.call_to_action.follow'),
222+ toggled: about.following === true,
223+ }),
224+ ]),
201225 ]),
202- ]),
203226
204- h(View, {style: styles.descriptionArea}, [
205- state.about.description
206- ? h(Button, {
207- sel: 'bio',
208- text: t('profile.call_to_action.open_biography.label'),
209- small: true,
210- style: styles.bioButton,
211- accessible: true,
212- accessibilityLabel: t(
213- 'profile.call_to_action.open_biography.accessibility_label',
214- ),
215- strong: false,
216- })
227+ about.description
228+ ? h(View, {style: styles.descriptionArea}, [
229+ h(Markdown, {
230+ key: 'md',
231+ text: about.description!,
232+ onLayout: this.onMarkdownMeasured,
233+ }),
234+ this.state.showReadBio
235+ ? h(Button, {
236+ sel: 'bio',
237+ key: 'bio',
238+ text: t('profile.call_to_action.open_biography.label'),
239+ strong: false,
240+ small: true,
241+ style: styles.readBio,
242+ accessible: true,
243+ accessibilityLabel: t(
244+ 'profile.call_to_action.open_biography.accessibility_label',
245+ ),
246+ })
247+ : null,
248+ ])
217249 : null,
218- ]),
219- ]);
250+
251+ h(View, {style: styles.headerMarginBottom}),
252+ ]);
253+ }
220254 }
221255
222256 export default function view(state$: Stream<State>, ssbSource: SSBSource) {
223257 const scrollHeaderBy = new Animated.Value(0);
@@ -270,9 +304,12 @@
270304 : null,
271305 selfFeedId: state.selfFeedId,
272306 lastSessionTimestamp: state.lastSessionTimestamp,
273307 yOffsetAnimVal: scrollHeaderBy,
274- HeaderComponent: h(ProfileHeader, {state}),
308+ HeaderComponent: h(ProfileHeader, {
309+ about: state.about,
310+ isSelfProfile,
311+ }),
275312 style: styles.feed,
276313 EmptyComponent: isSelfProfile
277314 ? h(EmptySection, {
278315 style: styles.emptySection,

Built with git-ssb-web