React Native Password Manager: The Screens

October 23, 2017

Hello everyone. In this article we will create all the application's screens.

To be able to preview the final rendering of each screen, we will install the library we will use for navigation, which will let us have each screen's header.

The navigation library is react-navigation. To install it, enter the following command:

yarn add react-navigation

Next, we will create the folder that will contain all the screens:

cd src && mkdir screens

To preview each screen as we go, we need to modify the index.js file at the root of src as follows:

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */
 
import { StackNavigator } from 'react-navigation'
 
import SetupScreen from './screens/Setup'
 
const App = StackNavigator({
  Home: {
    screen: SetupScreen,
    navigationOptions: ({ navigation }: Object) => ({
      title: 'Hello title',
      headerStyle: { backgroundColor: '#01D88D' },
      headerTintColor: 'white',
      //header:null if we want to hide the default header
    }),
  },
})
 
export default App

Here we use a simple StackNavigator with a single route. Then we will only need to replace the screen with the one we are working on.

Creating the screens

We can now move on to creating the screens.

Setup

The first screen we will create is the application's initialization screen, which appears on first use. It lets the user define the main password that will later be used to unlock the application and access their passwords.

In the screens folder, add the Setup.js file:

/*
 * @flow
 */
 
import React, { Component } from 'react'
import { ScrollView, Text, Button, View } from 'react-native'
import TextField from '../components/TextField'
import { PlateformStyleSheet } from '../common/PlatformHelper'
import { ANDROID_MARGIN, IOS_MARGIN } from '../constants/dimensions'
import { PRIMARY_TEXT, DELETE_COLOR, PRIMARY, WHITE } from '../constants/colors'
import strings from '../locales/strings'
 
type State = {
  password: string,
  confirmation: string,
}
 
export default class SetupScreen extends Component<void, void, State> {
  confirmation: Object
  state = {
    password: '',
    confirmation: '',
  }
 
  submit() {
    const { password, confirmation } = this.state
    console.log('====================================')
    console.log(`password : ${password} confirmation : ${confirmation}`)
    console.log('====================================')
  }
 
  render() {
    return (
      <ScrollView contentContainerStyle={styles.container}>
        <Text style={styles.instruction}>{strings.instructions}</Text>
        <TextField
          iconName="lock"
          iosOutline
          placeholder={strings.password}
          secureTextEntry
          value={this.state.password}
          onChangeText={text => this.setState({ password: text })}
          onSubmitEditing={() => {
            this.confirmation.focus()
          }}
          returnKeyType="next"
        />
        <TextField
          icon="lock"
          ref={c => {
            this.confirmation = c
          }}
          placeholder={strings.confirmation}
          secureTextEntry
          value={this.state.confirmation}
          onChangeText={text => this.setState({ confirmation: text })}
        />
 
        <Text style={[styles.instruction, { color: DELETE_COLOR }]}>This text will be replaced when we add redux</Text>
        <View style={styles.submit}>
          <Button title={strings.save} color={PRIMARY} onPress={() => this.submit()} />
        </View>
      </ScrollView>
    )
  }
}
 
const styles = PlateformStyleSheet({
  container: {
    flex: 1,
    paddingTop: 18,
    backgroundColor: WHITE,
  },
  instruction: {
    android: { padding: ANDROID_MARGIN },
    ios: { padding: IOS_MARGIN },
    color: PRIMARY_TEXT,
    textAlign: 'justify',
    marginBottom: 18,
  },
  submit: {
    width: 150,
 
    alignSelf: 'center',
  },
})

Then we will add the missing resources:

fr.js

...
// Setup screen,
setup: 'Initialisation',
instructions:
  'Pour utiliser Vault vous devez définir seulement un mot de passe. Vous devez le garder en sécurité car si vous le perdez vous ne pourrez pas restaurer vos données',
password: 'Mot de passe',
confirmation: 'Confirmation du mot de passe',
save: 'Enregistrer',

en.js

...
// Setup screen,
setup: 'Initialization',
instructions:
  "To use Vault you need to set only one password. You need to keep it safe because if you loose you can't recover your data ",
