improve conversation view

Change-Id: I63189d0b61d45e659ac7618a977282f7b4500753
diff --git a/JamiDaemon.js b/JamiDaemon.js
index 59d6b2b..764f1a1 100755
--- a/JamiDaemon.js
+++ b/JamiDaemon.js
@@ -29,7 +29,7 @@
             "AccountsChanged": () => {
                 console.log("AccountsChanged")
                 const newAccounts = []
-                this.stringVectToArr(this.dring.getAccountList()).forEach(accountId => {
+                JamiDaemon.vectToJs(this.dring.getAccountList()).forEach(accountId => {
                     for (const account in this.accounts) {
                         if (account.id === accountId) {
                             newAccounts.push(account)
@@ -37,8 +37,8 @@
                         }
                     }
                     newAccounts.push(new Account(accountId,
-                        this.mapToJs(this.dring.getAccountDetails(accountId)),
-                        this.mapToJs(this.dring.getVolatileAccountDetails(accountId))
+                        JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)),
+                        JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId))
                     ))
                 })
                 this.accounts = newAccounts
@@ -99,8 +99,26 @@
             },
             "RegisteredNameFound": (accountId, state, address, name) => {
                 console.log(`RegisteredNameFound: ${accountId} ${state} ${address} ${name}`)
+                const account = this.getAccount(accountId)
+                if (!account) {
+                    console.log(`Unknown account ${accountId}`)
+                    return
+                }
+                if (state == 0) {
+                    const contact = account.getContactFromCache(address)
+                    contact.setRegisteredName(name)
+                }
+                let index = account.lookups.length - 1;
+                while (index >= 0) {
+                    const lookup = account.lookups[index]
+                    if ((lookup.address && lookup.address === address) || (lookup.name && lookup.name === name)) {
+                        lookup.resolve({address, name, state})
+                        account.lookups.splice(index, 1);
+                    }
+                    index -= 1;
+                }
             },
-            "conversationReady": (accountId, conversationId) => {
+            "ConversationReady": (accountId, conversationId) => {
                 console.log(`conversationReady: ${accountId} ${conversationId}`)
                 const account = this.getAccount(accountId)
                 if (!account) {
@@ -109,13 +127,13 @@
                 }
                 let conversation = account.getConversation(conversationId)
                 if (!conversation) {
-                    const members = this.dring.getConversationMembers(accountId, conversationId)
-                    console.log(members)
-                    conversation = new Conversation(conversationId, members)
+                    const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, conversationId))
+                    members.forEach(member => member.contact = account.getContactFromCache(member.uri))
+                    conversation = new Conversation(conversationId, accountId, members)
                     account.addConversation(conversation)
                 }
             },
