diff --git a/native/profile/keyserver-selection-list.react.js b/native/profile/keyserver-selection-list.react.js --- a/native/profile/keyserver-selection-list.react.js +++ b/native/profile/keyserver-selection-list.react.js @@ -87,13 +87,6 @@ marginBottom: 24, paddingVertical: 2, }, - keyserverListItemContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingHorizontal: 24, - paddingVertical: 10, - }, separator: { backgroundColor: 'panelForegroundBorder', height: 1, diff --git a/native/profile/linked-devices-list-item.react.js b/native/profile/linked-devices-list-item.react.js new file mode 100644 --- /dev/null +++ b/native/profile/linked-devices-list-item.react.js @@ -0,0 +1,102 @@ +// @flow + +import * as React from 'react'; +import { View, TouchableOpacity } from 'react-native'; + +import { + type IdentityPlatformDetails, + identityDeviceTypes, +} from 'lib/types/identity-service-types.js'; + +import Pill from '../components/pill.react.js'; +import SWMIcon from '../components/swmansion-icon.react.js'; +import { useStyles, useColors } from '../themes/colors.js'; + +type Props = { + +deviceID: string, + +platformDetails: ?IdentityPlatformDetails, + +isPrimary: boolean, + +isThisDevice: boolean, +}; + +function LinkedDevicesListItem(props: Props): React.Node { + const { deviceID, platformDetails, isPrimary, isThisDevice } = props; + + const styles = useStyles(unboundStyles); + const colors = useColors(); + + const deviceType = platformDetails?.deviceType; + + const deviceIcon = React.useMemo(() => { + let name; + if ( + deviceType === identityDeviceTypes.IOS || + deviceType === identityDeviceTypes.ANDROID + ) { + name = 'phone'; + } else if (deviceType === identityDeviceTypes.KEYSERVER) { + name = 'cloud'; + } else if (deviceType === identityDeviceTypes.WEB) { + name = 'globe-1'; + } else { + name = 'question'; + } + return ( + + ); + }, [deviceType, colors.panelForegroundLabel]); + + const label = React.useMemo(() => { + const baseLabel = deviceID.substr(0, 7); + let finalLabel = baseLabel; + + if (isPrimary) { + finalLabel += ' (primary)'; + } + + if (isThisDevice) { + finalLabel += ' (this device)'; + } + + return finalLabel; + }, [deviceID, isPrimary, isThisDevice]); + + const deviceListItem = React.useMemo( + () => ( + + + + + + ), + [ + styles.listItemContainer, + styles.pillContainer, + label, + colors.codeBackground, + deviceIcon, + ], + ); + + return deviceListItem; +} + +const unboundStyles = { + listItemContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 24, + paddingVertical: 10, + }, + pillContainer: { + flex: 1, + alignItems: 'baseline', + }, +}; + +export default LinkedDevicesListItem; diff --git a/native/profile/linked-devices.react.js b/native/profile/linked-devices.react.js --- a/native/profile/linked-devices.react.js +++ b/native/profile/linked-devices.react.js @@ -1,17 +1,125 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; +import { Text, View, FlatList } from 'react-native'; +import { + getOwnPeerDevices, + type DeviceIDAndPlatformDetails, +} from 'lib/selectors/user-selectors.js'; +import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; + +import LinkedDevicesListItem from './linked-devices-list-item.react.js'; import type { ProfileNavigationProp } from './profile.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; +import { useSelector } from '../redux/redux-utils.js'; +import { useStyles } from '../themes/colors.js'; + +function keyExtractor(item: DeviceIDAndPlatformDetails) { + return item.deviceID; +} +function renderDeviceListItem({ + item, + index, + thisDeviceID, +}: { + +item: DeviceIDAndPlatformDetails, + +index: number, + +thisDeviceID: ?string, + ... +}) { + return ( + + ); +} type Props = { +navigation: ProfileNavigationProp<'LinkedDevices'>, +route: NavigationRoute<'LinkedDevices'>, }; // eslint-disable-next-line no-unused-vars function LinkedDevices(props: Props): React.Node { - return null; + const styles = useStyles(unboundStyles); + + const userDevicesInfos: $ReadOnlyArray = + useSelector(getOwnPeerDevices); + + const identityContext = React.useContext(IdentityClientContext); + invariant(identityContext, 'identity context not set'); + const { getAuthMetadata } = identityContext; + const [thisDeviceID, setThisDeviceID] = React.useState(null); + + React.useEffect(() => { + void (async () => { + const { deviceID } = await getAuthMetadata(); + setThisDeviceID(deviceID); + })(); + }, [getAuthMetadata]); + + const separatorComponent = React.useCallback( + () => , + [styles.separator], + ); + + const userDeviceList = React.useMemo( + () => ( + + USER DEVICES + + renderDeviceListItem({ item, index, thisDeviceID }) + } + keyExtractor={keyExtractor} + contentContainerStyle={styles.deviceListContentContainer} + ItemSeparatorComponent={separatorComponent} + /> + + ), + [ + separatorComponent, + userDevicesInfos, + styles.container, + styles.header, + styles.deviceListContentContainer, + thisDeviceID, + ], + ); + + return userDeviceList; } +const unboundStyles = { + container: { + flex: 1, + backgroundColor: 'panelBackground', + paddingTop: 24, + }, + header: { + color: 'panelBackgroundLabel', + fontSize: 12, + fontWeight: '400', + paddingBottom: 3, + paddingHorizontal: 24, + }, + deviceListContentContainer: { + backgroundColor: 'panelForeground', + borderBottomWidth: 1, + borderColor: 'panelForegroundBorder', + borderTopWidth: 1, + marginBottom: 24, + paddingVertical: 2, + }, + separator: { + backgroundColor: 'panelForegroundBorder', + height: 1, + marginHorizontal: 16, + }, +}; + export default LinkedDevices;