password: 'Password',
confirmation: 'Password confirmation',
save: 'Save',

strings.js

...
setup: I18n.t('setup'),
instructions: I18n.t('instructions'),
password: I18n.t('password'),
confirmation: I18n.t('confirmation'),
save: I18n.t('save'),

Unlock

Let's move on to the application unlock screen. Add Unlock.js:

/*
 * @flow
 */
 
import React, { Component } from 'react'
import { ScrollView, View, Image, Button, Text } from 'react-native'
import { PlateformStyleSheet } from '../common/PlatformHelper'
import TextField from '../components/TextField'
import strings from '../locales/strings'
import { ANDROID_MARGIN, IOS_MARGIN } from '../constants/dimensions'
import { PRIMARY, WHITE, DELETE_COLOR } from '../constants/colors'
 
type State = {
  password: string,
}
const image = require('../img/book.png')
 
export default class SetupScreen extends Component<void, void, State> {
  state = {
    password: '',
  }
 
  submit() {
    console.log('====================================')
    console.log(`password : ${this.state.password}`)
    console.log('====================================')
  }
 
  render() {
    return (
      <ScrollView contentContainerStyle={styles.container}>
        <Image source={image} style={styles.logo} />
        <TextField
          icon="lock"
          placeholder={strings.password}
          secureTextEntry
          value={this.state.password}
          onChangeText={text => this.setState({ password: text })}
        />
        <Text style={styles.error}>This text will be replaced when we add redux</Text>
        <View style={styles.submit}>
          <Button title={strings.unlock} color={PRIMARY} onPress={() => this.submit()} />
        </View>
      </ScrollView>
    )
  }
}
const styles = PlateformStyleSheet({
  container: {
    flex: 1,
    backgroundColor: WHITE,
    justifyContent: 'center',
    android: { padding: ANDROID_MARGIN },
    ios: { padding: IOS_MARGIN },
  },
  logo: {
    marginBottom: 32,
    alignSelf: 'center',
  },
  error: {
    marginVertical: 16,
    color: DELETE_COLOR,
    textAlign: 'justify',
  },
  submit: {
    width: 150,
    alignSelf: 'center',
  },
})

The resources:

fr.js

...
// Unlock Screen
  unlock: 'Déverrouiller',

en.js

...
// Unlock Screen
  unlock: 'Unlock',

strings.js

...
save: I18n.t('unlock'),

Passwords

Now we will add the screen that lists all passwords and also allows adding and searching passwords.

Before creating the screen, in the previous article where we created the components, I omitted a few components that we will need to create in this article. The first is the button that lets us add passwords on Android. In the components folder, add the ActionButton.js file and add the following code:

/*
 * @flow
 */
 
import React from 'react'
import { StyleSheet, TouchableNativeFeedback, View } from 'react-native'
import Icon from 'react-native-vector-icons/Ionicons'
import { ACTION_BUTTON } from '../constants/dimensions'
import { WHITE, PRIMARY } from '../constants/colors'
 
type Props = {
  onPress: () => void,
}
 
const ActionButton = (props: Props) => (
  <View style={[styles.container, { elevation: 4 }]}>
    <TouchableNativeFeedback
      background={TouchableNativeFeedback.SelectableBackgroundBorderless()}
      onPress={() => props.onPress()}
    >
      <View style={styles.container}>
        <Icon name="md-add" size={24} color={WHITE} />
      </View>
    </TouchableNativeFeedback>
  </View>
)
 
ActionButton.defaultProps = {
  onPress: () => console.log('onPress'),
}
 
export default ActionButton
const styles = StyleSheet.create({
  container: {
    height: ACTION_BUTTON,
    width: ACTION_BUTTON,
    borderRadius: ACTION_BUTTON,
    backgroundColor: PRIMARY,
    alignItems: 'center',
    justifyContent: 'center',
  },
})

Then we move on to the screen. In the screens folder, add Passwords.js:

/*
 * @flow
 */
