about summary refs log tree commit diff
path: root/src/screens/Profile/Header/Shell.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/screens/Profile/Header/Shell.tsx')
-rw-r--r--src/screens/Profile/Header/Shell.tsx164
1 files changed, 164 insertions, 0 deletions
diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx
new file mode 100644
index 000000000..1348b394c
--- /dev/null
+++ b/src/screens/Profile/Header/Shell.tsx
@@ -0,0 +1,164 @@
+import React, {memo} from 'react'
+import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {useNavigation} from '@react-navigation/native'
+import {AppBskyActorDefs, ModerationDecision} from '@atproto/api'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {NavigationProp} from 'lib/routes/types'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {BACK_HITSLOP} from 'lib/constants'
+import {useSession} from '#/state/session'
+import {Shadow} from '#/state/cache/types'
+import {useLightboxControls, ProfileImageLightbox} from '#/state/lightbox'
+
+import {atoms as a, useTheme} from '#/alf'
+import {LabelsOnMe} from '#/components/moderation/LabelsOnMe'
+import {BlurView} from 'view/com/util/BlurView'
+import {LoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
+import {UserAvatar} from 'view/com/util/UserAvatar'
+import {UserBanner} from 'view/com/util/UserBanner'
+import {ProfileHeaderAlerts} from '#/components/moderation/ProfileHeaderAlerts'
+
+interface Props {
+  profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
+  moderation: ModerationDecision
+  hideBackButton?: boolean
+  isPlaceholderProfile?: boolean
+}
+
+let ProfileHeaderShell = ({
+  children,
+  profile,
+  moderation,
+  hideBackButton = false,
+  isPlaceholderProfile,
+}: React.PropsWithChildren<Props>): React.ReactNode => {
+  const t = useTheme()
+  const {currentAccount} = useSession()
+  const {_} = useLingui()
+  const {openLightbox} = useLightboxControls()
+  const navigation = useNavigation<NavigationProp>()
+  const {isDesktop} = useWebMediaQueries()
+
+  const onPressBack = React.useCallback(() => {
+    if (navigation.canGoBack()) {
+      navigation.goBack()
+    } else {
+      navigation.navigate('Home')
+    }
+  }, [navigation])
+
+  const onPressAvi = React.useCallback(() => {
+    const modui = moderation.ui('avatar')
+    if (profile.avatar && !(modui.blur && modui.noOverride)) {
+      openLightbox(new ProfileImageLightbox(profile))
+    }
+  }, [openLightbox, profile, moderation])
+
+  const isMe = React.useMemo(
+    () => currentAccount?.did === profile.did,
+    [currentAccount, profile],
+  )
+
+  return (
+    <View style={t.atoms.bg} pointerEvents="box-none">
+      <View pointerEvents="none">
+        {isPlaceholderProfile ? (
+          <LoadingPlaceholder
+            width="100%"
+            height={150}
+            style={{borderRadius: 0}}
+          />
+        ) : (
+          <UserBanner
+            type={profile.associated?.labeler ? 'labeler' : 'default'}
+            banner={profile.banner}
+            moderation={moderation.ui('banner')}
+          />
+        )}
+      </View>
+
+      {children}
+
+      <View style={[a.px_lg, a.pb_sm]} pointerEvents="box-none">
+        <ProfileHeaderAlerts moderation={moderation} />
+        {isMe && (
+          <LabelsOnMe details={{did: profile.did}} labels={profile.labels} />
+        )}
+      </View>
+
+      {!isDesktop && !hideBackButton && (
+        <TouchableWithoutFeedback
+          testID="profileHeaderBackBtn"
+          onPress={onPressBack}
+          hitSlop={BACK_HITSLOP}
+          accessibilityRole="button"
+          accessibilityLabel={_(msg`Back`)}
+          accessibilityHint="">
+          <View style={styles.backBtnWrapper}>
+            <BlurView style={styles.backBtn} blurType="dark">
+              <FontAwesomeIcon size={18} icon="angle-left" color="white" />
+            </BlurView>
+          </View>
+        </TouchableWithoutFeedback>
+      )}
+      <TouchableWithoutFeedback
+        testID="profileHeaderAviButton"
+        onPress={onPressAvi}
+        accessibilityRole="image"
+        accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)}
+        accessibilityHint="">
+        <View
+          style={[
+            t.atoms.bg,
+            {borderColor: t.atoms.bg.backgroundColor},
+            styles.avi,
+            profile.associated?.labeler && styles.aviLabeler,
+          ]}>
+          <UserAvatar
+            type={profile.associated?.labeler ? 'labeler' : 'user'}
+            size={90}
+            avatar={profile.avatar}
+            moderation={moderation.ui('avatar')}
+          />
+        </View>
+      </TouchableWithoutFeedback>
+    </View>
+  )
+}
+ProfileHeaderShell = memo(ProfileHeaderShell)
+export {ProfileHeaderShell}
+
+const styles = StyleSheet.create({
+  backBtnWrapper: {
+    position: 'absolute',
+    top: 10,
+    left: 10,
+    width: 30,
+    height: 30,
+    overflow: 'hidden',
+    borderRadius: 15,
+    // @ts-ignore web only
+    cursor: 'pointer',
+  },
+  backBtn: {
+    width: 30,
+    height: 30,
+    borderRadius: 15,
+    alignItems: 'center',
+    justifyContent: 'center',
+  },
+  avi: {
+    position: 'absolute',
+    top: 110,
+    left: 10,
+    width: 94,
+    height: 94,
+    borderRadius: 47,
+    borderWidth: 2,
+  },
+  aviLabeler: {
+    borderRadius: 12,
+  },
+})