Browse Source

Add makeCall functionality

- Add make call possibility to contacts with home number in UI
- Rework interface a little
- Add server side calls support
- Add call status popup
- Implement endCall functionlity
Alexey Edelev 5 years ago
parent
commit
c9949d0b19

+ 2 - 1
examples/addressbook/CMakeLists.txt

@@ -17,7 +17,8 @@ set(GENERATED_HEADERS
     phonenumber.h
     simpleresult.h
     globalenums.h
-    none.h)
+    none.h
+    callstatus.h)
 
 file(GLOB PROTO_FILES ABSOLUTE ${CMAKE_CURRENT_SOURCE_DIR}/proto/addressbook.proto)
 

+ 14 - 0
examples/addressbook/addressbookengine.cpp

@@ -47,6 +47,7 @@ public:
 AddressBookEngine::AddressBookEngine() : QObject()
   , m_client(new AddressBookClient)
   , m_contacts(new ContactsListModel({}, this))
+  , m_callStatus(CallStatus::Inactive)
 {
     //Prepare ssl configuration
     QSslConfiguration conf = QSslConfiguration::defaultConfiguration();
@@ -64,13 +65,26 @@ AddressBookEngine::AddressBookEngine() : QObject()
     connect(m_client, &AddressBookClient::contactsUpdated, this, [this](const Contacts &contacts) {
         m_contacts->reset(contacts.list());
     });
+    m_client->subscribeCallStatusUpdates(qtprotobuf::examples::None(), m_callStatus);
 }
 
 void AddressBookEngine::addContact(qtprotobuf::examples::Contact *contact)
 {
+    Q_ASSERT(contact != nullptr);
     m_client->addContact(*contact);
 }
 
+void AddressBookEngine::makeCall(qtprotobuf::examples::PhoneNumber *phoneNumber)
+{
+    Q_ASSERT(phoneNumber != nullptr);
+    m_client->makeCall(*phoneNumber);
+}
+
+void AddressBookEngine::endCall()
+{
+    m_client->endCall(qtprotobuf::examples::None());
+}
+
 AddressBookEngine::~AddressBookEngine()
 {
     delete m_client;

+ 12 - 0
examples/addressbook/addressbookengine.h

@@ -28,6 +28,7 @@
 #include <QObject>
 #include "contacts.h"
 #include "universallistmodel.h"
+#include "callstatus.h"
 
 namespace qtprotobuf { namespace examples {
 class AddressBookClient;
@@ -40,6 +41,7 @@ class AddressBookEngine : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(ContactsListModel *contacts READ contacts NOTIFY contactsChanged)
+    Q_PROPERTY(qtprotobuf::examples::CallStatus *callStatus READ callStatus NOTIFY callStatusChanged)
 public:
     AddressBookEngine();
     virtual ~AddressBookEngine();
@@ -49,15 +51,25 @@ public:
         return m_contacts;
     }
 
+    qtprotobuf::examples::CallStatus *callStatus()
+    {
+        return &m_callStatus;
+    }
+
     Q_INVOKABLE void addContact(qtprotobuf::examples::Contact *contact);
+    Q_INVOKABLE void makeCall(qtprotobuf::examples::PhoneNumber *phoneNumber);
+    Q_INVOKABLE void endCall();
 
 signals:
     void contactsChanged();
 
+    void callStatusChanged();
+
 private:
     qtprotobuf::examples::AddressBookClient *m_client;
     ContactsListModel *m_contacts;
     qtprotobuf::examples::ContactList m_container;
+    qtprotobuf::examples::CallStatus m_callStatus;
 };
 
 Q_DECLARE_METATYPE(ContactsListModel*)

+ 14 - 1
examples/addressbook/proto/addressbook.proto

@@ -69,6 +69,18 @@ message SimpleResult {
     bool ok = 1;
 }
 
+message CallStatus {
+    enum Status {
+        Invalid = 0;
+        Inactive = 1;
+        Active = 2;
+        Ended = 3;
+    };
+
+    Status status = 1;
+    PhoneNumber phoneNumber = 2;
+}
+
 message ListFrame {
     sint32 start = 1;
     sint32 end = 2;
@@ -82,7 +94,8 @@ service AddressBook {
     rpc addContact(Contact) returns (Contacts) {}
     rpc removeContact(Contact) returns (Contacts) {}
     rpc contacts(ListFrame) returns (stream Contacts) {}
-    rpc makeCall(Contact) returns (SimpleResult) {}
+    rpc callStatus(None) returns (stream CallStatus) {}
+    rpc makeCall(PhoneNumber) returns (CallStatus) {}
     rpc endCall(None) returns (SimpleResult) {}
     rpc navigateTo(Address) returns (SimpleResult) {}
 }

+ 1 - 1
examples/addressbook/qml/AddContactView.qml

@@ -23,7 +23,7 @@
  * DEALINGS IN THE SOFTWARE.
  */
 
-import QtQuick 2.0
+import QtQuick 2.9
 import QtQuick.Controls 2.4
 
 import qtprotobuf.examples 1.0

+ 92 - 0
examples/addressbook/qml/CallPopup.qml

@@ -0,0 +1,92 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+import QtQuick 2.9
+import QtQuick.Controls 2.4
+
+import qtprotobuf.examples 1.0
+
+Rectangle {
+    id: root
+    property CallStatus callStatus: null
+    visible: false
+    color: "#B0BEC5"
+    border.width: 1
+    border.color: "#cfdfe7"
+    PrimaryText {
+        id: _statusText
+        text: _d.getCallStatusText()
+        anchors.horizontalCenter: parent.horizontalCenter
+        anchors.bottom: _hangButton.top
+        anchors.bottomMargin: 20
+    }
+    radius: 10
+
+    states: [
+        State {
+            name: "opened"
+            when: callStatus.status === CallStatus.Active || callStatus.status === CallStatus.Ended
+            PropertyChanges {
+                target: root
+                visible: true
+            }
+        },
+        State {
+            name: "closed"
+            when: callStatus.status !== CallStatus.Active && callStatus.status !== CallStatus.Ended
+            PropertyChanges {
+                target: root
+                visible: false
+            }
+        }
+    ]
+
+    FloatingRoundButton {
+        id: _hangButton
+        primaryColor: "#d31a5b"
+        secondaryColor: "#E91E63"
+        anchors.bottom: parent.bottom
+        anchors.horizontalCenter: parent.horizontalCenter
+        anchors.bottomMargin: 10
+        icon: "qrc:/images/drop.png"
+        onClicked: {
+            abEngine.endCall()
+        }
+    }
+
+    QtObject {
+        id: _d
+        function getCallStatusText() {
+            switch(callStatus.status) {
+            case CallStatus.Active:
+                return qsTr("Active call to %1...").arg("+" + callStatus.phoneNumber.countryCode + " " + callStatus.phoneNumber.number)
+            case CallStatus.Ended:
+                return qsTr("Ending call...")
+            default:
+                return qsTr("No active call")
+            }
+        }
+    }
+}

+ 2 - 0
examples/addressbook/qml/ContactDetails.qml

@@ -154,6 +154,8 @@ StackItem {
         primaryColor: "#4CAF50"
         secondaryColor: "#58cb5c"
         onClicked: {
+            abEngine.makeCall(contact.phonesData[0])
+            stack.pop();
         }
     }
 }

+ 16 - 0
examples/addressbook/qml/main.qml

@@ -75,6 +75,22 @@ ApplicationWindow {
             anchors.fill: background
         }
     }