import React, { Component } from 'react'
import { View, Platform, StyleSheet } from 'react-native'
import _ from 'lodash'
import SearchBar from '../components/SearchBar'
import PasswordList from '../components/PasswordList'
import ActionButton from '../components/ActionButton'
import { WHITE } from '../constants/colors'
import { ANDROID_MARGIN } from '../constants/dimensions'
import type { Password } from '../types/Password'
 
type State = {
  passwords: Array<Password>,
  searchResults: Array<Password>,
  searchValue: string,
}
 
class PassworsScreen extends Component<void, void, State> {
  // The state values are temporary and will change when we add redux
  state = {
    passwords: [
      { name: 'Twitter', key: 'uuid', icon: 'twitter', color: 'red' },
      { name: 'Facebook', key: 'uuid-2', icon: 'facebook', color: 'blue' },
      { name: 'Twitter', key: 'uuid-3', icon: 'twitter', color: 'red' },
      { name: 'Facebook', key: 'uuid-4', icon: 'facebook', color: 'blue' },
      { name: 'Twitter', key: 'uuid-5', icon: 'twitter', color: 'red' },
      { name: 'Facebook', key: 'uuid-6', icon: 'facebook', color: 'blue' },
    ],
    searchValue: '',
    searchResults: [
      { name: 'Twitter', key: 'uuid', icon: 'twitter', color: 'red' },
      { name: 'Facebook', key: 'uuid-2', icon: 'facebook', color: 'blue' },
      { name: 'Twitter', key: 'uuid-3', icon: 'twitter', color: 'red' },
      { name: 'Facebook', key: 'uuid-4', icon: 'facebook', color: 'blue' },
      { name: 'Twitter', key: 'uuid-5', icon: 'twitter', color: 'red' },
      { name: 'Facebook', key: 'uuid-6', icon: 'facebook', color: 'blue' },
    ],
  }
 
  showPassword(password: Password) {
    console.log('====================================')
    console.log(JSON.stringify(password))
    console.log('====================================')
  }
 
  searchPassword(search: string) {
    const result = _.filter(this.state.passwords, data => data.name.toLowerCase().includes(search.toLowerCase()))
    this.setState({
      searchResults: search.length > 0 ? result : this.state.passwords,
      searchValue: search,
    })
  }
 
  clearSearch() {
    this.setState({
      searchResults: this.state.passwords,
      searchValue: '',
    })
  }
 
  addNewItem() {
    console.log('====================================')
    console.log('add new item')
    console.log('====================================')
  }
 
  openMenu() {
    console.log('====================================')
    console.log('open drawer')
    console.log('====================================')
  }
 
  renderActionButton() {
    if (Platform.OS === 'android') {
      return (
        <View style={styles.action}>
          <ActionButton onPress={() => this.addNewItem()} />
        </View>
      )
    }
    return null
  }
  render() {
    const { searchResults, searchValue } = this.state
 
    return (
      <View style={styles.container}>
        <SearchBar
          onChangeText={text => this.searchPassword(text)}
          addItem={() => this.addNewItem()}
          onClear={() => this.clearSearch()}
          openMenu={() => this.openMenu()}
        />
        <PasswordList
          data={[]}
          onItemPress={item => this.showPassword(item)}
          fromSearch={searchValue.length > 0}
          emptyOnPress={() => this.addNewItem()}
        />
        {this.renderActionButton()}
      </View>
    )
  }
}
 
export default PassworsScreen
 
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: WHITE,
  },
  action: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    marginBottom: ANDROID_MARGIN,
    alignItems: 'center',
  },
})

Here we can clearly see that the screens are only an assembly of the components created earlier. All the states and actions we see in the rest of this article will be modified when we add redux to the project.

As you can see, the PasswordList component has an emptyOnPress property. If you remember, when we created the Empty component that is displayed when the password list is empty, we added a boolean property named fromSearch. This property is meant to define whether the list is empty because a search returned no results, or because the list contains no passwords by default.

When we are in the second case, the component displays a button for adding passwords. However, while creating the components, I forgot to add the button's onPress property to the PasswordList component, so we will do that now.

In the components/PasswordList index.js file, first add the emptyOnPress property to the Flow Props type:

type Props = {
  data: Array<Password>,
  onItemPress: (item: Password) => void,
  fromSearch: boolean,
  emptyOnPress: () => void,
}

Then add it when using our EmptyComponent in the FlatList's "ListEmptyComponent" property:

<FlatList
  numColumns={2}
  data={props.data}
  renderItem={({ item }) => renderItem(item, () => props.onItemPress(item))}
  keyExtractor={item => item.key}
  contentInset={defineContentInset()}
  automaticallyAdjustContentInsets={false}
  ListEmptyComponent={<EmptyComponent fromSearch={props.fromSearch} onPress={props.emptyOnPress} />}
/>

Finally, define a default value for this property:

PasswordList.defaultProps = {
  data: [
    { name: 'Twitter', key: 'uuid', icon: 'twitter', color: 'red' },
    { name: 'Facebook', key: 'uuid-2', icon: 'facebook', color: 'blue' },
  ],
  onItemPress: item => console.log(`item : ${item.name}`),
  fromSearch: false,
  emptyOnPress: console.log('empty on press'),
};

Settings

The Settings screen is the screen that allows the user to configure the application, for example to define the default length of generated passwords or choose whether passwords should be defined automatically during creation.

Still in the screens folder, add Settings.js:

/*
 * @flow
 */
 
import React, { Component } from 'react'
import { View, Alert } from 'react-native'
import { PlateformStyleSheet } from '../common/PlatformHelper'
import SettingRow from '../components/SettingRow'
import SliderRow from '../components/SliderRow'
import CheckBoxRow from '../components/CheckBoxRow'
import strings from '../locales/strings'
import { IOS_BACKGROUND, WHITE, DELETE_COLOR } from '../constants/colors'
 
type State = {
  passwordLength: number,
  autoGeneration: boolean,
}
 
class SettingsScreen extends Component<void, void, State> {
  state = {
    passwordLength: 18,
    autoGeneration: true,
  }
 
  setPasswordLength(length: number) {
    this.setState({
      passwordLength: length,
    })
  }
 
  setAutoGeneration(value: boolean) {
    this.setState({
      autoGeneration: value,
    })
  }
 
  deleteAllPasswords() {
    Alert.alert(strings.clear, strings.clearConfirmation, [
      { text: strings.cancel, style: 'cancel' },
      {
        text: strings.delete,
        onPress: () => console.log('all password delete'),
      },
    ])
  }
 
  render() {
    return (
      <View style={styles.container}>
        <SliderRow
          label={strings.passwordLength}
          onValueChange={value => this.setPasswordLength(value)}
          selectedValue={this.state.passwordLength}
        />
        <CheckBoxRow
          label={strings.passwordAuto}
          iconName="star"
          isChecked={this.state.autoGeneration}
          switchValueChange={value => this.setAutoGeneration(value)}
        />
        <View style={styles.itemContainer}>
          <SettingRow
            label={strings.deleteAllPasswords}
            iconName="trash"
            iconBackground={DELETE_COLOR}
            iosOultine
            onPress={() => this.deleteAllPasswords()}
          />
        </View>
      </View>
    )
  }
}
 
export default SettingsScreen
 
const styles = PlateformStyleSheet({
  container: {
    flex: 1,
    ios: {
      backgroundColor: IOS_BACKGROUND,
    },
    android: {
      backgroundColor: WHITE,
    },
  },
  itemContainer: {
    ios: {
      marginTop: 16,
    },
  },
})

Then the resources:

fr.js

...
passwordAuto: 'Génération automatique',
clear: 'Supprimer les données',
clearConfirmation: "Êtes-vous sûr de vouloir supprimer l'intégralité des mots de passe ?",
passwordLength: 'Longeur du mot de passe',
deleteAllPasswords: 'Supprimer tous les mots de passe',
delete: 'Supprimer',
cancel: 'Annuler',

en.js

...
// Settings screen
passwordAuto: 'Automatic generation',
clear: 'Clear data',
clearConfirmation: 'Are you sure you want to delete all passwords ?',
passwordLength: 'Password length',
deleteAllPasswords: 'Delete all passwords',
delete: 'Delete',
cancel: 'Cancel',

strings.js

