Merge lp:~rpadovani/reminders-app/multiple-accounts into lp:reminders-app

Proposed by Riccardo Padovani
Status: Merged
Approved by: Michael Zanetti
Approved revision: 59
Merged at revision: 62
Proposed branch: lp:~rpadovani/reminders-app/multiple-accounts
Merge into: lp:reminders-app
Diff against target: 409 lines (+184/-22)
11 files modified
src/app/CMakeLists.txt (+1/-0)
src/app/accountpreference.cpp (+39/-0)
src/app/accountpreference.h (+47/-0)
src/app/main.cpp (+7/-1)
src/app/qml/reminders.qml (+38/-15)
src/app/qml/ui/AccountSelectorPage.qml (+8/-1)
src/app/qml/ui/NotebooksPage.qml (+10/-1)
src/app/qml/ui/NotesPage.qml (+10/-1)
src/app/qml/ui/RemindersPage.qml (+10/-1)
src/plugin/Evernote/evernoteconnection.cpp (+9/-1)
src/plugin/Evernote/evernoteconnection.h (+5/-1)
To merge this branch: bzr merge lp:~rpadovani/reminders-app/multiple-accounts
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Approve
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Review via email: mp+205066@code.launchpad.net

Commit message

Added options to choose between multiple accounts

Description of the change

- User can switch account (bug #1274743)
- Auto-login with last account used
- If a user is choosing account and hasn't do login yet (is not switching account) back button is hidden (bug #1274742)

If a user removes is last account, default behavior is evokated (1 = autologin, > 1 dialog for choose account)

To post a comment you must log in.
55. By Riccardo Padovani

Added account preference

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :
Download full text (3.3 KiB)

Hey Riccardo,

nice. Congrats to your first real C++ branch :) Obviously there are a few issues to fix. But you know how picky I am. Don't blame yourself for the length of the list. I intentionally explained lots of things.

====

Hmm... Why do you save the username? That just makes it more complex.

Instead of saving the username into the config, I would just save the token. This way, on startup we can just load the token from the config, set it to EvernoteConnection and not bother dealing with AccountService at all.
Once the token is set, the username will be automatically refreshed inside the UserStore.

====

Here's a couple of other things I'd like to have changed:

134 CameraHelper helper;
135 view.engine()->rootContext()->setContextProperty("cameraHelper", &helper);
136
137 + view.engine()->rootContext()->setContextProperty("accountPreference", new AccountPreference());

This works, but please align it to look the same as the CameraHelper. Apart from the visual difference, there is a theoretical issue with your code too: If you create a variable without "new", it will be allocated on the stack and automatically deleted when the function ends. If you do "new" you will allocate the object on the heap and need to delete it yourself. Now, given that the program is removed from the memory when the main() ends, this is really a theoretical issue here and doesn't really hurt us in practice. But I hope you get the idea and see the benefit of keeping things consistent with the existing code.

===

199 + var accountName = accountPreference.accountName;
200 + if (accountName) {
201 + var i;
202 + for (i = 0; i < accounts.count; i++) {
203 + if (accounts.get(i, "displayName") == accountName) {
204 + accountService.objectHandle = accounts.get(i, "accountServiceHandle");
205 + }
206 + }
207 + }

As said above, this should be more like:

if (accountPreferences.token) {
    EvernoteConnection.token = accountPreferences.token;
} else {
  // Do the AccountService stuff...
}

====

372 +void EvernoteConnection::clearToken()
373 +{
374 + if (!EvernoteConnection::instance()->token().isEmpty()) {
375 + setToken(NULL);
376 + emit tokenChanged();
377 + }
378 +}

Heh, you're inside EvernoteConnection, you can directly access m_token instead of going through EvernoteConnection::instance()->token(). Also, never use NULL if a QString parameter is required. And one more: setToken() already does the emit tokenChanged(), you're emitting that twice now.

This function could (and should) be as short as:

void EvernoteConnection::clearToken()
{
    m_token.clear();
    emit tokenChanged();
}

or:

void EvernoteConnection::clearToken()
{
    setToken(QString());
}

I guess the latter one is the better one.

====

56 \ No newline at end of file

Make sure C++ code files always end with a newline. While this is again a theoretical issue in our case, it breaks compilation with some compilers, e.g. on Solaris.

====

283 + text: i18n.tr("Accounts")
284 + iconName: "contacts-app-symbolic"
285 + visible: accounts.count
286 + onTriggered: {
287 + openAccountPage(true);
288 + }
289 + }
290 +
291 + ToolbarButton {

I personally don't think its good to have the button in the too...

Read more...

review: Needs Fixing
56. By Riccardo Padovani

Fixes some problems

Revision history for this message
Riccardo Padovani (rpadovani) wrote :
Download full text (4.2 KiB)

> Hey Riccardo,
>
> nice. Congrats to your first real C++ branch :) Obviously there are a few
> issues to fix. But you know how picky I am. Don't blame yourself for the
> length of the list. I intentionally explained lots of things.
>

Thanks for your support and explanations :-)

> ====
>
> Hmm... Why do you save the username? That just makes it more complex.
>
> Instead of saving the username into the config, I would just save the token.
> This way, on startup we can just load the token from the config, set it to
> EvernoteConnection and not bother dealing with AccountService at all.
> Once the token is set, the username will be automatically refreshed inside the
> UserStore.
>

Well, I thinked is not secure to manage directly accounts without use AccountService, because we save accessToken, but if you say it's ok, I do in this way

> ====
>
> Here's a couple of other things I'd like to have changed:
>
> 134 CameraHelper helper;
> 135 view.engine()->rootContext()->setContextProperty("cameraHelper",
> &helper);
> 136
> 137 +
> view.engine()->rootContext()->setContextProperty("accountPreference", new
> AccountPreference());
>
> This works, but please align it to look the same as the CameraHelper. Apart
> from the visual difference, there is a theoretical issue with your code too:
> If you create a variable without "new", it will be allocated on the stack and
> automatically deleted when the function ends. If you do "new" you will
> allocate the object on the heap and need to delete it yourself. Now, given
> that the program is removed from the memory when the main() ends, this is
> really a theoretical issue here and doesn't really hurt us in practice. But I
> hope you get the idea and see the benefit of keeping things consistent with
> the existing code.
>

Done, thanks for explanation!

> ===
[...]
> As said above, this should be more like:
>
> if (accountPreferences.token) {
> EvernoteConnection.token = accountPreferences.token;
> } else {
> // Do the AccountService stuff...
> }

Ok, I implemented that, but now if accountPreferences.accountToken is not a valid token we have an authentication error, because there is no time to intercept UserStore.username, it is set after else is executed.
So I have no idea on how check if accountPreferences.token is not valid.
This isn't an uncommon user case: login with an account, user removes the account from Online Account, accountToken is no more valid.

> ====
>
> 372 +void EvernoteConnection::clearToken()
> 373 +{
> 374 + if (!EvernoteConnection::instance()->token().isEmpty()) {
> 375 + setToken(NULL);
> 376 + emit tokenChanged();
> 377 + }
> 378 +}
>
> Heh, you're inside EvernoteConnection, you can directly access m_token instead
> of going through EvernoteConnection::instance()->token(). Also, never use NULL
> if a QString parameter is required. And one more: setToken() already does the
> emit tokenChanged(), you're emitting that twice now.
>
> This function could (and should) be as short as:
>
> void EvernoteConnection::clearToken()
> {
> m_token.clear();
> emit tokenChanged();
> }
>
> or:
>
> void EvernoteConnection::clearTok...

Read more...

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

>
> Ok, I implemented that, but now if accountPreferences.accountToken is not a
> valid token we have an authentication error, because there is no time to
> intercept UserStore.username, it is set after else is executed.
> So I have no idea on how check if accountPreferences.token is not valid.
> This isn't an uncommon user case: login with an account, user removes the
> account from Online Account, accountToken is no more valid.

