N'étant pas infographiste, soyez indulgent pour la qualité visuelle. Avant de me lancer dans des explications de code, je tiens à préciser plusieurs choses :
- J'ai utilisé la dernière version du sdk Qt, à savoir la version 1.1.4, avec certains des composants Qt Quick pour Symbian.
- J'ai validé cette application sur un Nokia E7 upgradé avec Symbian Anna. La config de compilation Qt utilisée est donc "Qt 4.7.4 pour Symbian Anna".
- L'application ne permet pas d'envoyer un email avec le simulateur
- Il vous faut créer au moins un compte email sur votre téléphone Symbian
- J'ai fait le choix de gérer la partie purement graphique en QML et la partie logique métier en C++
J'ai créé une classe "helper" dénommée "EmailHelper" comme suit :
class EmailHelper : public QObject
{
Q_OBJECT
public:
explicit EmailHelper(QObject *parent = 0);
~EmailHelper();
Q_INVOKABLE bool sendEmail(QString accountName, QString recipient, QString subject, QString body);
Q_INVOKABLE QStringList getEmailAccountList();
signals:
void stateMsg(const QString &statemsg);
void errorMsg(const QString &errormsg);
private slots:
void messageStateChanged(QMessageService::State s);
private:
QMessageService iMessageService;
QMessageManager iManager;
QMessageService::State state;
QMap<QString, QMessageAccountId> accountList;
};
Comme vous le constatez cette classe me permet de récupérer la liste des comptes email configurés sur le téléphone (méthode getEmailAccountList) et d'envoyer un email depuis un compte donné (méthode sendEmail). Notez la signature particulière de ces deux méthodes : j'ai utilisé la macro "Q_INVOKABLE" afin de les rendre accessible depuis le contexte d'execution déclaratif QML. Enfin pour gérer l'envoi d'email, je m'appuie sur le module "Messaging" de Qt Mobility via l'usage des deux classes QMessageService et QMessageManager.
Coté implémentation, rien de bien compliqué :
- Un constructeur où je m'abonne au signal "stateChanged" émis par le service de messagerie:
QObject(parent)
{
state = QMessageService::InactiveState;
connect(&iMessageService, SIGNAL(stateChanged(QMessageService::State)), this, SLOT(messageStateChanged(QMessageService::State)));
}
- La méthode de récupération des comptes emails :
{
QStringList accountNameList;
#ifdef SENDEMAIL_ENABLED
// Récupération de la liste des comptes email et ajout dans la liste
foreach (const QMessageAccountId &id, iManager.queryAccounts())
{
QMessageAccount account(id);
if (account.messageTypes() & QMessage::Email)
{
QString name(account.name());
accountList.insert(name, account.id());
accountNameList.append(name);
}
}
#endif
return accountNameList;
}
Note : le service de messagerie étant commun aux SMS, MMS et Email, je mets un filtre en place sur les emails. Globalement un compte email est composé d'un libellé et d'un identifiant ; je stocke ces informations dans une propriété de type QMap afin de ré-utiliser la correspondance Libellé/Identifiant utlérieurement pour l'envoi d'un email. Et je retourne uniquement la liste de libellés de comptes à destination de l'affichage géré en QML
- La méthode d'envoi d'un email :
bool EmailHelper::sendEmail(QString accountName, QString recipient, QString subject, QString body)
{
#ifdef SENDEMAIL_ENABLED
if (!QMessageAccount::defaultAccount(QMessage::Email).isValid())
{
emit errorMsg("Aucun compte configuré pour envoyer un email.");
return false;
}
if (state == QMessageService::InactiveState || state == QMessageService::FinishedState)
{
QMessage message;
QMessageAccountId accountId = accountList[accountName];
message.setType(QMessage::Email);
message.setParentAccountId(accountId);
// Email de destination
QMessageAddress::Type addrType(QMessageAddress::Email);
QMessageAddressList toList;
toList.append(QMessageAddress(addrType, recipient));
message.setTo(toList);
// Objet du mail
if (subject.isEmpty()) {
emit errorMsg("Aucun objet spécifié");
return false;
}
else
message.setSubject(subject);
// Corps du mail
if (body.isEmpty()) {
emit errorMsg("Aucun contenu de message spécifié");
return false;
}
else
message.setBody(body);
// Envoi de l'email
bool result = iMessageService.send(message);
return result;
}
else
{
return false;
}
}
Cette méthode initialise un objet QMessage à partir du compte email séléctionné par l'utilisateur avec :
1. Une adresse de destination
2. Un objet
3. Un corps de mail
Note : pour aller plus loin dans la prise en main de l'api de messaging, je vous invite à ajouter une pièce jointe.
Afin de rendre ma classe visible et instantiable dans le contexte déclaratif QML, j'ai ajouté la ligne suivante dans la fonction "main" du programme :
qmlRegisterType<EmailHelper>("EmailHelper",1,0,"EmailHelper");
Ainsi, comme nous le verrons plutard, je pourrai instantier mon objet depuis QML via une simple déclaration comme cela :
EmailHelper {
}
}
- Pour finir j'ai implémenté le slot privé "messageStateChange" dont le rôle est de relayer le signal public "stateMsg" à la partie QML afin d'en informer de manière asynchrone du statut de l'envoi d'email :
void EmailHelper::messageStateChanged(QMessageService::State s){
Dernier point : il faut que l'application soit signée avec un certificat "Express Signed" ; le mode "auto-signé" ne fonctionne pas car nous avons besoin de la permission "ReadDeviceData". Donc soit vous utiliserez un certificat fourni par PublishToOVI via votre compte Nokia Publisher, soit vous utiliserez un certificat SymbianSign.
Ayant utilisé le template de projet Qt Quick pour Symbian avec utilisation des composants Qt Quick, je me retrouve donc avec deux fichiers :
1. main.qml
2. MainPage.qml
J'ai mis tout le code dans le fichier MainPage.qml ; pour un vrai projet il est recommendé de modulariser ses composants via une arborescence de fichiers bien organisés.
Commençons par les inclusions de module :
Rectangle {
Je définis ensuite une petite boîte de dialogue servant à afficher les messages d'erreur et de succés :
CommonDialog {
Puis j'instancie mon élément "EmailHelper" :
EmailHelper {
Comme vous pouvez le constater j'y défini les slots pour capturer les signaux "errorMsg" et "stateMsg" ; j'y rattache également la liste des noms de comptes emails via la variable "accountNameList" qui sera initialisée au chargement de la page QML comme le montre le code suivant :
Component.onCompleted: {
Par conséquent, la liste des comptes mails est récupérée au chargement de la page ; si cette liste est vide ou indisponible, j'affiche un popup invitant l'utilisateur à créer un compte email sur son téléphone.
Enfin j'affiche le formulaire de saisie permettant de contruire l'email à envoyer ; ce formulaire possède un header avec un bouton d'envoi :
// Fixed Header
La partie formulaire sera positionnée dans une zone scrollable (Flickable) :
Flickable {
// Sélection du compte email
Ensuite il s'agit d'ajouter les zones de saisies et le tour est joué. Une fois les données saisies, l'utilisateur n'a plus qu'à valider le bouton situé dans le header du formulaire et l'email est envoyé.
// Destinataire du message
state = s;
if (s == QMessageService::InactiveState)
{
emit stateMsg("InactiveState");
}
else if (s == QMessageService::ActiveState)
{
emit stateMsg("ActiveState");
}
else if (s == QMessageService::CanceledState)
{
emit stateMsg("CanceledState");
}
else if (s == QMessageService::FinishedState)
{
emit stateMsg("FinishedState");
}
else
{
emit stateMsg(QString("QMessageService::%1").arg(s));
}
}
Et voilà pour la partie C++
Partie Environnement :
Afin de pouvoir compiler et envoyer un email de manière effective, il est INDISPENSABLE de modifier son fichier projet (*.pro) comme suit :
- Ajouter les deux lignes suivantes pour l'utilisation du module "Messaging" de Qt Mobility :
CONFIG += mobility
MOBILITY += messaging
- Ajouter les permissions suivantes (notamment pour accéder aux comptes email de l'utilisateur) :
symbian:TARGET.CAPABILITY += NetworkServices \
LocalServices \
ReadUserData \
WriteUserData \
UserEnvironment \
ReadDeviceData \
WriteDeviceData
- Ajouter la dépendance aux composants Qt Quick
Dernier point : il faut que l'application soit signée avec un certificat "Express Signed" ; le mode "auto-signé" ne fonctionne pas car nous avons besoin de la permission "ReadDeviceData". Donc soit vous utiliserez un certificat fourni par PublishToOVI via votre compte Nokia Publisher, soit vous utiliserez un certificat SymbianSign.
Partie UI en QML :
Ayant utilisé le template de projet Qt Quick pour Symbian avec utilisation des composants Qt Quick, je me retrouve donc avec deux fichiers :
1. main.qml
2. MainPage.qml
J'ai mis tout le code dans le fichier MainPage.qml ; pour un vrai projet il est recommendé de modulariser ses composants via une arborescence de fichiers bien organisés.
Commençons par les inclusions de module :
- import com.nokia.symbian 1.1 : pour utiliser les composants Qt Quick
- import Qt.labs.components 1.1 : pour utiliser le regroupement exclusif des boutons radios
- import EmailHelper 1.0 : notre classe "helper" pour envoyer des emails
Rectangle {
id: rootWindow
anchors.fill: parent
width: 360
height: 640
gradient: Gradient {
GradientStop {
position: 0
color: "#ccc094"
}
GradientStop {
position: 0.8
color: "#777057"
}
GradientStop {
position: 1
color: "#000000"
}
}
Je définis ensuite une petite boîte de dialogue servant à afficher les messages d'erreur et de succés :
CommonDialog {
id: dialog
titleText: "Information"
buttonTexts: ["OK"]
content: Rectangle {
width: 300
height: 200
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Text {
id: dialogMessage
anchors.centerIn: parent
textFormat: Text.StyledText
width:290
text:""
wrapMode: Text.Wrap
color: "white"
}
}
onButtonClicked: {
if(mainPage.quitRequested == true) Qt.quit()
}
}
Puis j'instancie mon élément "EmailHelper" :
EmailHelper {
id: helper
property int accountNumber;
property variant accountNameList: []
onStateMsg: {
console.log(statemsg)
if(statemsg == "FinishedState")
{
indicator.visible = false
indicator.running = false
dialogMessage.text = qsTr("Email envoyé avec succès")
dialog.open()
}
}
onErrorMsg: {
console.log(errormsg)
indicator.visible = false
indicator.running = false
dialogMessage.text = errormsg
dialog.open()
}
}
Comme vous pouvez le constater j'y défini les slots pour capturer les signaux "errorMsg" et "stateMsg" ; j'y rattache également la liste des noms de comptes emails via la variable "accountNameList" qui sera initialisée au chargement de la page QML comme le montre le code suivant :
Component.onCompleted: {
console.log("Component.onCompleted")
helper.accountNameList = helper.getEmailAccountList()
helper.accountNumber = helper.accountNameList.length
if( helper.accountNumber == 0)
{
mainPage.quitRequested = true
dialogMessage.text = qsTr("Désolé vous n'avez aucun compte mail configuré sur ce téléphone")
dialog.open()
}
else console.log("Vous avez "+ helper.accountNumber +" comptes");
for(var i=0;i<helper.accountNumber;i++){
console.log("Compte "+i+" : "+helper.accountNameList[i])
}
}
Par conséquent, la liste des comptes mails est récupérée au chargement de la page ; si cette liste est vide ou indisponible, j'affiche un popup invitant l'utilisateur à créer un compte email sur son téléphone.
Enfin j'affiche le formulaire de saisie permettant de contruire l'email à envoyer ; ce formulaire possède un header avec un bouton d'envoi :
// Fixed Header
Item {
id: header
width: rootWindow.width
height: 60
z:3
Rectangle {
color: "black"
anchors.fill: parent
BusyIndicator {
id: indicator
anchors.leftMargin: 5
running: false
visible: false
}
Text {
id: title
text: qsTr("Custom Mail Client")
color: "white"
anchors.centerIn: parent
font.bold: true
font.pixelSize:20
}
Button {
id: send
iconSource: "send.png"
x:rootWindow.width-send.width
anchors.rightMargin: 5
width:header.height-5
height:header.height-5
onClicked: {
if(mailAccount.account.length>0 &&
mailRecipient.recipient.length>0 &&
mailSubject.subject.length>0 &&
mailBody.body.length>0)
{
var requestResult = helper.sendEmail(mailAccount.account, mailRecipient.recipient, mailSubject.subject, mailBody.body);
if(requestResult)
{
indicator.visible = true
indicator.running = true
}
else
{
dialogMessage.text = qsTr("Echec de la demande d'envoi d'email !");
dialog.open()
}
}
else if(mailRecipient.recipient.length == 0)
{
dialogMessage.text = qsTr("Veuillez saisir une addresse de destination")
dialog.open()
}
else if(mailSubject.subject.length == 0)
{
dialogMessage.text = qsTr("Veuillez renseigner un objet")
dialog.open()
}
else if(mailBody.body.length == 0)
{
dialogMessage.text = qsTr("Veuillez renseigner le corps du mail")
dialog.open()
}
}
}
}
}
La partie formulaire sera positionnée dans une zone scrollable (Flickable) :
Flickable {
id:flickable
width: parent.width
height:parent.height-header.height
anchors.top: header.bottom
contentWidth: flickable.width;
contentHeight: mailAccount.height+flickable.height*3/2
flickableDirection: Flickable.VerticalFlick
La partie bouton radio du formulaire pour sélectionner le compte email :
// Sélection du compte email
Item {
property string account: helper.accountNameList[0]
id: mailAccount
width: rootWindow.width-20
height: helper.accountNumber*50
anchors.top: parent.top
anchors.topMargin: 10
anchors.left: parent.left
anchors.leftMargin: 10
anchors.rightMargin: 10
Text {
id: mailAccountLabel
text: qsTr("Compte Mail :")
font.bold: true
font.pixelSize:20
}
CheckableGroup { id: group }
Column {
id: column
anchors.top: mailAccountLabel.bottom
anchors.topMargin: 5
spacing: platformStyle.paddingMedium
Repeater {
model: helper.accountNumber
RadioButton {
text: helper.accountNameList[index]
platformExclusiveGroup: group
onClicked: {
mailAccount.account = text
}
}
}
}
}
Ensuite il s'agit d'ajouter les zones de saisies et le tour est joué. Une fois les données saisies, l'utilisateur n'a plus qu'à valider le bouton situé dans le header du formulaire et l'email est envoyé.
// Destinataire du message
Item {
property alias recipient: recipientRect.text
id: mailRecipient
width: rootWindow.width-20
height: 50
anchors.top: mailAccount.bottom
anchors.topMargin: 25
anchors.left: parent.left
anchors.leftMargin: 10
anchors.rightMargin: 10
Text {
id: mailLabel
text: qsTr("Email de destination :")
font.bold: true
font.pixelSize:20
}
TextField {
id: recipientRect
anchors.top: mailLabel.bottom
anchors.topMargin: 5
width: parent.width
}
}
// Objet du mail
Item {
property alias subject: subjectRect.text
id: mailSubject
width: rootWindow.width-20
height: 50
anchors.top: mailRecipient.bottom
anchors.topMargin: 25
anchors.left: parent.left
anchors.leftMargin: 10
anchors.rightMargin: 10
Text {
id: subjectLabel
text: qsTr("Objet :")
font.bold: true
font.pixelSize: 20
}
TextField {
id: subjectRect
anchors.top: subjectLabel.bottom
anchors.topMargin: 5
width: parent.width
}
}
// Corps du mail
Item {
property alias body: bodyInput.text
id: mailBody
width: rootWindow.width-20
height: 150
anchors.top: mailSubject.bottom
anchors.topMargin: 25
anchors.left: parent.left
anchors.leftMargin: 10
anchors.rightMargin: 10
Text {
id: bodyLabel
text: qsTr("Corps du message :")
font.bold: true
font.pixelSize: 20
}
TextArea {
id: bodyInput
anchors.top: bodyLabel.bottom
anchors.topMargin: 5
width: parent.width
height : 120
}
}
Le code source complet du projet est téléchargeable ici
Bonne lecture.
Aucun commentaire:
Enregistrer un commentaire