...
passwordAuto: I18n.t('passwordAuto'),
clear: I18n.t('clear'),
clearConfirmation: I18n.t('clearConfirmation'),
passwordLength: I18n.t('passwordLength'),
deleteAllPasswords: I18n.t('deleteAllPasswords'),
delete: I18n.t('delete'),
cancel: I18n.t('cancel'),

Synchronization & InitSynchronization

Now let's move on to synchronization, which allows the user to save their encrypted data to Dropbox, restore it, or delete it.

This part is divided into two screens: the initialization screen, which will be displayed the first time the user goes to the synchronization section so they can link their Dropbox account to the application; and the second screen, which lets the user save, retrieve, and delete the backup.

Let's start with the initialization screen. Add the InitSynchronization.js file to the screens folder:

/*
 * @flow
 */
 
import React, { Component } from 'react'
import { Text, ScrollView, View, Image, Button } from 'react-native'
 
import { WHITE, PRIMARY_TEXT } from '../constants/colors'
import { PlateformStyleSheet } from '../common/PlatformHelper'
import strings from '../locales/strings'
 
const image = require('../img/paper_plane.png')
 
export default class InitSynchronizationScreen extends Component {
  render() {
    return (
      <ScrollView contentContainerStyle={styles.container}>
        <Image style={styles.image} source={image} />
        <Text style={styles.title}>{strings.synchInstruction}</Text>
        <View style={styles.login}>
          <Button title={strings.login} onPress={() => console.log('log in dropbox')} />
        </View>
      </ScrollView>
    )
  }
}
 
const styles = PlateformStyleSheet({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    ios: {
      backgroundColor: WHITE,
    },
    android: {
      backgroundColor: WHITE,
    },
  },
  image: {
    marginBottom: 32,
  },
  title: {
    fontWeight: 'bold',
    marginTop: 12,
    marginBottom: 12,
    textAlign: 'center',
    color: PRIMARY_TEXT,
  },
  login: {
    width: 150,
    alignSelf: 'center',
  },
})

Next we move on to the Synchronization screen. Add the Synchonization.js file:

/*
 * @flow
 */
 
import React, { Component } from 'react'
import { View, Alert, ActivityIndicator } from 'react-native'
import SettingRow from '../components/SettingRow'
import strings from '../locales/strings'
import { PlateformStyleSheet } from '../common/PlatformHelper'
import { IOS_BACKGROUND, WHITE, DELETE_COLOR, PRIMARY } from '../constants/colors'
 
export default class SynchronizationScreen extends Component {
  uploadBackup() {
    console.log('====================================')
    console.log('upload data')
    console.log('====================================')
  }
  downloadBackup() {
    console.log('====================================')
    console.log('download data')
    console.log('====================================')
  }
  deleteBackup() {
    Alert.alert(strings.clear, strings.clearConfirmation, [
      { text: strings.cancel, style: 'cancel' },
      { text: strings.delete, onPress: () => console.log('delete data') },
    ])
  }
  render() {
    return (
      <View style={styles.container}>
        <SettingRow
          label={strings.publish}
          iconName="cloud-upload"
          withSeparator
          iosOultine
          iosSeparator
          iconBackground={PRIMARY}
          onPress={() => this.uploadBackup()}
        />
        <SettingRow
          label={strings.pull}
          iconName="cloud-download"
          iosOultine
          iconBackground={PRIMARY}
          onPress={() => this.downloadBackup()}
        />
        <View style={styles.itmCtnr}>
          <SettingRow
            label={strings.deleteBackup}
            iconName="trash"
            iconBackground={DELETE_COLOR}
            iosOultine
            onPress={() => this.deleteBackup()}
          />
        </View>
      </View>
    )
  }
}
 
const styles = PlateformStyleSheet({
  container: {
    flex: 1,
    ios: {
      backgroundColor: IOS_BACKGROUND,
    },
    android: {
      backgroundColor: WHITE,
    },
  },
  itmCtnr: {
    ios: {
      marginTop: 16,
    },
  },
  loaderCtnr: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba( 0, 0, 0, 0.7 )',
    justifyContent: 'center',
    alignItems: 'center',
  },
})