+
+    Connections {
+        target: abEngine.callStatus
+        onStatusChanged: {
+            console.log("Call status: " + abEngine.callStatus.status);
+        }
+    }
+
+    CallPopup {
+        id: activeCallPopup
+        width: 300
+        height: 170
+        anchors.centerIn: parent
+        callStatus: abEngine.callStatus
+    }
+
     Component.onCompleted: {
         mainStack.push(contactList, {"stack": mainStack})
     }

+ 1 - 0
examples/addressbook/resources.qrc

@@ -21,5 +21,6 @@
         <file>qml/TextRow.qml</file>
         <file>images/call.png</file>
         <file>images/drop.png</file>
+        <file>qml/CallPopup.qml</file>
     </qresource>
 </RCC>

+ 73 - 9
examples/addressbookserver/main.cpp

@@ -2,17 +2,23 @@
 #include <fstream>
 #include <sstream>
 #include <vector>
+#include <thread>
+
 #include <grpc++/grpc++.h>
 #include "addressbook.pb.h"
 #include "addressbook.grpc.pb.h"
 using namespace ::qtprotobuf::examples;
 
 class ContactsHandler;
+class CallHandler;
 
-class AddressBookService final : public AddressBook::WithAsyncMethod_contacts<AddressBook::Service> {
+class AddressBookService final : public AddressBook::WithAsyncMethod_callStatus<AddressBook::WithAsyncMethod_contacts<AddressBook::Service>> {
 public:
+    PhoneNumber m_lastPhone;
+    CallStatus m_lastCallStatus;
     Contacts m_contacts;
     std::vector<::grpc::ServerAsyncWriter<Contacts> *> m_clients;
+    std::vector<::grpc::ServerAsyncWriter<CallStatus> *> m_callClients;
     AddressBookService(): m_clients({}) {
         Contact* contact = m_contacts.add_list();
         contact->set_firstname("John");
@@ -35,6 +41,9 @@ public:
         PhoneNumber *home = (*contact->mutable_phones()).Add();
         home->set_countrycode(49);
         home->set_number(12324534679);
+
+        m_lastCallStatus.set_allocated_phonenumber(new PhoneNumber);
+        m_lastCallStatus.set_status(CallStatus::Inactive);
     }
 
     ~AddressBookService() {}
@@ -46,8 +55,9 @@ public:
     }
 
     void registerWriter(ContactsHandler *handler);
+    void registerCallStatusHandler(CallHandler *handler);
 
-    ::grpc::Status addContact(::grpc::ServerContext* context, const Contact* request, Contacts* response) override
+    ::grpc::Status addContact(::grpc::ServerContext *context, const Contact *request, Contacts *response) override
     {
         bool isUserOk = false;
         bool isPasswordOk = false;
@@ -72,18 +82,46 @@ public:
         updateContacts();
         return ::grpc::Status();
     }
-    ::grpc::Status removeContact(::grpc::ServerContext* context, const Contact* request, Contacts* response) override
+
+    ::grpc::Status makeCall(grpc::ServerContext *, const PhoneNumber *request, CallStatus *response) override
     {
-        std::cout << "removeContact called" << std::endl;
-        updateContacts();
-        return ::grpc::Status(::grpc::UNIMPLEMENTED, "Unimplemented");
+        m_lastPhone = *request;
+        for(unsigned int i = 0; i < (m_callClients.size() - 1); i++) {
+            response->set_status(CallStatus::Active);
+            PhoneNumber *phoneNumber = new PhoneNumber(*request);
+            response->set_allocated_phonenumber(phoneNumber);
+            m_lastCallStatus = *response;
+            m_callClients[i]->Write(m_lastCallStatus, nullptr);
+        }
+        return ::grpc::Status();
     }
-    ::grpc::Status makeCall(::grpc::ServerContext* context, const Contact* request, SimpleResult* response) override
+
+    ::grpc::Status endCall(grpc::ServerContext *, const None *, SimpleResult *) override
+    {
+        std::cout << "Call ended" << std::endl;
+        m_lastCallStatus.set_status(CallStatus::Ended);
+        for(unsigned int i = 0; i < (m_callClients.size() - 1); i++) {
+            m_callClients[i]->Write(m_lastCallStatus, nullptr);
+        }
+
+        std::this_thread::sleep_for(std::chrono::seconds(1));
+
+        std::cout << "Call ended" << std::endl;
+        m_lastCallStatus.set_status(CallStatus::Inactive);
+        for(unsigned int i = 0; i < (m_callClients.size() - 1); i++) {
+            m_callClients[i]->Write(m_lastCallStatus, nullptr);
+        }
+        return ::grpc::Status();
+    }
+
+    ::grpc::Status removeContact(::grpc::ServerContext *, const Contact *, Contacts *) override
     {
-        std::cout << "makeCall called" << std::endl;
+        std::cout << "removeContact called" << std::endl;
+        updateContacts();
         return ::grpc::Status(::grpc::UNIMPLEMENTED, "Unimplemented");
     }
-    ::grpc::Status navigateTo(::grpc::ServerContext* context, const Address* request, SimpleResult* response) override
+
+    ::grpc::Status navigateTo(::grpc::ServerContext *, const Address *, SimpleResult *) override
     {
         std::cout << "navigateTo called" << std::endl;
         return ::grpc::Status(::grpc::UNIMPLEMENTED, "Unimplemented");
@@ -106,11 +144,30 @@ public:
     ::grpc::ServerCompletionQueue* cq_;
 };
 
+class CallHandler {
+public:
+    CallHandler(AddressBookService* service, ::grpc::ServerCompletionQueue* cq) :  tag_(0xdeadbeee)
+      , writer_(&ctx_)
+      , cq_(cq)
+    {
+        service->RequestcallStatus(&ctx_, &request_, &writer_, cq_, cq_, &tag_);
+        service->registerCallStatusHandler(this);
+    }
+    int tag_;
+    grpc::ServerContext ctx_;
+    None request_;
+    ::grpc::ServerAsyncWriter< ::qtprotobuf::examples::CallStatus> writer_;
+    ::grpc::ServerCompletionQueue* cq_;
+};
 
 void AddressBookService::registerWriter(ContactsHandler *handler)    {
     m_clients.push_back(&(handler->writer_));
 }
 
+void AddressBookService::registerCallStatusHandler(CallHandler *handler) {
+    m_callClients.push_back(&(handler->writer_));
+}
+
 int main(int argc, char *argv[])
 {
     std::string server_address("localhost:65001");
@@ -135,16 +192,23 @@ int main(int argc, char *argv[])
     std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
     std::cout << "Server listening on " << server_address << std::endl;
     ContactsHandler *last = new ContactsHandler(&service, cq.get());
+    CallHandler *lastCall = new CallHandler(&service, cq.get());
     while (true) {
         unsigned int *tag;
         bool ok;
         cq->Next((void**)&tag, &ok);
         if (tag == nullptr) {
+            std::cout << "Some request";
             continue;
         }
         if ((*tag) == 0xdeadbeef) {
             last->writer_.Write(service.m_contacts, nullptr);
             last = new ContactsHandler(&service, cq.get());
         }
+
+        if ((*tag) == 0xdeadbeee) {
+            lastCall->writer_.Write(service.m_lastCallStatus, nullptr);
+            lastCall = new CallHandler(&service, cq.get());
+        }
     }
 }