actually you do have a good point here... I didn't think of that. Maybe your previous version was indeed better. Sorry for that.

Thanks for fixing the rest. Lets hear the other's opinion on where the logout button should be placed.

Revision history for this message
David Planella (dpm) wrote :

> I personally don't think its good to have the button in the toolbar all the time,
> given that in practice you will never ever use it :)

> How about putting it into the HUD? David, Dani, Alan, what's your opinion on this?

I agree with Michael: I don't think we should clutter the toolbar with actions that most users will not use. However, I'm not too sure about putting them in the HUD. I personally think it should be part of the Online Accounts login workflow instead, but the best persons to ask should probably be Dani or mardy.

57. By Riccardo Padovani

Reverted changes to how the username is saved

58. By Riccardo Padovani

Merged from trunk

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
59. By Riccardo Padovani

Added check on numbers of accounts

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

ack!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/app/CMakeLists.txt'
2--- src/app/CMakeLists.txt 2014-01-27 20:27:37 +0000
3+++ src/app/CMakeLists.txt 2014-02-12 15:44:15 +0000
4@@ -3,6 +3,7 @@
5 set(reminders_SRCS
6 main.cpp
7 camerahelper.cpp
8+ accountpreference.cpp
9 ${QML_SRCS}
10 )
11
12
13=== added file 'src/app/accountpreference.cpp'
14--- src/app/accountpreference.cpp 1970-01-01 00:00:00 +0000
15+++ src/app/accountpreference.cpp 2014-02-12 15:44:15 +0000
16@@ -0,0 +1,39 @@
17+/*
18+ * Copyright: 2014 Canonical, Ltd
19+ *
20+ * This file is part of reminders
21+ *
22+ * reminders is free software: you can redistribute it and/or modify
23+ * it under the terms of the GNU General Public License as published by
24+ * the Free Software Foundation, either version 3 of the License, or
25+ * (at your option) any later version.
26+ *
27+ * reminders is distributed in the hope that it will be useful,
28+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
29+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30+ * GNU General Public License for more details.
31+ *
32+ * You should have received a copy of the GNU General Public License
33+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
34+ *
35+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
36+ * Riccardo Padovani <rpadovani@ubuntu.com>
37+ */
38+#include "accountpreference.h"
39+
40+AccountPreference::AccountPreference(QObject *parent): QObject(parent),
41+ m_settings(QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/com.ubuntu.reminders/reminders.conf", QSettings::IniFormat)
42+{
43+
44+}
45+
46+QString AccountPreference::accountName() const
47+{
48+ return m_settings.value("accountName").toString();
49+}
50+
51+void AccountPreference::setAccountName(const QString &accountName)
52+{
53+ m_settings.setValue("accountName", accountName);
54+ emit accountNameChanged();
55+}
56
57=== added file 'src/app/accountpreference.h'
58--- src/app/accountpreference.h 1970-01-01 00:00:00 +0000
59+++ src/app/accountpreference.h 2014-02-12 15:44:15 +0000
60@@ -0,0 +1,47 @@
61+/*
62+ * Copyright: 2014 Canonical, Ltd
63+ *
64+ * This file is part of reminders
65+ *
66+ * reminders is free software: you can redistribute it and/or modify
67+ * it under the terms of the GNU General Public License as published by
68+ * the Free Software Foundation, either version 3 of the License, or
69+ * (at your option) any later version.
70+ *
71+ * reminders is distributed in the hope that it will be useful,
72+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
73+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
74+ * GNU General Public License for more details.
75+ *
76+ * You should have received a copy of the GNU General Public License
77+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
78+ *
79+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
80+ * Riccardo Padovani <rpadovani@ubuntu.com>
81+ */
82+
83+#ifndef ACCOUNTPREFERENCE_H
84+#define ACCOUNTPREFERENCE_H
85+
86+#include <QSettings>
87+#include <QStandardPaths>
88+#include <QObject>
89+#include <QDebug>
90+
91+class AccountPreference: public QObject
92+{
93+ Q_OBJECT
94+ Q_PROPERTY(QString accountName READ accountName WRITE setAccountName NOTIFY accountNameChanged)
95+public:
96+ AccountPreference(QObject *parent = 0);
97+ QString accountName() const;
98+ void setAccountName(const QString &accountName);
99+
100+signals:
101+ void accountNameChanged();
102+
103+private:
104+ QSettings m_settings;
105+};
106+
107+#endif
108
109=== modified file 'src/app/main.cpp'
110--- src/app/main.cpp 2014-01-27 21:40:42 +0000
111+++ src/app/main.cpp 2014-02-12 15:44:15 +0000
112@@ -1,5 +1,5 @@
113 /*
114- * Copyright: 2013 Canonical, Ltd
115+ * Copyright: 2013 - 2014 Canonical, Ltd
116 *
117 * This file is part of reminders
118 *
119@@ -17,9 +17,11 @@
120 * along with this program. If not, see <http://www.gnu.org/licenses/>.
121 *
122 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
123+ * Riccardo Padovani <rpadovani@ubuntu.com>
124 */
125
126 #include "camerahelper.h"
127+#include "accountpreference.h"
128
129 #include <QtGui/QGuiApplication>
130 #include <QtQuick/QQuickView>
131@@ -55,6 +57,10 @@
132 CameraHelper helper;
133 view.engine()->rootContext()->setContextProperty("cameraHelper", &helper);
134
135+ // Set up account preferences
136+ AccountPreference preferences;
137+ view.engine()->rootContext()->setContextProperty("accountPreference", &preferences);
138+
139 // load the qml file
140 view.setSource(QUrl::fromLocalFile("qml/reminders.qml"));
141
142
143=== modified file 'src/app/qml/reminders.qml'
144--- src/app/qml/reminders.qml 2014-01-31 13:03:49 +0000
145+++ src/app/qml/reminders.qml 2014-02-12 15:44:15 +0000
146@@ -1,5 +1,5 @@
147 /*
148- * Copyright: 2013 Canonical, Ltd
149+ * Copyright: 2013 - 2014 Canonical, Ltd
150 *
151 * This file is part of reminders
152 *
153@@ -47,6 +47,18 @@
154 // Temporary background color. This can be changed to other suitable backgrounds when we get official mockup designs
155 backgroundColor: UbuntuColors.coolGrey
156
157+ property var accountPage;
158+
159+ function openAccountPage(isChangingAccount) {
160+ if (accountPage) {
161+ accountPage.destroy(100)
162+ }
163+ var component = Qt.createComponent(Qt.resolvedUrl("ui/AccountSelectorPage.qml"));
164+ accountPage = component.createObject(root, {accounts: accounts, isChangingAccount: isChangingAccount});
165+ accountPage.accountSelected.connect(function(handle) { accountService.objectHandle = handle; pagestack.pop(); });
166+ pagestack.push(accountPage);
167+ }
168+
169 AccountServiceModel {
170 id: accounts
171 service: "evernote"
172@@ -56,7 +68,9 @@
173 id: accountService
174 onObjectHandleChanged: authenticate(null);
175 onAuthenticated: {
176- console.log("Access token is " + reply.AccessToken)
177+ if (EvernoteConnection.token && EvernoteConnection.token != reply.AccessToken) {
178+ EvernoteConnection.clearToken();
179+ }
180 EvernoteConnection.token = reply.AccessToken;
181 }
182 onAuthenticationError: {
183@@ -67,25 +81,34 @@
184 Component.onCompleted: {
185 pagestack.push(rootTabs)
186 print("got accounts:", accounts.count)
187- switch (accounts.count) {
188- case 0:
189- print("No account available! Please setup an account in the system settings");
190- break;
191- case 1:
192- accountService.objectHandle = accounts.get(0, "accountServiceHandle");
193- break;
194- default:
195- var component = Qt.createComponent(Qt.resolvedUrl("ui/AccountSelectorPage.qml"));
196- var page = component.createObject(root, {accounts: accounts});
197- page.accountSelected.connect(function(handle) { accountService.objectHandle = handle; pagestack.pop(); });
198- pagestack.push(page);
199+ var accountName = accountPreference.accountName;
200+ if (accountName) {
201+ var i;
202+ for (i = 0; i < accounts.count; i++) {
203+ if (accounts.get(i, "displayName") == accountName) {
204+ accountService.objectHandle = accounts.get(i, "accountServiceHandle");
205+ }
206+ }
207+ }
208+ if (!accountService.objectHandle) {
209+ switch (accounts.count) {
210+ case 0:
211+ print("No account available! Please setup an account in the system settings");
212+ break;
213+ case 1:
214+ accountService.objectHandle = accounts.get(0, "accountServiceHandle");
215+ break;
216+ default:
217+ openAccountPage(false);
218+ }
219 }
220 }
221
222 Connections {
223 target: UserStore
224 onUsernameChanged: {
225- print("Logged in as user:", UserStore.username)
226+ print("Logged in as user:", UserStore.username);
227+ accountPreference.accountName = UserStore.username;
228 }
229 }
230
231
232=== modified file 'src/app/qml/ui/AccountSelectorPage.qml'
233--- src/app/qml/ui/AccountSelectorPage.qml 2014-02-03 11:25:41 +0000
234+++ src/app/qml/ui/AccountSelectorPage.qml 2014-02-12 15:44:15 +0000
235@@ -1,5 +1,5 @@
236 /*
237- * Copyright: 2013 Canonical, Ltd
238+ * Copyright: 2013 - 2014 Canonical, Ltd
239 *
240 * This file is part of reminders
241 *
242@@ -27,6 +27,7 @@
243 title: "Select Evernote account"
244
245 property alias accounts: listView.model
246+ property bool isChangingAccount
247
248 signal accountSelected(var handle)
249
250@@ -39,6 +40,7 @@
251 width: parent.width
252 height: units.gu(10)
253 model: accounts
254+ currentIndex: -1
255
256 delegate: Standard {
257 text: displayName
258@@ -49,4 +51,9 @@
259 }
260 }
261 }
262+
263+ tools: ToolbarItems {
264+ locked: !isChangingAccount
265+ opened: isChangingAccount
266+ }
267 }
268
269=== modified file 'src/app/qml/ui/NotebooksPage.qml'
270--- src/app/qml/ui/NotebooksPage.qml 2014-01-28 08:31:37 +0000
271+++ src/app/qml/ui/NotebooksPage.qml 2014-02-12 15:44:15 +0000
272@@ -1,5 +1,5 @@
273 /*
274- * Copyright: 2013 Canonical, Ltd
275+ * Copyright: 2013 - 2014 Canonical, Ltd
276 *
277 * This file is part of reminders
278 *
279@@ -43,6 +43,15 @@
280 ToolbarSpacer { }
281
282 ToolbarButton {
283+ text: i18n.tr("Accounts")
284+ iconName: "contacts-app-symbolic"
285+ visible: accounts.count > 1
286+ onTriggered: {
287+ openAccountPage(true);
288+ }
289+ }
290+
291+ ToolbarButton {
292 text: i18n.tr("Add notebook")
293 iconName: "add"
294 onTriggered: {
295
296=== modified file 'src/app/qml/ui/NotesPage.qml'
297--- src/app/qml/ui/NotesPage.qml 2014-02-05 13:25:44 +0000
298+++ src/app/qml/ui/NotesPage.qml 2014-02-12 15:44:15 +0000
299@@ -1,5 +1,5 @@
300 /*
301- * Copyright: 2013 Canonical, Ltd
302+ * Copyright: 2013 - 2014 Canonical, Ltd
303 *
304 * This file is part of reminders
305 *
306@@ -46,6 +46,15 @@
307 ToolbarSpacer { }
308
309 ToolbarButton {
310+ text: i18n.tr("Accounts")
311+ iconName: "contacts-app-symbolic"
312+ visible: accounts.count > 1
313+ onTriggered: {
314+ openAccountPage(true);
315+ }
316+ }
317+
318+ ToolbarButton {
319 text: i18n.tr("Add note")
320 iconName: "add"
321 onTriggered: {
322
323=== modified file 'src/app/qml/ui/RemindersPage.qml'
324--- src/app/qml/ui/RemindersPage.qml 2014-02-05 17:26:54 +0000
325+++ src/app/qml/ui/RemindersPage.qml 2014-02-12 15:44:15 +0000
326@@ -1,5 +1,5 @@
327 /*
328- * Copyright: 2013 Canonical, Ltd
329+ * Copyright: 2013 - 2014 Canonical, Ltd
330 *
331 * This file is part of reminders
332 *
333@@ -37,6 +37,15 @@
334 ToolbarSpacer { }
335
336 ToolbarButton {
337+ text: i18n.tr("Accounts")
338+ iconName: "contacts-app-symbolic"
339+ visible: accounts.count > 1
340+ onTriggered: {
341+ openAccountPage(true);
342+ }
343+ }
344+
345+ ToolbarButton {
346 text: i18n.tr("Add reminder")
347 iconName: "add"
348 onTriggered: {
349
350=== modified file 'src/plugin/Evernote/evernoteconnection.cpp'
351--- src/plugin/Evernote/evernoteconnection.cpp 2014-01-30 11:23:28 +0000
352+++ src/plugin/Evernote/evernoteconnection.cpp 2014-02-12 15:44:15 +0000
353@@ -1,5 +1,5 @@
354 /*
355- * Copyright: 2013 Canonical, Ltd
356+ * Copyright: 2013 - 2014 Canonical, Ltd
357 *
358 * This file is part of reminders
359 *
360@@ -16,6 +16,7 @@
361 * along with this program. If not, see <http://www.gnu.org/licenses/>.
362 *
363 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
364+ * Riccardo Padovani <rpadovani@ubuntu.com>
365 */
366
367 #include "evernoteconnection.h"
368@@ -163,6 +164,13 @@
369 }
370 }
371
372+void EvernoteConnection::clearToken()
373+{
374+ if (!EvernoteConnection::instance()->token().isEmpty()) {
375+ setToken(QString());
376+ }
377+}
378+
379 void EvernoteConnection::enqueue(EvernoteJob *job)
380 {
381 connect(job, &EvernoteJob::finished, this, &EvernoteConnection::startNextJob);
382
383=== modified file 'src/plugin/Evernote/evernoteconnection.h'
384--- src/plugin/Evernote/evernoteconnection.h 2014-01-27 13:00:41 +0000
385+++ src/plugin/Evernote/evernoteconnection.h 2014-02-12 15:44:15 +0000
386@@ -1,5 +1,5 @@
387 /*
388- * Copyright: 2013 Canonical, Ltd
389+ * Copyright: 2013 - 2014 Canonical, Ltd
390 *
391 * This file is part of reminders
392 *
393@@ -16,6 +16,7 @@
394 * along with this program. If not, see <http://www.gnu.org/licenses/>.
395 *
396 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
397+ * Riccardo Padovani <rpadovani@ubuntu.com>
398 */
399
400 #ifndef EVERNOTECONNECTION_H
401@@ -64,6 +65,9 @@
402
403 void enqueue(EvernoteJob *job);
404
405+public slots:
406+ void clearToken();
407+
408 signals:
409 void tokenChanged();
410

Subscribers

People subscribed via source and target branches