And finally the resources:

fr.js

...
// Synchronization screen
synchInstruction:
  'Avant de pouvoir synchroniser vos données. Vous devez vous connecter à DropBox.',
login: 'Se connecter',
publish: 'Publier les données',
pull: 'Récupérer les données',
deleteBackup: 'Supprimer la sauvegarde',

en.js

...
// Synchronization screen
synchInstruction: 'Before you can synchronize your data. You must log in in DropBox.',
login: 'Log in',
publish: 'Publish data',
pull: 'Download data',
deleteBackup: 'Delete backup',

strings.js

...
synchInstruction: I18n.t('synchInstruction'),
login: I18n.t('login'),
publish: I18n.t('publish'),
pull: I18n.t('pull'),
deleteBackup: I18n.t('deleteBackup'),

ReadOnly & Edition

Finally, to finish with the screens, we will create a screen for viewing the password record and a screen for creation and editing.

We will start with the ReadOnly.js screen, but before that we will add a component to display the information.

In the components folder, add ReadOnlyRow.js:

/*
 * @flow
 */
 
import React from 'react'
import { View, Text } from 'react-native'
import { ANDROID_MARGIN, IOS_MARGIN } from '../constants/dimensions'
import { PlateformStyleSheet } from '../common/PlatformHelper'
import { PRIMARY_TEXT, IOS_SLIDER_LABEL } from '../constants/colors'
 
type Props = {
  label: string,
  value: string,
}
 
const ReadOnlyRow = (props: Props) => (
  <View>
    <Text style={styles.label}>{props.label} :</Text>
    <Text style={styles.value}>{props.value}</Text>
  </View>
)
 
ReadOnlyRow.defaultProps = {
  label: 'Label',
  value: 'value',
}
 
const styles = PlateformStyleSheet({
  container: {
    alignItems: 'flex-start',
  },
  value: {
    color: PRIMARY_TEXT,
    ios: { padding: IOS_MARGIN },
    android: { padding: ANDROID_MARGIN },
    fontWeight: 'bold',
    fontSize: 16,
  },
  label: {
    color: IOS_SLIDER_LABEL,
    ios: { paddingHorizontal: IOS_MARGIN },
    android: { paddingHorizontal: ANDROID_MARGIN },
    fontSize: 16,
  },
})
 
export default ReadOnlyRow

Now, in the screens folder, add ReadOnly.js:

/*
 * @flow
 */
 
import React, { Component } from 'react'
import { View, TouchableOpacity, Text, ScrollView } from 'react-native'
import Icon from 'react-native-vector-icons/FontAwesome'
import { PlateformStyleSheet } from '../common/PlatformHelper'
import ReadOnlyRow from '../components/ReadOnlyRow'
import strings from '../locales/strings'
import { IOS_BACKGROUND, WHITE, DELETE_COLOR, PRIMARY } from '../constants/colors'
import { ANDROID_MARGIN, IOS_MARGIN } from '../constants/dimensions'
 
type State = {
  key: string,
  name: string,
  color: string,
  password: string,
  icon: string,
  login: string,
  url: string,
}
 
class ReadOnlyScreen extends Component<void, void, State> {
  state = {
    key: '9939diz-cd',
    name: 'Facebook',
    color: '#64A1F6',
    password: 'example-password-value',
    icon: 'facebook',
    login: '[email protected]',
    url: 'https://www.facebook.com/',
  }
 
  editPassword() {
    console.log('====================================')
    console.log('copy password')
    console.log('====================================')
  }
 
  copyPassword() {
    console.log('====================================')
    console.log('copy password')
    console.log('====================================')
  }
 
  deletePassword() {
    console.log('====================================')
    console.log('delete password')
    console.log('====================================')
  }
 