-            "conversationRemoved": (accountId, conversationId) => {
+            "ConversationRemoved": (accountId, conversationId) => {
                 console.log(`conversationRemoved: ${accountId} ${conversationId}`)
                 const account = this.getAccount(accountId)
                 if (!account) {
@@ -124,7 +142,7 @@
                 }
                 account.removeConversation(conversationId)
             },
-            "conversationLoaded": (accountId, conversationId) => {
+            "ConversationLoaded": (accountId, conversationId) => {
                 console.log(`conversationLoaded: ${accountId} ${conversationId}`)
                 const account = this.getAccount(accountId)
                 if (!account) {
@@ -132,7 +150,7 @@
                     return
                 }
             },
-            "messageReceived": (accountId, conversationId, message) => {
+            "MessageReceived": (accountId, conversationId, message) => {
                 console.log(`messageReceived: ${accountId} ${conversationId}`)
                 const account = this.getAccount(accountId)
                 if (!account) {
@@ -144,7 +162,7 @@
                     conversation.addMessage(message)
                 }
             },
-            "conversationRequestReceived": (accountId, conversationId, request) => {
+            "ConversationRequestReceived": (accountId, conversationId, request) => {
                 console.log(`conversationRequestReceived: ${accountId} ${conversationId}`)
                 const account = this.getAccount(accountId)
                 if (!account) {
@@ -152,7 +170,7 @@
                     return
                 }
             },
-            "conversationMemberEvent": (accountId, conversationId, member, event) => {
+            "ConversationMemberEvent": (accountId, conversationId, member, event) => {
                 console.log(`conversationMemberEvent: ${accountId} ${conversationId}`)
                 const account = this.getAccount(accountId)
                 if (!account) {
@@ -160,7 +178,7 @@
                     return
                 }
             },
-            "onConversationError": (accountId, conversationId, code, what) => {
+            "OnConversationError": (accountId, conversationId, code, what) => {
                 console.log(`onConversationError: ${accountId} ${conversationId}`)
                 const account = this.getAccount(accountId)
                 if (!account) {
@@ -169,11 +187,19 @@
                 }
             }
         })
-        this.stringVectToArr(this.dring.getAccountList()).forEach(accountId => {
-            this.accounts.push(new Account(accountId,
-                this.mapToJs(this.dring.getAccountDetails(accountId)),
-                this.mapToJs(this.dring.getVolatileAccountDetails(accountId))
-            ))
+
+        JamiDaemon.vectToJs(this.dring.getAccountList()).forEach(accountId => {
+            const account = new Account(accountId,
+                JamiDaemon.mapToJs(this.dring.getAccountDetails(accountId)),
+                JamiDaemon.mapToJs(this.dring.getVolatileAccountDetails(accountId))
+            )
+            JamiDaemon.vectToJs(this.dring.getConversations(accountId)).forEach(conversationId => {
+                const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, conversationId))
+                members.forEach(member => member.contact = account.getContactFromCache(member.uri))
+                const conversation = new Conversation(conversationId, accountId, members)
+                account.addConversation(conversation)
+            })
+            this.accounts.push(account)
         })
     }
 
@@ -194,103 +220,158 @@
         return this.accounts
     }
     /*getAccountDetails(accountId) {
-        return this.mapToJs(this.dring.getAccountDetails(accountId));
+        return this.mapToJs(this.dring.getAccountDetails(accountId))
     }*/
     setAccountDetails(accountId, details) {
-        this.dring.setAccountDetails(accountId, mapToNative(details));
+        this.dring.setAccountDetails(accountId, mapToNative(details))
     }
     getAudioOutputDeviceList() {
-        return this.stringVectToArr(this.dring.getAudioOutputDeviceList());
+        return JamiDaemon.vectToJs(this.dring.getAudioOutputDeviceList())
     }
     getVolume(deviceName) {
-        return this.dring.getVolume(deviceName);
+        return this.dring.getVolume(deviceName)
     }
     setVolume(deviceName, volume) {
-        return this.dring.setVolume(deviceName, volume);
+        return this.dring.setVolume(deviceName, volume)
+    }
+
+    lookupName(accountId, name) {
+        const p = new Promise((resolve, reject) => {
+            const account = this.getAccount(accountId)
+            if (!account) {
+                reject(new Error("Can't find account"))
+            } else {
+                account.lookups.push({name, resolve, reject})
+            }
+        })
+        this.dring.lookupName(accountId, "", name)
+        return p
+    }
+
+    lookupAddress(accountId, address) {
+        console.log(`lookupAddress ${accountId} ${address}`)
+        const p = new Promise((resolve, reject) => {
+            const account = this.getAccount(accountId)
+            if (!account) {
+                reject(new Error("Can't find account"))
+            } else {
+                account.lookups.push({address, resolve, reject})
+            }
+        })
+        this.dring.lookupAddress(accountId, "", address)
+        return p
     }
 
     stop() {
-        this.dring.fini();
+        this.dring.fini()
     }
 
+    addContact(accountId, contactId) {
+        this.dring.addContact(accountId, contactId)
+        const details = JamiDaemon.mapToJs(this.dring.getContactDetails(accountId, contactId))
+        if (details.conversationId) {
+            const account = this.getAccount(accountId)
+            if (account) {
+                let conversation = account.getConversation(details.conversationId)
+                if (!conversation) {
+                    const members = JamiDaemon.vectMapToJs(this.dring.getConversationMembers(accountId, details.conversationId))
+                    members.forEach(member => member.contact = account.getContactFromCache(member.uri))
+                    conversation = new Conversation(details.conversationId, accountId, members)
+                    account.addConversation(conversation)
+                }
+            }
+        }
+        return details
+    }
+
+    //getContactDetails
+
 // private
 
     boolToStr(bool) {
-        return bool ? "true" : "false";
+        return bool ? "true" : "false"
     }
 
     accountDetailsToNative(account) {
-        const params = new this.dring.StringMap();
+        const params = new this.dring.StringMap()
         if (account.managerUri)
-            params.set("Account.managerUri", account.managerUri);
+            params.set("Account.managerUri", account.managerUri)
         if (account.managerUsername)
-            params.set("Account.managerUsername", account.managerUsername);
+            params.set("Account.managerUsername", account.managerUsername)
         if (account.archivePassword) {
-            params.set("Account.archivePassword", account.archivePassword);
+            params.set("Account.archivePassword", account.archivePassword)
         } else {
-            console.log("archivePassword required");
+            console.log("archivePassword required")
             return;
         }
         if (account.alias)
-            params.set("Account.alias", account.alias);
+            params.set("Account.alias", account.alias)
         if (account.displayName)
-            params.set("Account.displayName", account.displayName);
+            params.set("Account.displayName", account.displayName)
         if (account.enable)
-            params.set("Account.enable", this.boolToStr(account.enable));
+            params.set("Account.enable", this.boolToStr(account.enable))
         if (account.autoAnswer)
-            params.set("Account.autoAnswer", this.boolToStr(account.autoAnswer));
+            params.set("Account.autoAnswer", this.boolToStr(account.autoAnswer))
         if (account.ringtonePath)
-            params.set("Account.ringtonePath", account.ringtonePath);
+            params.set("Account.ringtonePath", account.ringtonePath)
         if (account.ringtoneEnabled)
-            params.set("Account.ringtoneEnabled", this.boolToStr(account.ringtoneEnabled));
+            params.set("Account.ringtoneEnabled", this.boolToStr(account.ringtoneEnabled))
         if (account.videoEnabled)
-            params.set("Account.videoEnabled", this.boolToStr(account.videoEnabled));
+            params.set("Account.videoEnabled", this.boolToStr(account.videoEnabled))
         if (account.useragent) {
-            params.set("Account.useragent", account.useragent);
-            params.set("Account.hasCustomUserAgent", "TRUE");
+            params.set("Account.useragent", account.useragent)
+            params.set("Account.hasCustomUserAgent", "TRUE")
         } else {
-            params.set("Account.hasCustomUserAgent", "FALSE");
+            params.set("Account.hasCustomUserAgent", "FALSE")
         }
         if (account.audioPortMin)
-            params.set("Account.audioPortMin", account.audioPortMin);
+            params.set("Account.audioPortMin", account.audioPortMin)
         if (account.audioPortMax)
-            params.set("Account.audioPortMax", account.audioPortMax);
+            params.set("Account.audioPortMax", account.audioPortMax)
         if (account.videoPortMin)
-            params.set("Account.videoPortMin", account.videoPortMin);
+            params.set("Account.videoPortMin", account.videoPortMin)
         if (account.videoPortMax)
-            params.set("Account.videoPortMax", account.videoPortMax);
+            params.set("Account.videoPortMax", account.videoPortMax)
         if (account.localInterface)
-            params.set("Account.localInterface", account.localInterface);
+            params.set("Account.localInterface", account.localInterface)
         if (account.publishedSameAsLocal)
-            params.set("Account.publishedSameAsLocal", this.boolToStr(account.publishedSameAsLocal));
+            params.set("Account.publishedSameAsLocal", this.boolToStr(account.publishedSameAsLocal))
         if (account.localPort)
-            params.set("Account.localPort", account.localPort);
+            params.set("Account.localPort", account.localPort)
         if (account.publishedPort)
-            params.set("Account.publishedPort", account.publishedPort);
+            params.set("Account.publishedPort", account.publishedPort)
         if (account.publishedAddress)
-            params.set("Account.publishedAddress", account.publishedAddress);
+            params.set("Account.publishedAddress", account.publishedAddress)
         if (account.upnpEnabled)
-            params.set("Account.upnpEnabled", this.boolToStr(account.upnpEnabled));
-        return params;
+            params.set("Account.upnpEnabled", this.boolToStr(account.upnpEnabled))
+        return params
     }
-    stringVectToArr(stringvect) {
-        const outputArr = [];
-        for (let i = 0; i < stringvect.size(); i++)
-            outputArr.push(stringvect.get(i));
-        return outputArr;
+    static vectToJs(vect) {
+        const len = vect.size()
+        const outputArr = new Array(len)
+        for (let i = 0; i < len; i++)
+            outputArr[i] = vect.get(i)
+        return outputArr
     }
-    mapToJs(m) {
-        const outputObj = {};
-        this.stringVectToArr(m.keys())
-            .forEach(k => outputObj[k] = m.get(k));
-        return outputObj;
+    static mapToJs(m) {
+        const outputObj = {}
+        JamiDaemon.vectToJs(m.keys())
+            .forEach(k => outputObj[k] = m.get(k))
+        return outputObj
     }
-    mapToNative(map){
-        const ret = new this.dring.StringMap();
-        map.forEach((value, key) => ret.set(key, value));
-        return ret;
+    static vectMapToJs(vectMap) {
+        const len = vectMap.size()
+        const outputArr = new Array(len)
+        for (let i = 0; i < len; i++)
+            outputArr[i] = JamiDaemon.mapToJs(vectMap.get(i))
+        return outputArr
     }
 
+    mapToNative(map){
+        const ret = new this.dring.StringMap()
+        map.forEach((value, key) => ret.set(key, value))
+        return ret;
+    }
 }
 
 module.exports = JamiDaemon;
diff --git a/client/src/App.js b/client/src/App.js
index bf9cc15..10db90a 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -12,10 +12,12 @@
 
 import { BrowserRouter as Router, Route, Switch, Link, Redirect } from 'react-router-dom';
 
-import SignInPage from "./pages/loginDialog.jsx";
+import SignInPage from "./pages/loginDialog.jsx"
 import JamiMessenger from "./pages/messenger.jsx"
 import AccountSettings from "./pages/accountSettings.jsx"
 import AccountSelection from "./pages/accountSelection.jsx"
+import AddContactPage from "./pages/addContactPage.jsx"
+
 import NotFoundPage from "./pages/404.jsx"
 
 class App extends React.Component {
@@ -28,13 +30,18 @@
   }
 
   render() {
+    console.log("App render")
+    console.log(this.props)
+
       return <React.Fragment>
         <CssBaseline />
         <Router>
           <Switch>
             <Route exact path="/"><Redirect to="/account" /></Route>
-            <Route path="/account/:accountId" component={JamiMessenger} />
             <Route path="/account/:accountId/settings" component={AccountSettings} />
+            <Route path="/account/:accountId/addContact/:contactId" component={JamiMessenger} />
+            <Route path="/account/:accountId/conversation/:conversationId" component={JamiMessenger} />
+            <Route path="/account/:accountId" component={JamiMessenger} />
             <Route path="/account" component={AccountSelection} />
             <Route component={NotFoundPage} />
           </Switch>
diff --git a/client/src/components/ContactList.js b/client/src/components/ContactList.js
index 50b773d..ad75783 100644
--- a/client/src/components/ContactList.js
+++ b/client/src/components/ContactList.js
@@ -4,9 +4,11 @@
 class ContactList extends React.Component {
     render() {
         return (
+            <div className="rooms-list">
             <List>
 
             </List>
+            </div>
         )
     }
 }
diff --git a/client/src/components/ConversationList.js b/client/src/components/ConversationList.js
index 4620d77..e83b6c1 100644
--- a/client/src/components/ConversationList.js
+++ b/client/src/components/ConversationList.js
@@ -1,16 +1,35 @@
 import List from '@material-ui/core/List'
 import React from 'react'
 import ConversationListItem from './ConversationListItem'
+import ListSubheader from '@material-ui/core/ListSubheader';
+import Conversation from '../../../model/Conversation';
+import GroupRoundedIcon from '@material-ui/icons/GroupRounded';
+import { Typography } from '@material-ui/core';
 
 class ConversationList extends React.Component {
     render() {
-
+        console.log("ConversationList render " + this.props.accountId)
+        console.log(this.props.conversations)
         return (
-            <List>
-                {this.props.conversations.forEach(conversation => {
-                    <ConversationListItem conversation={conversation} />
-                })}
-            </List>
+            <div className="rooms-list">
+                <List>
+                    {this.props.search instanceof Conversation &&
+                    (<div>
+                        <ListSubheader>Public directory</ListSubheader>
+                        <ConversationListItem conversation={this.props.search} />
+                    <ListSubheader>Conversations</ListSubheader>
+                    </div>)}
+                    {this.props.conversations.map(conversation =>
+                        <ConversationListItem key={conversation.getId()} conversation={conversation} />
+                    )}
+                    {this.props.conversations.length === 0 && (
+                        <div className="list-placeholder">
+                            <GroupRoundedIcon color="disabled" fontSize="large"  />
+                            <Typography className="subtitle" variant="subtitle2">No conversation yet</Typography>
+                        </div>
+                    )}
+                </List>
+            </div>
         )
     }
 }
diff --git a/client/src/components/ConversationListItem.js b/client/src/components/ConversationListItem.js
index 3a271fc..cc72c70 100644
--- a/client/src/components/ConversationListItem.js
+++ b/client/src/components/ConversationListItem.js
@@ -1,16 +1,34 @@
 import { Avatar, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'
 import React from 'react'
+import Conversation from '../../../model/Conversation'
+import { withRouter } from 'react-router-dom';
 
 class ConversationListItem extends React.Component {
     render() {
+        const conversation = this.props.conversation;
+        const pathId = this.props.match.params.conversationId || this.props.match.params.contactId
+        const isSelected = conversation.getDisplayUri() === pathId
+        console.log("ConversationListItem render " + conversation)
+        console.log(this.props)
 
-        return (
-            <ListItem alignItems="flex-start">
-                <ListItemAvatar><Avatar>{this.props.conversation.getDisplayName()[0]}</Avatar></ListItemAvatar>
-                <ListItemText primary={this.props.conversation.getDisplayName()} />
-            </ListItem>
-        )
+        const uri = conversation.getId() ? `conversation/${conversation.getId()}` : `addContact/${conversation.getFirstMember().contact.getUri()}`;
+        if (conversation instanceof Conversation) {
+            return (
+                <ListItem
+                    button
+                    alignItems="flex-start"
+                    selected={isSelected}
+                    style={{overflow:'hidden'}}
+                    onClick={() => this.props.history.push(`/account/${conversation.getAccountId()}/${uri}`)}>
+                    <ListItemAvatar><Avatar>{conversation.getDisplayName()[0].toUpperCase()}</Avatar></ListItemAvatar>
+                    <ListItemText
+                        style={{overflow:'hidden', textOverflow:'ellipsis'}}
+                        primary={conversation.getDisplayName()} secondary={conversation.getDisplayUri()} />
+                </ListItem>
+            )
+        } else
+            return null
     }
 }
 
-export default ConversationListItem
\ No newline at end of file
+export default withRouter(ConversationListItem)
\ No newline at end of file
diff --git a/client/src/components/ConversationView.js b/client/src/components/ConversationView.js
new file mode 100644
index 0000000..678aafe
--- /dev/null
+++ b/client/src/components/ConversationView.js
@@ -0,0 +1,84 @@
+import CircularProgress from '@material-ui/core/CircularProgress';
+import React from 'react';
+import MessageList from './MessageList';
+import SendMessageForm from './SendMessageForm';
+import authManager from '../AuthManager'
+import Conversation from '../../../model/Conversation';
+
+class ConversationView extends React.Component {
+    constructor(props) {
+        super(props)
+        this.state = {
+            loaded:false,
+            error: false,
+            messages:[]
+        }
+    }
+
+    componentDidMount() {
+      this.controller = new AbortController()
+      if (!this.state.loaded) {
+        authManager.fetch(`/api/accounts/${this.props.accountId}/conversations/${this.props.conversationId}`, {signal: this.controller.signal})
+          .then(res => res.json())
+          .then(result => {
+            console.log(result)
+            this.setState({
+              loaded: true,
+              conversation: Conversation.from(this.props.accountId, result)// result.map(account => Account.from(account)),
+            })
+          }, error => {
+            console.log(`get error ${error}`)
+            this.setState({
+              loaded: true,
+              error: true
+            })
+          })
+        }
+    }
+    componentDidUpdate(prevProps, prevState) {
+      console.log("componentDidUpdate " + this.props.conversationId)
+      if (this.props.conversationId !== prevProps.conversationId) {
+        if (this.state.loaded === true) {
+          this.setState({
+              loaded:false,
+              error: false,
+              messages:[]
+          })
+        }
+        this.controller = new AbortController()
+        authManager.fetch(`/api/accounts/${this.props.accountId}/conversations/${this.props.conversationId}`, {signal: this.controller.signal})
+          .then(res => res.json())
+          .then(result => {
+            console.log(result)
+            this.setState({
+              loaded: true,
+              conversation: Conversation.from(this.props.accountId, result)// result.map(account => Account.from(account)),
+            })
+          }, error => {
+            console.log(`get error ${error}`)
+            this.setState({
+              loaded: true,
+              error: true
+            })
+          })
+      }
+    }
+    componentWillUnmount() {
+      this.controller.abort()
+    }
+
+    render() {
+        if (this.state.loaded === false) {
+            return <CircularProgress />
+        } else if (this.state.error === true) {
+            return <div>Error loding {this.props.conversationId}</div>
+        } else {
+        return <React.Fragment>
+            <MessageList conversation={this.state.conversation} messages={this.state.messages} />
+            <SendMessageForm sendMessage={this.sendMessage} />
+          </React.Fragment>
+        }
+    }
+}
+
+export default ConversationView
\ No newline at end of file
diff --git a/client/src/components/MessageList.js b/client/src/components/MessageList.js
index 08bdbea..48bfb28 100644
--- a/client/src/components/MessageList.js
+++ b/client/src/components/MessageList.js
@@ -1,12 +1,22 @@
+import { Avatar, Box, Divider, Typography } from '@material-ui/core'
+import Paper from '@material-ui/core/Paper'
 import React from 'react'
 import Message from './Message'
 
-
 class MessageList extends React.Component {
     render() {
         return (
             <div className="message-list">
-
+                <Box>
+                <Box style={{display:'inline-block', margin: 16, verticalAlign:'middle'}}>
+                    <Avatar>{this.props.conversation.getDisplayName()[0].toUpperCase()}</Avatar>
+                </Box>
+                <Box style={{display:'inline-block', verticalAlign:'middle'}}>
+                    <Typography variant="h5">{this.props.conversation.getDisplayName()}</Typography>
+                    <Typography variant="subtitle1">{this.props.conversation.getId()}</Typography>
+                    </Box>
+                    </Box>
+                <Divider orientation="horizontal" />
                 {
                     this.props.messages.map((message, index) => {
                         /*DUMMY_DATA.map((message, index) => {*/
diff --git a/client/src/components/NewContactForm.js b/client/src/components/NewContactForm.js
index ef590c5..c98c9b4 100644
--- a/client/src/components/NewContactForm.js
+++ b/client/src/components/NewContactForm.js
@@ -1,17 +1,51 @@
 import React from 'react'
+import SearchIcon from '@material-ui/icons/Search';
+import InputBase from '@material-ui/core/InputBase';
+import InputAdornment from '@material-ui/core/InputAdornment';
 
 class NewContactForm extends React.Component {
+    constructor(props) {
+        super(props)
+        this.state = {value: ''}
+        this.controller = new AbortController()
+        this.handleChange = this.handleChange.bind(this)
+        this.handleSubmit = this.handleSubmit.bind(this)
+    }
+
+    componentDidMount() {
+        this.controller = new AbortController()
+    }
+
+    componentWillUnmount() {
+        this.controller.abort()
+        this.req = undefined
+    }
+
+    handleChange(event) {
+        this.setState({value: event.target.value})
+        this.props.onChange(event.target.value)
+    }
+
+    handleSubmit(event) {
+        event.preventDefault()
+        if (this.props.onSubmit)
+            this.props.onSubmit(this.state.value)
+    }
+
     render() {
         return (
-            <div className="new-room-form">
-                <form>
-                    <input
-                        type="text"
-                        placeholder="Ajouter un contact"
-                        required />
-                    <button id="create-room-btn" type="submit">+</button>
-                </form>
-            </div>
+            <form className="main-search" onSubmit={this.handleSubmit} noValidate autoComplete="off">
+                <InputBase
+                    className="main-search-input"
+                    type="search"
+                    placeholder="Ajouter un contact"
+                    onChange={this.handleChange}
+                    startAdornment={
+                        <InputAdornment position="start">
+                            <SearchIcon />
+                        </InputAdornment>
+                        } />
+            </form>
         )
     }
 }
diff --git a/client/src/components/SendMessageForm.js b/client/src/components/SendMessageForm.js
index fd648e5..5270e9a 100644
--- a/client/src/components/SendMessageForm.js
+++ b/client/src/components/SendMessageForm.js
@@ -1,51 +1,51 @@
 import React from 'react'
-import TextField from '@material-ui/core/TextField'
-//import InputEmoji from "react-input-emoji";
+import { makeStyles } from '@material-ui/core/styles';
+import { IconButton, InputBase, Paper } from '@material-ui/core'
+import SendIcon from '@material-ui/icons/Send';
 
-class SendMessageForm extends React.Component {
+const useStyles = makeStyles((theme) => ({
+    root: {
+      margin: 16,
+      padding: '2px 4px',
+      display: 'flex',
+      alignItems: 'center',
+      borderRadius: 8
+    },
+    input: {
+      marginLeft: theme.spacing(1),
+      flex: 1,
+    },
+    iconButton: {
+      padding: 10,
+    },
+    divider: {
+      height: 28,
+      margin: 4,
+    },
+  }));
 
-    constructor() {
-        super()
-        this.state = {
-            message: ''
-        }
-        this.handleChange = this.handleChange.bind(this)
-        this.handleSubmit = this.handleSubmit.bind(this)
+  export default function SendMessageForm(props) {
+    const classes = useStyles();
+
+    const handleSubmit = e => {
+        e.preventDefault()
     }
 
-    handleChange(e) {
-        this.setState({
-            message: e
-        })
-    }
-
-    handleSubmit(e) {
-        //e.preventDefault()
-        this.props.sendMessage(this.state.message)
-        //this.props.sendMessage(this.state.message)
-        this.setState({
-            message: ''
-        })
-    }
-
-    render() {
-        return (
-            <div
-                //onSubmit={this.handleSubmit}
-                className="send-message-form">
-                <TextField
-                    disabled={this.props.disabled}
-                    onChange={this.handleChange}
-                    value={this.state.message}
-                    //cleanOnEnter
-                    //onEnter={this.handleSubmit}
-                    placeholder="Écris ton message et cliques sur Entrer"
-                    height="35"
-                />
-
-            </div>
-        )
-    }
+    return (
+        <div className="send-message-form">
+        <Paper component="form"
+            onSubmit={handleSubmit}
+            className="send-message-card"
+            className={classes.root}>
+            <InputBase
+                className={classes.input}
+                placeholder="Write something nice"
+                height="35"
+            />
+            <IconButton type="submit" className={classes.iconButton} aria-label="search">
+                <SendIcon />
+            </IconButton>
+        </Paper>
+        </div>
+    )
 }
-
-export default SendMessageForm
\ No newline at end of file
diff --git a/client/src/index.ejs b/client/src/index.ejs
index f1cd060..aee5126 100644
--- a/client/src/index.ejs
+++ b/client/src/index.ejs
@@ -4,7 +4,7 @@
 <head>
   <meta charset="utf-8" />
   <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
-  <meta name="viewport" content="width=device-width, initial-scale=1" />
+  <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
   <meta name="theme-color" content="#000000" />
   <meta name="description" content="Web site created using create-react-app" />
   <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
diff --git a/client/src/index.scss b/client/src/index.scss
index 062ee6f..73ba105 100644
--- a/client/src/index.scss
+++ b/client/src/index.scss
@@ -13,7 +13,7 @@
   padding: 0;
   font-family: 'Open Sans', sans-serif;
   font-weight: 200;
-  color: #3e5869; 
+  color: #3e5869;
 }
 
 #root {
@@ -24,15 +24,15 @@
   display: grid;
   height: 100%;
   grid-template-columns: repeat(6, 1fr);
-  grid-template-rows: 40px 1fr 1fr 1fr 1fr 1fr 60px;
-  grid-template-areas: 
+  grid-template-rows: 40px 40px 1fr 1fr 1fr 1fr 92px;
+  grid-template-areas:
       "h h h h h h"
+      "n m m m m m"
       "r m m m m m"
       "r m m m m m"
       "r m m m m m"
       "r m m m m m"
-      "r m m m m m"
-      "n s s s s s";
+      "r s s s s s";
 }
 
 .login {
@@ -43,7 +43,7 @@
   grid-area: h;
 }
 
-.new-room-form {
+.main-search {
   grid-area: n;
 }
 
@@ -70,6 +70,23 @@
   color: var(--send-message-form);
 }
 
+.main-search-input {
+  padding: 8px;
+  background-color: var(--secondary-color);
+  width: 100%;
+}
+
+.rooms-list {
+  overflow-y: scroll;
+}
+.rooms-list .MuiListItemText-primary,
+.rooms-list .MuiListItemText-secondary
+{
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/*
 .rooms-list {
   box-sizing: border-box;
   padding: 10px;
@@ -79,7 +96,7 @@
 }
 
 .rooms-list ul {
-  list-style-type: none;    
+  list-style-type: none;
   padding: 0;
   overflow: scoll;
 }
@@ -97,13 +114,14 @@
   color: var(--secondary-text-color);
   font-weight: 600;
   text-decoration: none;
-  
+
 }
 
 .rooms-list .room.active a {
   color: var(--secondary-color);
 }
-
+*/
+/*
 .new-room-form {
   padding: 0 5px;
   background: var(--secondary-color);
@@ -126,7 +144,7 @@
   background: var(--secondary-color);
   color: var(--main-text-color);
   border: 0;
-} 
+}
 
 .new-room-form input::placeholder {
   color: var(--main-text-color);
@@ -143,6 +161,16 @@
 
 .new-room-form button {
   border: 0;
+}*/
+
+.list-placeholder {
+  margin: 32px auto;
+  width: 256px;
+  text-align: center;
+}
+
+.list-placeholder .subtitle {
+  color:#a0a0a0;
 }
 
 .message {
@@ -164,6 +192,12 @@
   border-radius: 8px;
 }
 
+.send-message-card {
+  border-radius: 8px;
+  margin: 16px;
+  /*padding: 8px;*/
+}
+/*
 .message-list {
   box-sizing: border-box;
   padding-left: 6px;
@@ -180,8 +214,8 @@
   height: 100%;
   font-size: 34px;
   font-weight: 300;
-}
-
+}*/
+/*
 .send-message-form {
   background: var(--send-message-form);
   display: flex;
@@ -204,7 +238,7 @@
 .send-message-form input::placeholder {
   color: var(--main-text-color);
 }
-
+*/
 .help-text {
   position: absolute;
   top: 10px;
diff --git a/client/src/pages/accountSettings.jsx b/client/src/pages/accountSettings.jsx
index d7f71cf..12fa43a 100644
--- a/client/src/pages/accountSettings.jsx
+++ b/client/src/pages/accountSettings.jsx
@@ -13,10 +13,10 @@
     this.accountId = props.accountId || props.match.params.accountId
     this.state = { loaded: false, account: props.account }
     this.req = undefined
-    this.controller = new AbortController()
   }
 
   componentDidMount() {
+    this.controller = new AbortController()
     if (this.req === undefined) {
       this.req = authManager.fetch(`/api/accounts/${this.accountId}`, {signal: this.controller.signal})
         .then(res => res.json())
diff --git a/client/src/pages/addContactPage.jsx b/client/src/pages/addContactPage.jsx
new file mode 100644
index 0000000..ee57b69
--- /dev/null
+++ b/client/src/pages/addContactPage.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { useHistory } from "react-router-dom";
+
+import { Box, Container, Fab, Card, CardContent, Typography } from '@material-ui/core';
+import GroupAddRounded from '@material-ui/icons/GroupAddRounded';
+import { makeStyles } from '@material-ui/core/styles';
+import authManager from '../AuthManager'
+
+const useStyles = makeStyles((theme) => ({
+  root: {
+    '& > *': {
+      margin: theme.spacing(1),
+    },
+  },
+  extendedIcon: {
+    marginRight: theme.spacing(1),
+  },
+}))
+
+export default function AddContactPage(props) {
+  const classes = useStyles()
+  const history = useHistory();
+  const accountId = props.accountId || props.match.params.accountId
+  const contactId = props.contactId || props.match.params.contactId
+
+  const handleClick = async e => {
+    const response = await authManager.fetch(`/api/accounts/${accountId}/conversations`, {
+      method: 'POST',
+      headers: {
+        'Accept': 'application/json',
+        'Content-Type': 'application/json'
+      },
+      body: JSON.stringify({members:[contactId]})
+    }).then(res => res.json())
+
+    console.log(response)
+    if (response.conversationId) {
+      history.push(`/account/${accountId}/conversation/${response.conversationId}`)
+    }
+  }
+
+  return (
+    <Container className='message-list'>
+      <Card variant='outlined' style={{ borderRadius: 16, maxWidth: 560, margin: "16px auto" }}>
+        <CardContent>
+          <Typography variant='h6'>Jami key ID</Typography>
+          <Typography variant='body1'>{contactId}</Typography>
+          <Box style={{textAlign: 'center', marginTop: 16}}>
+          <Fab variant='extended' color='primary' onClick={handleClick}>
+            <GroupAddRounded className={classes.extendedIcon} />
+            Add contact
+          </Fab>
+          </Box>
+        </CardContent>
+      </Card>
+    </Container>)
+}
diff --git a/client/src/pages/messenger.jsx b/client/src/pages/messenger.jsx
index a91a04e..f5e5104 100644
--- a/client/src/pages/messenger.jsx
+++ b/client/src/pages/messenger.jsx
@@ -4,18 +4,23 @@
 import MessageList from '../components/MessageList'
 import SendMessageForm from '../components/SendMessageForm'
 import NewContactForm from '../components/NewContactForm'
+
 //import Sound from 'react-sound';
 import io from "socket.io-client";
 import ConversationList from '../components/ConversationList';
 import CircularProgress from '@material-ui/core/CircularProgress';
 //const socket = io.connect('http://localhost:3000');
 import authManager from '../AuthManager'
+import Conversation from '../../../model/Conversation'
+import Contact from '../../../model/Contact'
+import ConversationView from '../components/ConversationView';
+import AddContactPage from './addContactPage.jsx';
 
 class JamiMessenger extends React.Component {
 
   constructor(props) {
     super(props)
-    this.accountId = props.accountId || props.match.params.accountId
+
     this.state = {
       conversations: undefined,
       messages: [],
@@ -34,16 +39,20 @@
     //  })
     //});
     this.sendMessage = this.sendMessage.bind(this)
+    this.handleSearch = this.handleSearch.bind(this)
     this.controller = new AbortController()
   }
 
   componentDidMount() {
+    const accountId = this.props.accountId || this.props.match.params.accountId
+    const conversationId = this.props.conversationId || this.props.match.params.conversationId
+
     if (this.req === undefined) {
-      this.req = authManager.fetch(`/api/accounts/${this.accountId}/conversations`, {signal: this.controller.signal})
+      this.req = authManager.fetch(`/api/accounts/${accountId}/conversations`, {signal: this.controller.signal})
         .then(res => res.json())
         .then(result => {
           console.log(result)
-          this.setState({conversations: result})
+          this.setState({conversations:  Object.values(result).map(c => Conversation.from(accountId, c))})
         })
     }
     /*socket.on('receivedMessage', (data) => {
@@ -63,6 +72,27 @@
     this.req = undefined
   }
 
+  handleSearch(query) {
+    const accountId = this.props.accountId || this.props.match.params.accountId
+    const conversationId = this.props.conversationId || this.props.match.params.conversationId
+
+    authManager.fetch(`/api/accounts/${accountId}/ns/name/${query}`)
+    .then(response => {
+      if (response.status === 200) {
+        return response.json()
+      } else {
+        throw new Error(response.status)
+      }
+    }).then(response => {
+      console.log(response)
+      const contact = new Contact(response.address)
+      contact.setRegisteredName(response.name)
+      this.setState({searchResult: contact ? Conversation.fromSingleContact(accountId, contact) : undefined})
+    }).catch(e => {
+      this.setState({searchResult: e})
+    })
+  }
+
   sendMessage(text) {
     var data = {
       senderId: 'Me',
@@ -77,13 +107,23 @@
     })
   }
   render() {
+    const accountId = this.props.accountId || this.props.match.params.accountId
+    const conversationId = this.props.conversationId || this.props.match.params.conversationId
+    const contactId = this.props.contactId || this.props.match.params.contactId
+
+    console.log("JamiMessenger render " + conversationId)
+    console.log(this.props)
+    console.log(this.state)
+
     return (
       <div className="app" >
         <Header />
-        {this.state.conversations ? <ConversationList conversations={this.state.conversations} /> : <CircularProgress />}
-        <MessageList messages={this.state.messages} />
-        <SendMessageForm sendMessage={this.sendMessage} />
-        <NewContactForm />
+        {this.state.conversations ?
+          <ConversationList search={this.state.searchResult} conversations={this.state.conversations} accountId={accountId} /> :
+          <CircularProgress />}
+        <NewContactForm onChange={query => this.handleSearch(query)} />
+        {conversationId && <ConversationView accountId={accountId} conversationId={conversationId} />}
+        {contactId && <AddContactPage accountId={accountId} contactId={contactId} />}
         {this.state.sound && <Sound
           url="stairs.mp3" /*https://notificationsounds.com/message-tones/stairs-567*/
           playStatus={Sound.status.PLAYING}
diff --git a/model/Account.js b/model/Account.js
index 6b43f1c..1f70c33 100644
--- a/model/Account.js
+++ b/model/Account.js
@@ -8,6 +8,7 @@
         this.contactCache = {}
         this.contacts = {}
         this.conversations = {}
+        this.lookups = []
     }
 
     static from(object) {
@@ -54,6 +55,9 @@
     getConversationIds() {
         return Object.keys(this.conversations)
     }
+    getConversations() {
+        return this.conversations
+    }
 
     getConversation(conversationId) {
         return this.conversations[conversationId]
diff --git a/model/Contact.js b/model/Contact.js
index b21ddcd..9d00098 100644
--- a/model/Contact.js
+++ b/model/Contact.js
@@ -6,12 +6,17 @@
     }
 
     static from(object) {
-        return new Contact(object.uri)
+        const contact = new Contact(object.uri)
+        if (object.registeredName)
+            contact.setRegisteredName(object.registeredName)
+        return contact
     }
 
     getUri() { return this.uri }
 
-    getRegisteredName() { this.registeredName }
+    getRegisteredName() { return this.registeredName }
+
+    setRegisteredName(name) { this.registeredName = name }
 
     getDisplayName() {
         return this.displayName || this.getRegisteredName() || this.getUri()
@@ -19,7 +24,8 @@
 
     getObject() {
         return {
-            uri: this.uri
+            uri: this.uri,
+            registeredName: this.registeredName
         }
     }
 }
diff --git a/model/Conversation.js b/model/Conversation.js
index 8046219..22dc477 100644
--- a/model/Conversation.js
+++ b/model/Conversation.js
@@ -1,27 +1,43 @@
+const Contact = require('./Contact')
+
 class Conversation {
-    constructor(id, members) {
+    constructor(id, accountId, members) {
         this.id = id
+        this.accountId = accountId
         this.members = members || []
         this.messages = []
     }
 
-    static from(object) {
-        return new Conversation(object.id, object.members)
+    static from(accountId, object) {
+        return new Conversation(object.id, accountId, object.members.map(member => {
+            member.contact = Contact.from(member.contact)
+            return member
+        }))
+    }
+    static fromSingleContact(accountId, contact) {
+        return new Conversation(undefined, accountId, [{contact}])
     }
 
     getId() { return this.id }
 
+    getAccountId() { return this.accountId }
+
     getDisplayName() {
         if (this.members.length !== 0) {
-            return this.members[0].getDisplayName()
+            return this.members[0].contact.getDisplayName()
         }
         return this.getDisplayUri()
     }
 
-    getObject() {
+    getObject(params) {
+        const members = params.memberFilter ? this.members.filter(params.memberFilter) : this.members
         return {
             id: this.id,
-            members: this.members
+            members: members.map(member => {
+                const copiedMember = { role: member.role }//Object.assign({}, member);
+                copiedMember.contact = member.contact.getObject()
+                return copiedMember
+            })
         }
     }
 
@@ -30,7 +46,15 @@
     }
 
     getDisplayUri() {
-        return this.getId()
+        return this.getId() || this.getFirstMember().contact.getUri()
+    }
+
+    getFirstMember() {
+        return this.members[0]
+    }
+
+    getMembers() {
+        return this.members
     }
 
     addMessage(message) {
diff --git a/routes/jami.js b/routes/jami.js
index 9ba97a7..5d50002 100644
--- a/routes/jami.js
+++ b/routes/jami.js
@@ -7,6 +7,7 @@
 
     getRouter() {
         const router = express.Router({mergeParams: true})
+        router.use(express.json());
 
         // Accounts
         router.get(['/accounts'], (req, res, next) => {
@@ -14,7 +15,10 @@
             res.json(this.jami.getAccountList().map(account => account.getSummary()))
         })
 
-        router.get(['/accounts/:accountId'], (req, res, next) => {
+        const accountRouter = express.Router({mergeParams: true})
+        router.use('/accounts/:accountId', accountRouter)
+
+        accountRouter.get(['/'], (req, res, next) => {
             console.log(`Get account ${req.params.accountId}`)
             const account = this.jami.getAccount(req.params.accountId)
             if (account)
@@ -24,7 +28,7 @@
         })
 
         // Contacts
-        router.get(['/accounts/:accountId/contacts'], (req, res, next) => {
+        accountRouter.get(['/contacts'], (req, res, next) => {
             console.log(`Get account ${req.params.accountId}`)
             const account = this.jami.getAccount(req.params.accountId)
             if (account)
@@ -34,24 +38,79 @@
         })
 
         // Conversations
-        const conversationRouter = express.Router({mergeParams: true})
-        conversationRouter.get('/', (req, res, next) => {
+        accountRouter.get('/conversations', (req, res, next) => {
             console.log(`Get conversations for account ${req.params.accountId}`)
             const account = this.jami.getAccount(req.params.accountId)
             if (!account)
                 res.sendStatus(404)
-            res.json(account.getConversationIds())
+            const conversations = account.getConversations()
+            res.json(Object.keys(conversations).map(conversationId => conversations[conversationId].getObject({
+                memberFilter: member => member.contact.getUri() !== account.getUri()
+            })))
+            //res.json(account.getConversations())
         })
 
-        conversationRouter.get('/:conversationId', (req, res, next) => {
+        accountRouter.post('/conversations', (req, res, next) => {
+            console.log(`Create conversations for account, contact ${req.params.accountId}`)
+            console.log(req.body)
+            const account = this.jami.getAccount(req.params.accountId)
+            if (!account)
+                res.sendStatus(404)
+            if (req.body.members.length === 1) {
+                const details = this.jami.addContact(req.params.accountId, req.body.members[0])
+                res.json(details)
+            } else
+                res.sendStatus(400)
+        })
+
+        accountRouter.get('/conversations/:conversationId', (req, res, next) => {
             console.log(`Get conversation ${req.params.conversationId} for account ${req.params.accountId}`)
             const account = this.jami.getAccount(req.params.accountId)
             if (!account)
                 res.sendStatus(404)
-            res.json(account.getConversation(req.params.conversationId))
+            const conversation = account.getConversation(req.params.conversationId)
+            if (!conversation)
+                res.sendStatus(404)
+            else {
+                res.json(conversation.getObject({
+                    memberFilter: member => member.contact.getUri() !== account.getUri()
+                }))
+            }
         })
 
-        router.use('/accounts/:accountId/conversations', conversationRouter)
+        // Nameserver
+        const nsRouter = express.Router({mergeParams: true})
+        accountRouter.use('/ns', nsRouter)
+
+        nsRouter.get(['/name/:nameQuery'], (req, res, next) => {
+            console.log(`Name lookup ${req.params.nameQuery}`)
+            this.jami.lookupName(req.params.accountId, req.params.nameQuery)
+                .then(result => {
+                    if (result.state == 0)
+                        res.json(result)
+                    else if (result.state == 1)
+                        res.sendStatus(400)
+                    else
+                        res.sendStatus(404)
+                }).catch(e => {
+                    res.sendStatus(404)
+                })
+        })
+        nsRouter.get(['/addr/:addrQuery'], (req, res, next) => {
+            console.log(`Address lookup ${req.params.addrQuery}`)
+            this.jami.lookupAddress(req.params.accountId, req.params.addrQuery)
+                .then(result => {
+                    if (result.state == 0)
+                        res.json(result)
+                    else if (result.state == 1)
+                        res.sendStatus(400)
+                    else
+                        res.sendStatus(404)
+                }).catch(e => {
+                    res.sendStatus(404)
+                })
+        })
+
         return router
     }
 }