about summary refs log tree commit diff
path: root/src/view/com/modals
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/modals')
-rw-r--r--src/view/com/modals/ChangeEmail.tsx244
-rw-r--r--src/view/com/modals/CreateOrEditMuteList.tsx4
-rw-r--r--src/view/com/modals/EditProfile.tsx4
-rw-r--r--src/view/com/modals/Modal.tsx20
-rw-r--r--src/view/com/modals/ProfilePreview.tsx11
-rw-r--r--src/view/com/modals/SwitchAccount.tsx113
-rw-r--r--src/view/com/modals/VerifyEmail.tsx281
-rw-r--r--src/view/com/modals/Waitlist.tsx2
-rw-r--r--src/view/com/modals/crop-image/CropImage.web.tsx6
9 files changed, 330 insertions, 355 deletions
diff --git a/src/view/com/modals/ChangeEmail.tsx b/src/view/com/modals/ChangeEmail.tsx
index c92dabdca..012570556 100644
--- a/src/view/com/modals/ChangeEmail.tsx
+++ b/src/view/com/modals/ChangeEmail.tsx
@@ -1,11 +1,5 @@
 import React, {useState} from 'react'
-import {
-  ActivityIndicator,
-  KeyboardAvoidingView,
-  SafeAreaView,
-  StyleSheet,
-  View,
-} from 'react-native'
+import {ActivityIndicator, SafeAreaView, StyleSheet, View} from 'react-native'
 import {ScrollView, TextInput} from './util'
 import {observer} from 'mobx-react-lite'
 import {Text} from '../util/text/Text'
