diff --git a/native/profile/profile-screen.react.js b/native/profile/profile-screen.react.js
index a5622292c..e1dea066d 100644
--- a/native/profile/profile-screen.react.js
+++ b/native/profile/profile-screen.react.js
@@ -1,642 +1,645 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { View, Text, Platform, ScrollView } from 'react-native';
import uuid from 'uuid';
import {
logOutActionTypes,
useLogOut,
usePrimaryDeviceLogOut,
useSecondaryDeviceLogOut,
} from 'lib/actions/user-actions.js';
import { useStringForUser } from 'lib/hooks/ens-cache.js';
import { useCheckIfPrimaryDevice } from 'lib/hooks/primary-device-hooks.js';
import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js';
import { accountHasPassword } from 'lib/shared/account-utils.js';
import {
type OutboundDMOperationSpecification,
dmOperationSpecificationTypes,
} from 'lib/shared/dm-ops/dm-op-utils.js';
import { useProcessAndSendDMOperation } from 'lib/shared/dm-ops/process-dm-ops.js';
import type { LogOutResult } from 'lib/types/account-types.js';
import type { DMCreateThreadOperation } from 'lib/types/dm-ops';
import { thickThreadTypes } from 'lib/types/thread-types-enum.js';
import { type CurrentUserInfo } from 'lib/types/user-types.js';
import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js';
import {
useDispatchActionPromise,
type DispatchActionPromise,
} from 'lib/utils/redux-promise-utils.js';
-import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js';
+import {
+ usingCommServicesAccessToken,
+ usingRestoreFlow,
+} from 'lib/utils/services-utils.js';
import type { ProfileNavigationProp } from './profile.react.js';
import { deleteNativeCredentialsFor } from '../account/native-credentials.js';
import EditUserAvatar from '../avatars/edit-user-avatar.react.js';
import Action from '../components/action-row.react.js';
import Button from '../components/button.react.js';
import EditSettingButton from '../components/edit-setting-button.react.js';
import SingleLine from '../components/single-line.react.js';
import type { NavigationRoute } from '../navigation/route-names.js';
import {
EditPasswordRouteName,
DeleteAccountRouteName,
BuildInfoRouteName,
DevToolsRouteName,
AppearancePreferencesRouteName,
FriendListRouteName,
BlockListRouteName,
PrivacyPreferencesRouteName,
DefaultNotificationsPreferencesRouteName,
LinkedDevicesRouteName,
BackupMenuRouteName,
KeyserverSelectionListRouteName,
TunnelbrokerMenuRouteName,
FarcasterAccountSettingsRouteName,
} from '../navigation/route-names.js';
import { useSelector } from '../redux/redux-utils.js';
import { type Colors, useColors, useStyles } from '../themes/colors.js';
import Alert from '../utils/alert.js';
import { useShowVersionUnsupportedAlert } from '../utils/hooks.js';
import { useStaffCanSee } from '../utils/staff-utils.js';
type ProfileRowProps = {
+content: string,
+onPress: () => void,
+danger?: boolean,
};
function ProfileRow(props: ProfileRowProps): React.Node {
const { content, onPress, danger } = props;
return (
);
}
const unboundStyles = {
avatarSection: {
alignItems: 'center',
paddingVertical: 16,
},
container: {
flex: 1,
},
content: {
flex: 1,
},
deleteAccountButton: {
paddingHorizontal: 24,
paddingVertical: 12,
},
editPasswordButton: {
paddingTop: Platform.OS === 'android' ? 3 : 2,
},
header: {
color: 'panelBackgroundLabel',
fontSize: 12,
fontWeight: '400',
paddingBottom: 3,
paddingHorizontal: 24,
},
label: {
color: 'panelForegroundTertiaryLabel',
fontSize: 16,
paddingRight: 12,
},
loggedInLabel: {
color: 'panelForegroundTertiaryLabel',
fontSize: 16,
},
logOutText: {
color: 'link',
fontSize: 16,
paddingLeft: 6,
},
row: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
},
scrollView: {
backgroundColor: 'panelBackground',
},
scrollViewContentContainer: {
paddingTop: 24,
},
paddedRow: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 24,
paddingVertical: 10,
},
section: {
backgroundColor: 'panelForeground',
borderBottomWidth: 1,
borderColor: 'panelForegroundBorder',
borderTopWidth: 1,
marginBottom: 24,
paddingVertical: 1,
},
unpaddedSection: {
backgroundColor: 'panelForeground',
borderBottomWidth: 1,
borderColor: 'panelForegroundBorder',
borderTopWidth: 1,
marginBottom: 24,
},
username: {
color: 'panelForegroundLabel',
flex: 1,
},
value: {
color: 'panelForegroundLabel',
fontSize: 16,
textAlign: 'right',
},
};
type BaseProps = {
+navigation: ProfileNavigationProp<'ProfileScreen'>,
+route: NavigationRoute<'ProfileScreen'>,
};
type Props = {
...BaseProps,
+currentUserInfo: ?CurrentUserInfo,
+isPrimaryDevice: boolean,
+logOutLoading: boolean,
+colors: Colors,
+styles: $ReadOnly,
+dispatchActionPromise: DispatchActionPromise,
+logOut: () => Promise,
+logOutPrimaryDevice: () => Promise,
+logOutSecondaryDevice: () => Promise,
+staffCanSee: boolean,
+stringForUser: ?string,
+isAccountWithPassword: boolean,
+onCreateDMThread: () => Promise,
+currentUserFID: ?string,
};
class ProfileScreen extends React.PureComponent {
get loggedOutOrLoggingOut(): boolean {
return (
!this.props.currentUserInfo ||
this.props.currentUserInfo.anonymous ||
this.props.logOutLoading
);
}
render(): React.Node {
let developerTools,
defaultNotifications,
keyserverSelection,
tunnelbrokerMenu;
const { staffCanSee } = this.props;
if (staffCanSee) {
developerTools = (
);
defaultNotifications = (
);
keyserverSelection = (
);
tunnelbrokerMenu = (
);
}
let backupMenu;
if (staffCanSee) {
backupMenu = (
);
}
let passwordEditionUI;
if (
accountHasPassword(this.props.currentUserInfo) &&
this.props.isPrimaryDevice
) {
passwordEditionUI = (
Password
••••••••••••••••
);
}
let linkedDevices;
- if (__DEV__) {
+ if (usingRestoreFlow) {
linkedDevices = (
);
}
let farcasterAccountSettings;
if (usingCommServicesAccessToken || __DEV__) {
farcasterAccountSettings = (
);
}
let experimentalLogoutActions;
if (__DEV__) {
experimentalLogoutActions = (
<>
>
);
}
let dmActions;
if (staffCanSee) {
dmActions = (
<>
>
);
}
return (
USER AVATAR
ACCOUNT
Logged in as
{this.props.stringForUser}
{passwordEditionUI}
PREFERENCES
{defaultNotifications}
{backupMenu}
{tunnelbrokerMenu}
{farcasterAccountSettings}
{linkedDevices}
{keyserverSelection}
{developerTools}
{experimentalLogoutActions}
{dmActions}
);
}
onPressLogOut = () => {
if (this.loggedOutOrLoggingOut) {
return;
}
if (!this.props.isAccountWithPassword) {
Alert.alert(
'Log out',
'Are you sure you want to log out?',
[
{ text: 'No', style: 'cancel' },
{
text: 'Yes',
onPress: this.logOutWithoutDeletingNativeCredentialsWrapper,
style: 'destructive',
},
],
{ cancelable: true },
);
return;
}
const alertTitle =
Platform.OS === 'ios' ? 'Keep Login Info in Keychain' : 'Keep Login Info';
const alertDescription =
'We will automatically fill out log-in forms with your credentials ' +
'in the app.';
Alert.alert(
alertTitle,
alertDescription,
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Keep',
onPress: this.logOutWithoutDeletingNativeCredentialsWrapper,
},
{
text: 'Remove',
onPress: this.logOutAndDeleteNativeCredentialsWrapper,
style: 'destructive',
},
],
{ cancelable: true },
);
};
onPressNewLogout = () => {
void (async () => {
if (this.loggedOutOrLoggingOut) {
return;
}
let alertTitle, alertMessage, onPressAction;
if (this.props.isPrimaryDevice) {
alertTitle = 'Log out all devices?';
alertMessage =
'This device is your primary device, ' +
'so logging out will cause all of your other devices to log out too.';
onPressAction = this.logOutPrimaryDevice;
} else {
alertTitle = 'Log out?';
alertMessage = 'Are you sure you want to log out of this device?';
onPressAction = this.logOutSecondaryDevice;
}
Alert.alert(
alertTitle,
alertMessage,
[
{ text: 'No', style: 'cancel' },
{
text: 'Yes',
onPress: onPressAction,
style: 'destructive',
},
],
{ cancelable: true },
);
})();
};
logOutWithoutDeletingNativeCredentialsWrapper = () => {
if (this.loggedOutOrLoggingOut) {
return;
}
this.logOut();
};
logOutAndDeleteNativeCredentialsWrapper = async () => {
if (this.loggedOutOrLoggingOut) {
return;
}
await this.deleteNativeCredentials();
this.logOut();
};
logOut() {
void this.props.dispatchActionPromise(
logOutActionTypes,
this.props.logOut(),
);
}
logOutPrimaryDevice = async () => {
if (this.loggedOutOrLoggingOut) {
return;
}
void this.props.dispatchActionPromise(
logOutActionTypes,
this.props.logOutPrimaryDevice(),
);
};
logOutSecondaryDevice = async () => {
if (this.loggedOutOrLoggingOut) {
return;
}
void this.props.dispatchActionPromise(
logOutActionTypes,
this.props.logOutSecondaryDevice(),
);
};
async deleteNativeCredentials() {
await deleteNativeCredentialsFor();
}
onPressEditPassword = () => {
this.props.navigation.navigate({ name: EditPasswordRouteName });
};
onPressDeleteAccount = () => {
this.props.navigation.navigate({ name: DeleteAccountRouteName });
};
onPressFaracsterAccount = () => {
this.props.navigation.navigate({ name: FarcasterAccountSettingsRouteName });
};
onPressDevices = () => {
this.props.navigation.navigate({ name: LinkedDevicesRouteName });
};
onPressBuildInfo = () => {
this.props.navigation.navigate({ name: BuildInfoRouteName });
};
onPressDevTools = () => {
this.props.navigation.navigate({ name: DevToolsRouteName });
};
onPressAppearance = () => {
this.props.navigation.navigate({ name: AppearancePreferencesRouteName });
};
onPressPrivacy = () => {
this.props.navigation.navigate({ name: PrivacyPreferencesRouteName });
};
onPressDefaultNotifications = () => {
this.props.navigation.navigate({
name: DefaultNotificationsPreferencesRouteName,
});
};
onPressFriendList = () => {
this.props.navigation.navigate({ name: FriendListRouteName });
};
onPressBlockList = () => {
this.props.navigation.navigate({ name: BlockListRouteName });
};
onPressBackupMenu = () => {
this.props.navigation.navigate({ name: BackupMenuRouteName });
};
onPressTunnelbrokerMenu = () => {
this.props.navigation.navigate({ name: TunnelbrokerMenuRouteName });
};
onPressKeyserverSelection = () => {
this.props.navigation.navigate({ name: KeyserverSelectionListRouteName });
};
onPressCreateThread = () => {
void this.props.onCreateDMThread();
};
}
const logOutLoadingStatusSelector =
createLoadingStatusSelector(logOutActionTypes);
const ConnectedProfileScreen: React.ComponentType =
React.memo(function ConnectedProfileScreen(props: BaseProps) {
const currentUserInfo = useSelector(state => state.currentUserInfo);
const logOutLoading =
useSelector(logOutLoadingStatusSelector) === 'loading';
const colors = useColors();
const styles = useStyles(unboundStyles);
const callPrimaryDeviceLogOut = usePrimaryDeviceLogOut();
const callSecondaryDeviceLogOut = useSecondaryDeviceLogOut();
const dispatchActionPromise = useDispatchActionPromise();
const staffCanSee = useStaffCanSee();
const stringForUser = useStringForUser(currentUserInfo);
const isAccountWithPassword = useSelector(state =>
accountHasPassword(state.currentUserInfo),
);
const currentUserID = useCurrentUserFID();
const [isPrimaryDevice, setIsPrimaryDevice] = React.useState(false);
const checkIfPrimaryDevice = useCheckIfPrimaryDevice();
const showVersionUnsupportedAlert = useShowVersionUnsupportedAlert(false);
const logOutOptions = React.useMemo(
() => ({
handleUseNewFlowResponse: showVersionUnsupportedAlert,
}),
[showVersionUnsupportedAlert],
);
const callLogOut = useLogOut(logOutOptions);
const userID = useSelector(
state => state.currentUserInfo && state.currentUserInfo.id,
);
const processAndSendDMOperation = useProcessAndSendDMOperation();
const onCreateDMThread = React.useCallback(async () => {
invariant(userID, 'userID should be set');
const op: DMCreateThreadOperation = {
type: 'create_thread',
threadID: uuid.v4(),
creatorID: userID,
time: Date.now(),
threadType: thickThreadTypes.LOCAL,
memberIDs: [],
roleID: uuid.v4(),
newMessageID: uuid.v4(),
};
const specification: OutboundDMOperationSpecification = {
type: dmOperationSpecificationTypes.OUTBOUND,
op,
recipients: {
type: 'self_devices',
},
};
await processAndSendDMOperation(specification);
}, [processAndSendDMOperation, userID]);
React.useEffect(() => {
void (async () => {
const checkIfPrimaryDeviceResult = await checkIfPrimaryDevice();
setIsPrimaryDevice(checkIfPrimaryDeviceResult);
})();
}, [checkIfPrimaryDevice]);
return (
);
});
export default ConnectedProfileScreen;