  render() {
    const { icon, color, name, password, login, url } = this.state
    return (
      <ScrollView style={styles.scrollContent}>
        <View style={styles.container}>
          <View style={styles.iconCtnr}>
            <View style={styles.icon}>
              <Icon name={icon} size={35} color={color} />
            </View>
          </View>
          <ReadOnlyRow label={strings.siteName} value={name} />
          <ReadOnlyRow label={strings.siteUrl} value={url} />
          <ReadOnlyRow label={strings.userName} value={login} />
          <ReadOnlyRow label={strings.password} value={password} />
 
          <View style={styles.actionContainer}>
            <TouchableOpacity style={styles.action} onPress={() => this.copyPassword()}>
              <Text style={[styles.actionLabel, { color: '#647CF6' }]}>{strings.copy}</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.action} onPress={() => this.editPassword()}>
              <Text style={[styles.actionLabel, { color: PRIMARY }]}>{strings.edit}</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.action} onPress={() => this.deletePassword()}>
              <Text style={[styles.actionLabel, { color: DELETE_COLOR }]}>{strings.delete}</Text>
            </TouchableOpacity>
          </View>
        </View>
      </ScrollView>
    )
  }
}
 
export default ReadOnlyScreen
 
const styles = PlateformStyleSheet({
  scrollContent: {
    flex: 1,
    backgroundColor: IOS_BACKGROUND,
  },
  container: {
    backgroundColor: WHITE,
    marginHorizontal: ANDROID_MARGIN,
    marginBottom: ANDROID_MARGIN,
    android: {
      marginTop: ANDROID_MARGIN,
    },
    ios: {
      marginTop: 42,
    },
  },
  iconCtnr: {
    marginBottom: 24,
    ios: { marginTop: -32 },
    android: { marginTop: ANDROID_MARGIN },
    alignSelf: 'center',
  },
  icon: {
    android: {
      borderWidth: 1,
      borderColor: PRIMARY,
    },
    height: 75,
    width: 75,
    borderRadius: 75,
    backgroundColor: WHITE,
    justifyContent: 'center',
    alignItems: 'center',
  },
  actionContainer: {
    marginTop: 32,
  },
  action: {
    ios: {
      padding: IOS_MARGIN,
      marginBottom: 16,
    },
    android: {
      padding: ANDROID_MARGIN,
      marginBottom: 6,
    },
    alignItems: 'center',
    justifyContent: 'center',
  },
  actionLabel: {
    fontSize: 18,
    android: { fontWeight: 'bold' },
  },
})

Then add the Edition.js file:

/*
 * @flow
 */
 
import React, { Component } from 'react'
import { View, Button, TouchableOpacity } from 'react-native'
import Icon from 'react-native-vector-icons/FontAwesome'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'
 
import { PlateformStyleSheet } from '../common/PlatformHelper'
import TextField from '../components/TextField'
import IconPicker from '../components/IconPicker'
import IconModal from '../components/IconModal'
import ColorSelector from '../components/ColorSelector'
import strings from '../locales/strings'
import { IOS_BACKGROUND, WHITE, PRIMARY } from '../constants/colors'
import { ANDROID_MARGIN, IOS_MARGIN } from '../constants/dimensions'
 
type State = {
  key: string,
  name: string,
  color: string,
  password: string,
  icon: string,
  login: string,
  url: string,
  modalIsOpen: boolean,
}
 
class ReadOnlyScreen extends Component<void, void, State> {
  loginField: Object
  urlField: Object
  passwordField: Object
 
  state = {
    key: '9939diz-cd',
    name: 'Facebook',
    color: '#64A1F6',
    password: 'example-password-value',
    icon: 'facebook',
    login: '[email protected]',
    url: 'https://www.facebook.com/',
    modalIsOpen: false,
  }
 
  save() {
    console.log('====================================')
    console.log('save password')
    console.log('====================================')
  }
 
  generatePassword() {
    console.log('====================================')
    console.log('generate password')
    console.log('====================================')
  }
 
  selectIcon(icon: string) {
    this.setState({
      icon,
    })
  }
 
  selectColor(color: string) {
    this.setState({ color })
  }
 
  toggleModal() {
    this.setState({
      modalIsOpen: !this.state.modalIsOpen,
    })
  }
 