@@ -101,142 +95,134 @@ export const Component = observer(function Component({}: {}) {
   }
 
   return (
-    <KeyboardAvoidingView
-      behavior="padding"
-      style={[pal.view, styles.container]}>
-      <SafeAreaView style={s.flex1}>
-        <ScrollView
-          testID="changeEmailModal"
-          style={[s.flex1, isMobile && {paddingHorizontal: 18}]}>
-          <View style={styles.titleSection}>
-            <Text type="title-lg" style={[pal.text, styles.title]}>
-              {stage === Stages.InputEmail ? 'Change Your Email' : ''}
-              {stage === Stages.ConfirmCode ? 'Security Step Required' : ''}
-              {stage === Stages.Done ? 'Email Updated' : ''}
-            </Text>
-          </View>
-
-          <Text type="lg" style={[pal.textLight, {marginBottom: 10}]}>
-            {stage === Stages.InputEmail ? (
-              <>Enter your new email address below.</>
-            ) : stage === Stages.ConfirmCode ? (
-              <>
-                An email has been sent to your previous address,{' '}
-                {store.session.currentSession?.email || ''}. It includes a
-                confirmation code which you can enter below.
-              </>
-            ) : (
-              <>
-                Your email has been updated but not verified. As a next step,
-                please verify your new email.
-              </>
-            )}
+    <SafeAreaView style={[pal.view, s.flex1]}>
+      <ScrollView
+        testID="changeEmailModal"
+        style={[s.flex1, isMobile && {paddingHorizontal: 18}]}>
+        <View style={styles.titleSection}>
+          <Text type="title-lg" style={[pal.text, styles.title]}>
+            {stage === Stages.InputEmail ? 'Change Your Email' : ''}
+            {stage === Stages.ConfirmCode ? 'Security Step Required' : ''}
+            {stage === Stages.Done ? 'Email Updated' : ''}
           </Text>
+        </View>
 
-          {stage === Stages.InputEmail && (
-            <TextInput
-              testID="emailInput"
-              style={[styles.textInput, pal.border, pal.text]}
-              placeholder="alice@mail.com"
-              placeholderTextColor={pal.colors.textLight}
-              value={email}
-              onChangeText={setEmail}
-              accessible={true}
-              accessibilityLabel="Email"
-              accessibilityHint=""
-              autoCapitalize="none"
-              autoComplete="email"
-              autoCorrect={false}
-            />
-          )}
-          {stage === Stages.ConfirmCode && (
-            <TextInput
-              testID="confirmCodeInput"
-              style={[styles.textInput, pal.border, pal.text]}
-              placeholder="XXXXX-XXXXX"
-              placeholderTextColor={pal.colors.textLight}
-              value={confirmationCode}
-              onChangeText={setConfirmationCode}
-              accessible={true}
-              accessibilityLabel="Confirmation code"
-              accessibilityHint=""
-              autoCapitalize="none"
-              autoComplete="off"
-              autoCorrect={false}
-            />
+        <Text type="lg" style={[pal.textLight, {marginBottom: 10}]}>
+          {stage === Stages.InputEmail ? (
+            <>Enter your new email address below.</>
+          ) : stage === Stages.ConfirmCode ? (
+            <>
+              An email has been sent to your previous address,{' '}
+              {store.session.currentSession?.email || ''}. It includes a
+              confirmation code which you can enter below.
+            </>
+          ) : (
+            <>
+              Your email has been updated but not verified. As a next step,
+              please verify your new email.
+            </>
           )}
+        </Text>
 
-          {error ? (
-            <ErrorMessage message={error} style={styles.error} />
-          ) : undefined}
+        {stage === Stages.InputEmail && (
+          <TextInput
+            testID="emailInput"
+            style={[styles.textInput, pal.border, pal.text]}
+            placeholder="alice@mail.com"
+            placeholderTextColor={pal.colors.textLight}
+            value={email}
+            onChangeText={setEmail}
+            accessible={true}
+            accessibilityLabel="Email"
+            accessibilityHint=""
+            autoCapitalize="none"
+            autoComplete="email"
+            autoCorrect={false}
+          />
+        )}
+        {stage === Stages.ConfirmCode && (
+          <TextInput
+            testID="confirmCodeInput"
+            style={[styles.textInput, pal.border, pal.text]}
+            placeholder="XXXXX-XXXXX"
+            placeholderTextColor={pal.colors.textLight}
+            value={confirmationCode}
+            onChangeText={setConfirmationCode}
+            accessible={true}
+            accessibilityLabel="Confirmation code"
+            accessibilityHint=""
+            autoCapitalize="none"
+            autoComplete="off"
+            autoCorrect={false}
+          />
+        )}
 
-          <View style={[styles.btnContainer]}>
-            {isProcessing ? (
-              <View style={styles.btn}>
-                <ActivityIndicator color="#fff" />
-              </View>
-            ) : (
-              <View style={{gap: 6}}>
-                {stage === Stages.InputEmail && (
-                  <Button
-                    testID="requestChangeBtn"
-                    type="primary"
-                    onPress={onRequestChange}
-                    accessibilityLabel="Request Change"
-                    accessibilityHint=""
-                    label="Request Change"
-                    labelContainerStyle={{justifyContent: 'center', padding: 4}}
-                    labelStyle={[s.f18]}
-                  />
-                )}
-                {stage === Stages.ConfirmCode && (
-                  <Button
-                    testID="confirmBtn"
-                    type="primary"
-                    onPress={onConfirm}
-                    accessibilityLabel="Confirm Change"
-                    accessibilityHint=""
-                    label="Confirm Change"
-                    labelContainerStyle={{justifyContent: 'center', padding: 4}}
-                    labelStyle={[s.f18]}
-                  />
-                )}
-                {stage === Stages.Done && (
-                  <Button
-                    testID="verifyBtn"
-                    type="primary"
-                    onPress={onVerify}
-                    accessibilityLabel="Verify New Email"
-                    accessibilityHint=""
-                    label="Verify New Email"
-                    labelContainerStyle={{justifyContent: 'center', padding: 4}}
-                    labelStyle={[s.f18]}
-                  />
-                )}
+        {error ? (
+          <ErrorMessage message={error} style={styles.error} />
+        ) : undefined}
+
+        <View style={[styles.btnContainer]}>
+          {isProcessing ? (
+            <View style={styles.btn}>
+              <ActivityIndicator color="#fff" />
+            </View>
+          ) : (
+            <View style={{gap: 6}}>
+              {stage === Stages.InputEmail && (
+                <Button
+                  testID="requestChangeBtn"
+                  type="primary"
+                  onPress={onRequestChange}
+                  accessibilityLabel="Request Change"
+                  accessibilityHint=""
+                  label="Request Change"
+                  labelContainerStyle={{justifyContent: 'center', padding: 4}}
+                  labelStyle={[s.f18]}
+                />
+              )}
+              {stage === Stages.ConfirmCode && (
                 <Button
-                  testID="cancelBtn"
-                  type="default"
-                  onPress={() => store.shell.closeModal()}
-                  accessibilityLabel="Cancel"
+                  testID="confirmBtn"
+                  type="primary"
+                  onPress={onConfirm}
+                  accessibilityLabel="Confirm Change"
                   accessibilityHint=""
-                  label="Cancel"
+                  label="Confirm Change"
                   labelContainerStyle={{justifyContent: 'center', padding: 4}}
                   labelStyle={[s.f18]}
                 />
-              </View>
-            )}
-          </View>
-        </ScrollView>
-      </SafeAreaView>
-    </KeyboardAvoidingView>
+              )}
+              {stage === Stages.Done && (
+                <Button
+                  testID="verifyBtn"
+                  type="primary"
+                  onPress={onVerify}
+                  accessibilityLabel="Verify New Email"
+                  accessibilityHint=""
+                  label="Verify New Email"
+                  labelContainerStyle={{justifyContent: 'center', padding: 4}}
+                  labelStyle={[s.f18]}
+                />
+              )}
+              <Button
+                testID="cancelBtn"
+                type="default"
+                onPress={() => store.shell.closeModal()}
+                accessibilityLabel="Cancel"
+                accessibilityHint=""
+                label="Cancel"
+                labelContainerStyle={{justifyContent: 'center', padding: 4}}
+                labelStyle={[s.f18]}
+              />
+            </View>
+          )}
+        </View>
+      </ScrollView>
+    </SafeAreaView>
   )
 })
 
 const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    paddingBottom: isWeb ? 0 : 40,
-  },
   titleSection: {
     paddingTop: isWeb ? 0 : 4,
     paddingBottom: isWeb ? 14 : 10,
diff --git a/src/view/com/modals/CreateOrEditMuteList.tsx b/src/view/com/modals/CreateOrEditMuteList.tsx
index 3f3cfc5f0..4a440afeb 100644
--- a/src/view/com/modals/CreateOrEditMuteList.tsx
+++ b/src/view/com/modals/CreateOrEditMuteList.tsx
@@ -18,7 +18,7 @@ import {ListModel} from 'state/models/content/list'
 import {s, colors, gradients} from 'lib/styles'
 import {enforceLen} from 'lib/strings/helpers'
 import {compressIfNeeded} from 'lib/media/manip'
-import {UserAvatar} from '../util/UserAvatar'
+import {EditableUserAvatar} from '../util/UserAvatar'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useTheme} from 'lib/ThemeContext'
 import {useAnalytics} from 'lib/analytics/analytics'
@@ -148,7 +148,7 @@ export function Component({
         )}
         <Text style={[styles.label, pal.text]}>List Avatar</Text>
         <View style={[styles.avi, {borderColor: pal.colors.background}]}>
-          <UserAvatar
+          <EditableUserAvatar
             type="list"
             size={80}
             avatar={avatar}
diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx
index 620aad9fc..58d0857ad 100644
--- a/src/view/com/modals/EditProfile.tsx
+++ b/src/view/com/modals/EditProfile.tsx
@@ -20,7 +20,7 @@ import {enforceLen} from 'lib/strings/helpers'
 import {MAX_DISPLAY_NAME, MAX_DESCRIPTION} from 'lib/constants'
 import {compressIfNeeded} from 'lib/media/manip'
 import {UserBanner} from '../util/UserBanner'
-import {UserAvatar} from '../util/UserAvatar'
+import {EditableUserAvatar} from '../util/UserAvatar'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useTheme} from 'lib/ThemeContext'
 import {useAnalytics} from 'lib/analytics/analytics'
@@ -153,7 +153,7 @@ export function Component({
             onSelectNewBanner={onSelectNewBanner}
           />
           <View style={[styles.avi, {borderColor: pal.colors.background}]}>
-            <UserAvatar
+            <EditableUserAvatar
               size={80}
               avatar={userAvatar}
               onSelectNewAvatar={onSelectNewAvatar}
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index 4f3f424a3..1fe1299d7 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -1,11 +1,12 @@
 import React, {useRef, useEffect} from 'react'
 import {StyleSheet} from 'react-native'
-import {SafeAreaView} from 'react-native-safe-area-context'
+import {SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-context'
 import {observer} from 'mobx-react-lite'
 import BottomSheet from '@gorhom/bottom-sheet'
 import {useStores} from 'state/index'
 import {createCustomBackdrop} from '../util/BottomSheetCustomBackdrop'
 import {usePalette} from 'lib/hooks/usePalette'
+import {timeout} from 'lib/async/timeout'
 import {navigate} from '../../../Navigation'
 import once from 'lodash.once'
 
@@ -36,11 +37,13 @@ import * as SwitchAccountModal from './SwitchAccount'
 import * as LinkWarningModal from './LinkWarning'
 
 const DEFAULT_SNAPPOINTS = ['90%']
+const HANDLE_HEIGHT = 24
 
 export const ModalsContainer = observer(function ModalsContainer() {
   const store = useStores()
   const bottomSheetRef = useRef<BottomSheet>(null)
   const pal = usePalette('default')
+  const safeAreaInsets = useSafeAreaInsets()
 
   const activeModal =
     store.shell.activeModals[store.shell.activeModals.length - 1]
@@ -53,12 +56,16 @@ export const ModalsContainer = observer(function ModalsContainer() {
       navigateOnce('Profile', {name: activeModal.did})
     }
   }
-  const onBottomSheetChange = (snapPoint: number) => {
+  const onBottomSheetChange = async (snapPoint: number) => {
     if (snapPoint === -1) {
       store.shell.closeModal()
     } else if (activeModal?.name === 'profile-preview' && snapPoint === 1) {
-      // ensure we navigate to Profile and close the modal
-      navigateOnce('Profile', {name: activeModal.did})
+      await navigateOnce('Profile', {name: activeModal.did})
+      // There is no particular callback for when the view has actually been presented.
+      // This delay gives us a decent chance the navigation has flushed *and* images have loaded.
+      // It's acceptable because the data is already being fetched + it usually takes longer anyway.
+      // TODO: Figure out why avatar/cover don't always show instantly from cache.
+      await timeout(200)
       store.shell.closeModal()
     }
   }
@@ -75,6 +82,7 @@ export const ModalsContainer = observer(function ModalsContainer() {
     }
   }, [store.shell.isModalActive, bottomSheetRef, activeModal?.name])
 
+  let needsSafeTopInset = false
   let snapPoints: (string | number)[] = DEFAULT_SNAPPOINTS
   let element
   if (activeModal?.name === 'confirm') {
@@ -86,6 +94,7 @@ export const ModalsContainer = observer(function ModalsContainer() {
   } else if (activeModal?.name === 'profile-preview') {
     snapPoints = ProfilePreviewModal.snapPoints
     element = <ProfilePreviewModal.Component {...activeModal} />
+    needsSafeTopInset = true // Need to align with the target profile screen.
   } else if (activeModal?.name === 'server-input') {
     snapPoints = ServerInputModal.snapPoints
     element = <ServerInputModal.Component {...activeModal} />
@@ -164,10 +173,13 @@ export const ModalsContainer = observer(function ModalsContainer() {
     )
   }
 
+  const topInset = needsSafeTopInset ? safeAreaInsets.top - HANDLE_HEIGHT : 0
   return (
     <BottomSheet
       ref={bottomSheetRef}
       snapPoints={snapPoints}
+      topInset={topInset}
+      handleHeight={HANDLE_HEIGHT}
       index={store.shell.isModalActive ? 0 : -1}
       enablePanDownToClose
       android_keyboardInputMode="adjustResize"
diff --git a/src/view/com/modals/ProfilePreview.tsx b/src/view/com/modals/ProfilePreview.tsx
index 225a3972b..dad02aa5e 100644
--- a/src/view/com/modals/ProfilePreview.tsx
+++ b/src/view/com/modals/ProfilePreview.tsx
@@ -9,7 +9,6 @@ import {useAnalytics} from 'lib/analytics/analytics'
 import {ProfileHeader} from '../profile/ProfileHeader'
 import {InfoCircleIcon} from 'lib/icons'
 import {useNavigationState} from '@react-navigation/native'
-import {isIOS} from 'platform/detection'
 import {s} from 'lib/styles'
 
 export const snapPoints = [520, '100%']
@@ -36,11 +35,7 @@ export const Component = observer(function ProfilePreviewImpl({
 
   return (
     <View testID="profilePreview" style={[pal.view, s.flex1]}>
-      <View
-        style={[
-          styles.headerWrapper,
-          isLoading && isIOS && styles.headerPositionAdjust,
-        ]}>
+      <View style={[styles.headerWrapper]}>
         <ProfileHeader
           view={model}
           hideBackButton
@@ -70,10 +65,6 @@ const styles = StyleSheet.create({
   headerWrapper: {
     height: 440,
   },
-  headerPositionAdjust: {
-    // HACK align the header for the profilescreen transition -prf
-    paddingTop: 23,
-  },
   hintWrapper: {
     height: 80,
   },
diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx
index 51d75e3ef..d5fa32692 100644
--- a/src/view/com/modals/SwitchAccount.tsx
+++ b/src/view/com/modals/SwitchAccount.tsx
@@ -37,74 +37,69 @@ export function Component({}: {}) {
   }, [track, store])
 
   return (
-    <View style={[styles.container, pal.view]}>
+    <BottomSheetScrollView
+      style={[styles.container, pal.view]}
+      contentContainerStyle={[styles.innerContainer, pal.view]}>
       <Text type="title-xl" style={[styles.title, pal.text]}>
         Switch Account
       </Text>
-      <BottomSheetScrollView
-        style={styles.container}
-        contentContainerStyle={[styles.innerContainer, pal.view]}>
-        {isSwitching ? (
+      {isSwitching ? (
+        <View style={[pal.view, styles.linkCard]}>
+          <ActivityIndicator />
+        </View>
+      ) : (
+        <Link href={makeProfileLink(store.me)} title="Your profile" noFeedback>
           <View style={[pal.view, styles.linkCard]}>
-            <ActivityIndicator />
-          </View>
-        ) : (
-          <Link
-            href={makeProfileLink(store.me)}
-            title="Your profile"
-            noFeedback>
-            <View style={[pal.view, styles.linkCard]}>
-              <View style={styles.avi}>
-                <UserAvatar size={40} avatar={store.me.avatar} />
-              </View>
-              <View style={[s.flex1]}>
-                <Text type="md-bold" style={pal.text} numberOfLines={1}>
-                  {store.me.displayName || store.me.handle}
-                </Text>
-                <Text type="sm" style={pal.textLight} numberOfLines={1}>
-                  {store.me.handle}
-                </Text>
-              </View>
-              <TouchableOpacity
-                testID="signOutBtn"
-                onPress={isSwitching ? undefined : onPressSignout}
-                accessibilityRole="button"
-                accessibilityLabel="Sign out"
-                accessibilityHint={`Signs ${store.me.displayName} out of Bluesky`}>
-                <Text type="lg" style={pal.link}>
-                  Sign out
-                </Text>
-              </TouchableOpacity>
-            </View>
-          </Link>
-        )}
-        {store.session.switchableAccounts.map(account => (
-          <TouchableOpacity
-            testID={`switchToAccountBtn-${account.handle}`}
-            key={account.did}
-            style={[pal.view, styles.linkCard, isSwitching && styles.dimmed]}
-            onPress={
-              isSwitching ? undefined : () => onPressSwitchAccount(account)
-            }
-            accessibilityRole="button"
-            accessibilityLabel={`Switch to ${account.handle}`}
-            accessibilityHint="Switches the account you are logged in to">
             <View style={styles.avi}>
-              <UserAvatar size={40} avatar={account.aviUrl} />
+              <UserAvatar size={40} avatar={store.me.avatar} />
             </View>
             <View style={[s.flex1]}>
-              <Text type="md-bold" style={pal.text}>
-                {account.displayName || account.handle}
+              <Text type="md-bold" style={pal.text} numberOfLines={1}>
+                {store.me.displayName || store.me.handle}
               </Text>
-              <Text type="sm" style={pal.textLight}>
-                {account.handle}
+              <Text type="sm" style={pal.textLight} numberOfLines={1}>
+                {store.me.handle}
               </Text>
             </View>
-            <AccountDropdownBtn handle={account.handle} />
-          </TouchableOpacity>
-        ))}
-      </BottomSheetScrollView>
-    </View>
+            <TouchableOpacity
+              testID="signOutBtn"
+              onPress={isSwitching ? undefined : onPressSignout}
+              accessibilityRole="button"
+              accessibilityLabel="Sign out"
+              accessibilityHint={`Signs ${store.me.displayName} out of Bluesky`}>
+              <Text type="lg" style={pal.link}>
+                Sign out
+              </Text>
+            </TouchableOpacity>
+          </View>
+        </Link>
+      )}
+      {store.session.switchableAccounts.map(account => (
+        <TouchableOpacity
+          testID={`switchToAccountBtn-${account.handle}`}
+          key={account.did}
+          style={[pal.view, styles.linkCard, isSwitching && styles.dimmed]}
+          onPress={
+            isSwitching ? undefined : () => onPressSwitchAccount(account)
+          }
+          accessibilityRole="button"
+          accessibilityLabel={`Switch to ${account.handle}`}
+          accessibilityHint="Switches the account you are logged in to">
+          <View style={styles.avi}>
+            <UserAvatar size={40} avatar={account.aviUrl} />
+          </View>
+          <View style={[s.flex1]}>
+            <Text type="md-bold" style={pal.text}>
+              {account.displayName || account.handle}
+            </Text>
+            <Text type="sm" style={pal.textLight}>
+              {account.handle}
+            </Text>
+          </View>
+          <AccountDropdownBtn handle={account.handle} />
+        </TouchableOpacity>
+      ))}
+    </BottomSheetScrollView>
   )
 }
 
@@ -113,7 +108,7 @@ const styles = StyleSheet.create({
     flex: 1,
   },
   innerContainer: {
-    paddingBottom: 20,
+    paddingBottom: 40,
   },
   title: {
     textAlign: 'center',
diff --git a/src/view/com/modals/VerifyEmail.tsx b/src/view/com/modals/VerifyEmail.tsx
index 0a626a4ef..9fe8811b0 100644
--- a/src/view/com/modals/VerifyEmail.tsx
+++ b/src/view/com/modals/VerifyEmail.tsx
@@ -1,7 +1,6 @@
 import React, {useState} from 'react'
 import {
   ActivityIndicator,
-  KeyboardAvoidingView,
   Pressable,
   SafeAreaView,
   StyleSheet,
@@ -82,169 +81,163 @@ export const Component = observer(function Component({
   }
 
   return (
-    <KeyboardAvoidingView
-      behavior="padding"
-      style={[pal.view, styles.container]}>
-      <SafeAreaView style={s.flex1}>
-        <ScrollView
-          testID="verifyEmailModal"
-          style={[s.flex1, isMobile && {paddingHorizontal: 18}]}>
-          {stage === Stages.Reminder && <ReminderIllustration />}
-          <View style={styles.titleSection}>
-            <Text type="title-lg" style={[pal.text, styles.title]}>
-              {stage === Stages.Reminder ? 'Please Verify Your Email' : ''}
-              {stage === Stages.ConfirmCode ? 'Enter Confirmation Code' : ''}
-              {stage === Stages.Email ? 'Verify Your Email' : ''}
-            </Text>
-          </View>
-
-          <Text type="lg" style={[pal.textLight, {marginBottom: 10}]}>
-            {stage === Stages.Reminder ? (
-              <>
-                Your email has not yet been verified. This is an important
-                security step which we recommend.
-              </>
-            ) : stage === Stages.Email ? (
-              <>
-                This is important in case you ever need to change your email or
-                reset your password.
-              </>
-            ) : stage === Stages.ConfirmCode ? (
-              <>
-                An email has been sent to{' '}
-                {store.session.currentSession?.email || ''}. It includes a
-                confirmation code which you can enter below.
-              </>
-            ) : (
-              ''
-            )}
+    <SafeAreaView style={[pal.view, s.flex1]}>
+      <ScrollView
+        testID="verifyEmailModal"
+        style={[s.flex1, isMobile && {paddingHorizontal: 18}]}>
+        {stage === Stages.Reminder && <ReminderIllustration />}
+        <View style={styles.titleSection}>
+          <Text type="title-lg" style={[pal.text, styles.title]}>
+            {stage === Stages.Reminder ? 'Please Verify Your Email' : ''}
+            {stage === Stages.ConfirmCode ? 'Enter Confirmation Code' : ''}
+            {stage === Stages.Email ? 'Verify Your Email' : ''}
           </Text>
+        </View>
 
-          {stage === Stages.Email ? (
+        <Text type="lg" style={[pal.textLight, {marginBottom: 10}]}>
+          {stage === Stages.Reminder ? (
             <>
-              <View style={styles.emailContainer}>
-                <FontAwesomeIcon
-                  icon="envelope"
-                  color={pal.colors.text}
-                  size={16}
-                />
-                <Text
-                  type="xl-medium"
-                  style={[pal.text, s.flex1, {minWidth: 0}]}>
-                  {store.session.currentSession?.email || ''}
-                </Text>
-              </View>
-              <Pressable
-                accessibilityRole="link"
-                accessibilityLabel="Change my email"
-                accessibilityHint=""
-                onPress={onEmailIncorrect}
-                style={styles.changeEmailLink}>
-                <Text type="lg" style={pal.link}>
-                  Change
-                </Text>
-              </Pressable>
+              Your email has not yet been verified. This is an important
+              security step which we recommend.
+            </>
+          ) : stage === Stages.Email ? (
+            <>
+              This is important in case you ever need to change your email or
+              reset your password.
             </>
           ) : stage === Stages.ConfirmCode ? (
-            <TextInput
-              testID="confirmCodeInput"
-              style={[styles.textInput, pal.border, pal.text]}
-              placeholder="XXXXX-XXXXX"
-              placeholderTextColor={pal.colors.textLight}
-              value={confirmationCode}
-              onChangeText={setConfirmationCode}
-              accessible={true}
-              accessibilityLabel="Confirmation code"
+            <>
+              An email has been sent to{' '}
+              {store.session.currentSession?.email || ''}. It includes a
+              confirmation code which you can enter below.
+            </>
+          ) : (
+            ''
+          )}
+        </Text>
+
+        {stage === Stages.Email ? (
+          <>
+            <View style={styles.emailContainer}>
+              <FontAwesomeIcon
+                icon="envelope"
+                color={pal.colors.text}
+                size={16}
+              />
+              <Text type="xl-medium" style={[pal.text, s.flex1, {minWidth: 0}]}>
+                {store.session.currentSession?.email || ''}
+              </Text>
+            </View>
+            <Pressable
+              accessibilityRole="link"
+              accessibilityLabel="Change my email"
               accessibilityHint=""
-              autoCapitalize="none"
-              autoComplete="off"
-              autoCorrect={false}
-            />
-          ) : undefined}
+              onPress={onEmailIncorrect}
+              style={styles.changeEmailLink}>
+              <Text type="lg" style={pal.link}>
+                Change
+              </Text>
+            </Pressable>
+          </>
+        ) : stage === Stages.ConfirmCode ? (
+          <TextInput
+            testID="confirmCodeInput"
+            style={[styles.textInput, pal.border, pal.text]}
+            placeholder="XXXXX-XXXXX"
+            placeholderTextColor={pal.colors.textLight}
+            value={confirmationCode}
+            onChangeText={setConfirmationCode}
+            accessible={true}
+            accessibilityLabel="Confirmation code"
+            accessibilityHint=""
+            autoCapitalize="none"
+            autoComplete="off"
+            autoCorrect={false}
+          />
+        ) : undefined}
 
-          {error ? (
-            <ErrorMessage message={error} style={styles.error} />
-          ) : undefined}
+        {error ? (
+          <ErrorMessage message={error} style={styles.error} />
+        ) : undefined}
 
-          <View style={[styles.btnContainer]}>
-            {isProcessing ? (
-              <View style={styles.btn}>
-                <ActivityIndicator color="#fff" />
-              </View>
-            ) : (
-              <View style={{gap: 6}}>
-                {stage === Stages.Reminder && (
+        <View style={[styles.btnContainer]}>
+          {isProcessing ? (
+            <View style={styles.btn}>
+              <ActivityIndicator color="#fff" />
+            </View>
+          ) : (
+            <View style={{gap: 6}}>
+              {stage === Stages.Reminder && (
+                <Button
+                  testID="getStartedBtn"
+                  type="primary"
+                  onPress={() => setStage(Stages.Email)}
+                  accessibilityLabel="Get Started"
+                  accessibilityHint=""
+                  label="Get Started"
+                  labelContainerStyle={{justifyContent: 'center', padding: 4}}
+                  labelStyle={[s.f18]}
+                />
+              )}
+              {stage === Stages.Email && (
+                <>
                   <Button
-                    testID="getStartedBtn"
+                    testID="sendEmailBtn"
                     type="primary"
-                    onPress={() => setStage(Stages.Email)}
-                    accessibilityLabel="Get Started"
+                    onPress={onSendEmail}
+                    accessibilityLabel="Send Confirmation Email"
                     accessibilityHint=""
-                    label="Get Started"
-                    labelContainerStyle={{justifyContent: 'center', padding: 4}}
+                    label="Send Confirmation Email"
+                    labelContainerStyle={{
+                      justifyContent: 'center',
+                      padding: 4,
+                    }}
                     labelStyle={[s.f18]}
                   />
-                )}
-                {stage === Stages.Email && (
-                  <>
-                    <Button
-                      testID="sendEmailBtn"
-                      type="primary"
-                      onPress={onSendEmail}
-                      accessibilityLabel="Send Confirmation Email"
-                      accessibilityHint=""
-                      label="Send Confirmation Email"
-                      labelContainerStyle={{
-                        justifyContent: 'center',
-                        padding: 4,
-                      }}
-                      labelStyle={[s.f18]}
-                    />
-                    <Button
-                      testID="haveCodeBtn"
-                      type="default"
-                      accessibilityLabel="I have a code"
-                      accessibilityHint=""
-                      label="I have a confirmation code"
-                      labelContainerStyle={{
-                        justifyContent: 'center',
-                        padding: 4,
-                      }}
-                      labelStyle={[s.f18]}
-                      onPress={() => setStage(Stages.ConfirmCode)}
-                    />
-                  </>
-                )}
-                {stage === Stages.ConfirmCode && (
                   <Button
-                    testID="confirmBtn"
-                    type="primary"
-                    onPress={onConfirm}
-                    accessibilityLabel="Confirm"
+                    testID="haveCodeBtn"
+                    type="default"
+                    accessibilityLabel="I have a code"
                     accessibilityHint=""
-                    label="Confirm"
-                    labelContainerStyle={{justifyContent: 'center', padding: 4}}
+                    label="I have a confirmation code"
+                    labelContainerStyle={{
+                      justifyContent: 'center',
+                      padding: 4,
+                    }}
                     labelStyle={[s.f18]}
+                    onPress={() => setStage(Stages.ConfirmCode)}
                   />
-                )}
+                </>
+              )}
+              {stage === Stages.ConfirmCode && (
                 <Button
-                  testID="cancelBtn"
-                  type="default"
-                  onPress={() => store.shell.closeModal()}
-                  accessibilityLabel={
-                    stage === Stages.Reminder ? 'Not right now' : 'Cancel'
-                  }
+                  testID="confirmBtn"
+                  type="primary"
+                  onPress={onConfirm}
+                  accessibilityLabel="Confirm"
                   accessibilityHint=""
-                  label={stage === Stages.Reminder ? 'Not right now' : 'Cancel'}
+                  label="Confirm"
                   labelContainerStyle={{justifyContent: 'center', padding: 4}}
                   labelStyle={[s.f18]}
                 />
-              </View>
-            )}
-          </View>
-        </ScrollView>
-      </SafeAreaView>
-    </KeyboardAvoidingView>
+              )}
+              <Button
+                testID="cancelBtn"
+                type="default"
+                onPress={() => store.shell.closeModal()}
+                accessibilityLabel={
+                  stage === Stages.Reminder ? 'Not right now' : 'Cancel'
+                }
+                accessibilityHint=""
+                label={stage === Stages.Reminder ? 'Not right now' : 'Cancel'}
+                labelContainerStyle={{justifyContent: 'center', padding: 4}}
+                labelStyle={[s.f18]}
+              />
+            </View>
+          )}
+        </View>
+      </ScrollView>
+    </SafeAreaView>
   )
 })
 
@@ -274,10 +267,6 @@ function ReminderIllustration() {
 }
 
 const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    paddingBottom: isWeb ? 0 : 40,
-  },
   titleSection: {
     paddingTop: isWeb ? 0 : 4,
     paddingBottom: isWeb ? 14 : 10,
diff --git a/src/view/com/modals/Waitlist.tsx b/src/view/com/modals/Waitlist.tsx
index 1104c0a39..0fb371fe4 100644
--- a/src/view/com/modals/Waitlist.tsx
+++ b/src/view/com/modals/Waitlist.tsx
@@ -77,6 +77,8 @@ export function Component({}: {}) {
           keyboardAppearance={theme.colorScheme}
           value={email}
           onChangeText={setEmail}
+          onSubmitEditing={onPressSignup}
+          enterKeyHint="done"
           accessible={true}
           accessibilityLabel="Email"
           accessibilityHint="Input your email to get on the Bluesky waitlist"
diff --git a/src/view/com/modals/crop-image/CropImage.web.tsx b/src/view/com/modals/crop-image/CropImage.web.tsx
index c5959cf4c..8e35201d1 100644
--- a/src/view/com/modals/crop-image/CropImage.web.tsx
+++ b/src/view/com/modals/crop-image/CropImage.web.tsx
@@ -100,7 +100,7 @@ export function Component({
           accessibilityHint="Sets image aspect ratio to wide">
           <RectWideIcon
             size={24}
-            style={as === AspectRatio.Wide ? s.blue3 : undefined}
+            style={as === AspectRatio.Wide ? s.blue3 : pal.text}
           />
         </TouchableOpacity>
         <TouchableOpacity
@@ -110,7 +110,7 @@ export function Component({
           accessibilityHint="Sets image aspect ratio to tall">
           <RectTallIcon
             size={24}
-            style={as === AspectRatio.Tall ? s.blue3 : undefined}
+            style={as === AspectRatio.Tall ? s.blue3 : pal.text}
           />
         </TouchableOpacity>
         <TouchableOpacity
@@ -120,7 +120,7 @@ export function Component({
           accessibilityHint="Sets image aspect ratio to square">
           <SquareIcon
             size={24}
-            style={as === AspectRatio.Square ? s.blue3 : undefined}
+            style={as === AspectRatio.Square ? s.blue3 : pal.text}
           />
         </TouchableOpacity>
       </View>