  render() {
    const { icon, color, name, password, login, url, modalIsOpen } = this.state
 
    return (
      <View style={styles.main}>
        <KeyboardAwareScrollView style={styles.scrollContent}>
          <View style={styles.container}>
            <View style={styles.iconCtnr}>
              <View style={styles.icon}>
                <IconPicker icon={icon} color={color} onPress={() => this.toggleModal()} />
              </View>
            </View>
 
            <TextField
              placeholder={strings.siteName}
              value={name}
              onSubmitEditing={() => this.urlField.focus()}
              onChangeText={text => this.setState({ name: text })}
              returnKeyType="next"
            />
            <TextField
              icon="globe"
              placeholder={strings.siteUrl}
              value={url}
              ref={c => {
                this.urlField = c
              }}
              onSubmitEditing={() => this.loginField.focus()}
              onChangeText={text => this.setState({ url: text })}
              returnKeyType="next"
            />
            <TextField
              icon="person"
              placeholder={strings.userName}
              value={login}
              ref={c => {
                this.loginField = c
              }}
              onSubmitEditing={() => this.passwordField.focus()}
              onChangeText={text => this.setState({ login: text })}
              returnKeyType="next"
            />
            <TextField
              icon="lock"
              placeholder={strings.password}
              value={password}
              ref={c => {
                this.passwordField = c
              }}
              onChangeText={text => this.setState({ password: text })}
              secureTextEntry
            />
            <View style={styles.colorSelector}>
              <ColorSelector onPress={colorValue => this.selectColor(colorValue)} />
            </View>
 
            <View style={styles.actionContainer}>
              <Button title={strings.save} color={PRIMARY} onPress={() => this.save()} />
            </View>
          </View>
        </KeyboardAwareScrollView>
 
        <TouchableOpacity style={styles.generate} onPress={() => this.generatePassword()}>
          <Icon name="magic" size={32} color={PRIMARY} />
        </TouchableOpacity>
 
        <IconModal
          onSelectIcon={iconName => this.selectIcon(iconName)}
          isOpen={modalIsOpen}
          toggleModal={() => this.toggleModal()}
        />
      </View>
    )
  }
}
 
export default ReadOnlyScreen
 
const styles = PlateformStyleSheet({
  main: {
    flex: 1,
  },
  scrollContent: {
    flex: 1,
    backgroundColor: IOS_BACKGROUND,
  },
  generate: {
    android: {
      top: ANDROID_MARGIN,
      padding: ANDROID_MARGIN,
    },
    ios: {
      top: 42,
      padding: IOS_MARGIN,
    },
    height: 60,
    width: 60,
    alignItems: 'center',
    justifyContent: 'center',
    position: 'absolute',
    right: ANDROID_MARGIN,
    backgroundColor: WHITE,
  },
  container: {
    backgroundColor: WHITE,
    marginHorizontal: ANDROID_MARGIN,
    marginBottom: ANDROID_MARGIN,
    android: {
      marginTop: ANDROID_MARGIN,
    },
    ios: {
      marginTop: 42,
    },
  },
  iconCtnr: {
    marginBottom: 24,
    ios: { marginTop: -32 },
    android: { marginTop: ANDROID_MARGIN },
    alignSelf: 'center',
  },
  actionContainer: {
    width: 150,
    alignSelf: 'center',
    marginTop: 32,
    android: {
      marginBottom: ANDROID_MARGIN,
    },
    ios: {
      marginBottom: IOS_MARGIN,
    },
  },
  colorSelector: {
    android: {
      padding: ANDROID_MARGIN,
    },
    ios: {
      padding: IOS_MARGIN,
    },
  },
})

And finally the resources:

fr.js

...
// Edition
siteName: 'Nom du site',
siteUrl: 'Url du site',
userName: 'Login',
edit: 'Editer',
copy: 'Copier le mot de passe',

en.js

...
siteName: 'Site Name',
siteUrl: 'Site url',
userName: 'Login',
edit: 'Edit',
copy: 'Copy password',

strings.js

...
siteName: I18n.t('siteName'),
siteUrl: I18n.t('siteUrl'),
userName: I18n.t('userName'),
edit: I18n.t('edit'),
copy: I18n.t('copy'),

We have now finished creating the application's screens.

Series navigation