xref: /haiku/src/apps/haikudepot/ui/ToLatestUserUsageConditionsWindow.cpp (revision 4b347fccb28bb9f7242ba8bff1f49671247a7a75)
1 /*
2  * Copyright 2020, Andrew Lindesay <apl@lindesay.co.nz>.
3  * All rights reserved. Distributed under the terms of the MIT License.
4  */
5 
6 #include "ToLatestUserUsageConditionsWindow.h"
7 
8 #include <Alert.h>
9 #include <Autolock.h>
10 #include <AutoLocker.h>
11 #include <Button.h>
12 #include <Catalog.h>
13 #include <CheckBox.h>
14 #include <LayoutBuilder.h>
15 #include <Locker.h>
16 #include <SeparatorView.h>
17 
18 #include "AppUtils.h"
19 #include "LinkView.h"
20 #include "LocaleUtils.h"
21 #include "Logger.h"
22 #include "Model.h"
23 #include "UserUsageConditionsWindow.h"
24 #include "ServerHelper.h"
25 #include "WebAppInterface.h"
26 #include "TextView.h"
27 
28 #undef B_TRANSLATION_CONTEXT
29 #define B_TRANSLATION_CONTEXT "ToLatestUserUsageConditionsWindow"
30 
31 #define PLACEHOLDER_TEXT B_UTF8_ELLIPSIS
32 
33 #define KEY_USER_USAGE_CONDITIONS	"userUsageConditions"
34 
35 #define NO_PRIOR_MESSAGE_TEXT "The user [%Nickname%] has authenticated, but " \
36 	"before proceeding, you are required to agree to the most recent usage " \
37 	"conditions."
38 
39 #define PRIOR_MESSAGE_TEXT "The user \"%Nickname%\" has previously agreed to " \
40 	"usage conditions, but the usage conditions have been updated since. " \
41 	"The updated usage conditions now need to be agreed to."
42 
43 enum {
44 	MSG_AGREE =									'agre',
45 	MSG_AGREE_FAILED =							'agfa',
46 	MSG_AGREE_MINIMUM_AGE_TOGGLE =				'amat',
47 	MSG_AGREE_USER_USAGE_CONDITIONS_TOGGLE =	'auct'
48 };
49 
50 
ToLatestUserUsageConditionsWindow(BWindow * parent,Model & model,const UserDetail & userDetail)51 ToLatestUserUsageConditionsWindow::ToLatestUserUsageConditionsWindow(
52 	BWindow* parent,
53 	Model& model, const UserDetail& userDetail)
54 	:
55 	BWindow(BRect(), B_TRANSLATE("Update usage conditions"),
56 		B_FLOATING_WINDOW_LOOK, B_MODAL_SUBSET_WINDOW_FEEL,
57 		B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS
58 			| B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_NOT_CLOSABLE),
59 	fModel(model),
60 	fUserDetail(userDetail),
61 	fWorkerThread(-1),
62 	fQuitRequestedDuringWorkerThread(false),
63 	fMutableControlsEnabled(false)
64 {
65 	AddToSubset(parent);
66 	_InitUiControls();
67 
68 	// some layout magic happening here.  If the checkboxes are put directly
69 	// into the main vertical-group then the window tries to shrink to the
70 	// preferred size of the checkboxes.  To avoid this, a grid is used to house
71 	// the checkboxes and a fake extra grid column is added to the right of the
72 	// checkboxes which prevents the window from reducing in size to meet the
73 	// checkboxes.
74 
75 	BLayoutBuilder::Group<>(this, B_VERTICAL)
76 		.AddGrid()
77 			.Add(fMessageTextView, 0, 0, 2)
78 			.Add(fConfirmMinimumAgeCheckBox, 0, 1, 1)
79 			.Add(fConfirmUserUsageConditionsCheckBox, 0, 2, 1)
80 			.Add(fUserUsageConditionsLink, 0, 3, 2)
81 		.End()
82 		.Add(new BSeparatorView(B_HORIZONTAL))
83 			// rule off
84 		.AddGroup(B_HORIZONTAL, 1)
85 			.AddGlue()
86 			.Add(fLogoutButton)
87 			.Add(fAgreeButton)
88 		.End()
89 		.Add(fWorkerIndicator, 1)
90 		.SetInsets(B_USE_WINDOW_INSETS);
91 
92 	GetLayout()->SetExplicitMinSize(BSize(500, B_SIZE_UNSET));
93 	ResizeToPreferred();
94 	CenterOnScreen();
95 
96 	_FetchData();
97 		// start a new thread to pull down the user usage conditions data.
98 }
99 
100 
~ToLatestUserUsageConditionsWindow()101 ToLatestUserUsageConditionsWindow::~ToLatestUserUsageConditionsWindow()
102 {
103 	BAutolock locker(&fLock);
104 
105 	if (fWorkerThread >= 0)
106 		wait_for_thread(fWorkerThread, NULL);
107 }
108 
109 
110 void
_InitUiControls()111 ToLatestUserUsageConditionsWindow::_InitUiControls()
112 {
113 	fMessageTextView = new TextView("message text view");
114 	BString message;
115 	if (fUserDetail.Agreement().Code().IsEmpty())
116 		message = B_TRANSLATE(NO_PRIOR_MESSAGE_TEXT);
117 	else
118 		message = B_TRANSLATE(PRIOR_MESSAGE_TEXT);
119 	message.ReplaceAll("%Nickname%", fUserDetail.Nickname());
120 	fMessageTextView->SetText(message);
121 
122 	fConfirmMinimumAgeCheckBox = new BCheckBox("confirm minimum age",
123 		PLACEHOLDER_TEXT,
124 			// is filled in when the user usage conditions data is available
125 		new BMessage(MSG_AGREE_MINIMUM_AGE_TOGGLE));
126 
127 	fConfirmUserUsageConditionsCheckBox = new BCheckBox(
128 		"confirm usage conditions",
129 		B_TRANSLATE("I agree to the usage conditions"),
130 		new BMessage(MSG_AGREE_USER_USAGE_CONDITIONS_TOGGLE));
131 
132 	fUserUsageConditionsLink = new LinkView("usage conditions view",
133 		B_TRANSLATE("View the usage conditions"),
134 		new BMessage(MSG_VIEW_LATEST_USER_USAGE_CONDITIONS));
135 	fUserUsageConditionsLink->SetTarget(this);
136 
137 	fLogoutButton = new BButton("logout", B_TRANSLATE("Logout"),
138 		new BMessage(MSG_LOG_OUT));
139 	fAgreeButton = new BButton("agree", B_TRANSLATE("Agree"),
140 		new BMessage(MSG_AGREE));
141 
142 	fWorkerIndicator = new BarberPole("fetch data worker indicator");
143 	BSize workerIndicatorSize;
144 	workerIndicatorSize.SetHeight(20);
145 	fWorkerIndicator->SetExplicitSize(workerIndicatorSize);
146 
147 	fMutableControlsEnabled = false;
148 	_EnableMutableControls();
149 }
150 
151 
152 void
_EnableMutableControls()153 ToLatestUserUsageConditionsWindow::_EnableMutableControls()
154 {
155 	bool ageChecked = fConfirmMinimumAgeCheckBox->Value() == 1;
156 	bool conditionsChecked = fConfirmUserUsageConditionsCheckBox->Value() == 1;
157 	fUserUsageConditionsLink->SetEnabled(fMutableControlsEnabled);
158 	fAgreeButton->SetEnabled(fMutableControlsEnabled && ageChecked
159 		&& conditionsChecked);
160 	fLogoutButton->SetEnabled(fMutableControlsEnabled);
161 	fConfirmUserUsageConditionsCheckBox->SetEnabled(fMutableControlsEnabled);
162 	fConfirmMinimumAgeCheckBox->SetEnabled(fMutableControlsEnabled);
163 }
164 
165 
166 void
MessageReceived(BMessage * message)167 ToLatestUserUsageConditionsWindow::MessageReceived(BMessage* message)
168 {
169 	switch (message->what) {
170 		case MSG_USER_USAGE_CONDITIONS_DATA:
171 		{
172 			BMessage userUsageConditionsMessage;
173 			message->FindMessage(KEY_USER_USAGE_CONDITIONS,
174 				&userUsageConditionsMessage);
175 			UserUsageConditions userUsageConditions(
176 				&userUsageConditionsMessage);
177 			_DisplayData(userUsageConditions);
178 			fWorkerIndicator->Stop();
179 			break;
180 		}
181 		case MSG_LOG_OUT:
182 			_HandleLogout();
183 			break;
184 		case MSG_AGREE:
185 			_HandleAgree();
186 			break;
187 		case MSG_AGREE_FAILED:
188 			_HandleAgreeFailed();
189 			break;
190 		case MSG_VIEW_LATEST_USER_USAGE_CONDITIONS:
191 			_HandleViewUserUsageConditions();
192 			break;
193 		case MSG_AGREE_MINIMUM_AGE_TOGGLE:
194 		case MSG_AGREE_USER_USAGE_CONDITIONS_TOGGLE:
195 			_EnableMutableControls();
196 			break;
197 		default:
198 			BWindow::MessageReceived(message);
199 			break;
200 	}
201 }
202 
203 
204 bool
QuitRequested()205 ToLatestUserUsageConditionsWindow::QuitRequested()
206 {
207 	BAutolock locker(&fLock);
208 
209 	if (fWorkerThread >= 0) {
210 		if (Logger::IsDebugEnabled())
211 			HDINFO("quit requested while worker thread is operating -- will "
212 				"try again once the worker thread has completed");
213 		fQuitRequestedDuringWorkerThread = true;
214 		return false;
215 	}
216 
217 	return true;
218 }
219 
220 
221 void
_SetWorkerThread(thread_id thread)222 ToLatestUserUsageConditionsWindow::_SetWorkerThread(thread_id thread)
223 {
224 	if (thread >= 0) {
225 		fWorkerThread = thread;
226 		resume_thread(fWorkerThread);
227 	} else {
228 		fWorkerThread = -1;
229 		if (fQuitRequestedDuringWorkerThread)
230 			BMessenger(this).SendMessage(B_QUIT_REQUESTED);
231 		fQuitRequestedDuringWorkerThread = false;
232 	}
233 }
234 
235 
236 void
_SetWorkerThreadLocked(thread_id thread)237 ToLatestUserUsageConditionsWindow::_SetWorkerThreadLocked(thread_id thread)
238 {
239 	BAutolock locker(&fLock);
240 	_SetWorkerThread(thread);
241 }
242 
243 
244 /*!	This method is called on the main thread in order to initiate the background
245 	processing to obtain the user usage conditions data.  It will take
246 	responsibility for coordinating the creation of the thread and starting the
247 	thread etc...
248 */
249 
250 void
_FetchData()251 ToLatestUserUsageConditionsWindow::_FetchData()
252 {
253 	{
254 		BAutolock locker(&fLock);
255 		if (-1 != fWorkerThread) {
256 			debugger("illegal state - attempt to fetch, but thread in "
257 				"progress");
258 		}
259 	}
260 
261 	thread_id thread = spawn_thread(&_FetchDataThreadEntry,
262 		"Fetch usage conditions data", B_NORMAL_PRIORITY, this);
263 	if (thread >= 0) {
264 		fWorkerIndicator->Start();
265 		_SetWorkerThreadLocked(thread);
266 	} else {
267 		debugger("unable to start a thread to fetch the user usage "
268 			"conditions.");
269 	}
270 }
271 
272 
273 /*!	This method is called from the thread; it is
274 	the entry-point for the background processing to obtain the user usage
275 	conditions.
276 */
277 
278 /*static*/ int32
_FetchDataThreadEntry(void * data)279 ToLatestUserUsageConditionsWindow::_FetchDataThreadEntry(void* data)
280 {
281 	ToLatestUserUsageConditionsWindow* win
282 		= reinterpret_cast<ToLatestUserUsageConditionsWindow*>(data);
283 	win->_FetchDataPerform();
284 	return 0;
285 }
286 
287 
288 /*!	This method will perform the task of obtaining data about the user usage
289 	conditions.
290 */
291 
292 void
_FetchDataPerform()293 ToLatestUserUsageConditionsWindow::_FetchDataPerform()
294 {
295 	UserUsageConditions conditions;
296 	WebAppInterface* interface = fModel.GetWebAppInterface();
297 
298 	if (interface->RetrieveUserUsageConditions("", conditions) == B_OK) {
299 		BMessage userUsageConditionsMessage;
300 		conditions.Archive(&userUsageConditionsMessage, true);
301 		BMessage dataMessage(MSG_USER_USAGE_CONDITIONS_DATA);
302 		dataMessage.AddMessage(KEY_USER_USAGE_CONDITIONS,
303 			&userUsageConditionsMessage);
304 		BMessenger(this).SendMessage(&dataMessage);
305 	} else {
306 		_NotifyFetchProblem();
307 		BMessenger(this).SendMessage(B_QUIT_REQUESTED);
308 	}
309 
310 	_SetWorkerThreadLocked(-1);
311 }
312 
313 
314 void
_NotifyFetchProblem()315 ToLatestUserUsageConditionsWindow::_NotifyFetchProblem()
316 {
317 	AppUtils::NotifySimpleError(
318 		B_TRANSLATE("Usage conditions download problem"),
319 		B_TRANSLATE("An error has arisen downloading the usage "
320 			"conditions. Check the log for details and try again. "
321 			ALERT_MSG_LOGS_USER_GUIDE));
322 }
323 
324 
325 void
_Agree()326 ToLatestUserUsageConditionsWindow::_Agree()
327 {
328 	{
329 		BAutolock locker(&fLock);
330 		if (-1 != fWorkerThread) {
331 			debugger("illegal state - attempt to agree, but thread in "
332 				"progress");
333 		}
334 	}
335 
336 	fMutableControlsEnabled = false;
337 	_EnableMutableControls();
338 	thread_id thread = spawn_thread(&_AgreeThreadEntry,
339 		"Agree usage conditions", B_NORMAL_PRIORITY, this);
340 	if (thread >= 0) {
341 		fWorkerIndicator->Start();
342 		_SetWorkerThreadLocked(thread);
343 	} else {
344 		debugger("unable to start a thread to fetch the user usage "
345 			"conditions.");
346 	}
347 }
348 
349 
350 /*static*/ int32
_AgreeThreadEntry(void * data)351 ToLatestUserUsageConditionsWindow::_AgreeThreadEntry(void* data)
352 {
353 	ToLatestUserUsageConditionsWindow* win
354 		= reinterpret_cast<ToLatestUserUsageConditionsWindow*>(data);
355 	win->_AgreePerform();
356 	return 0;
357 }
358 
359 
360 void
_AgreePerform()361 ToLatestUserUsageConditionsWindow::_AgreePerform()
362 {
363 	BMessenger messenger(this);
364 	BMessage responsePayload;
365 	WebAppInterface* webApp = fModel.GetWebAppInterface();
366 	status_t result = webApp->AgreeUserUsageConditions(
367 		fUserUsageConditions.Code(), responsePayload);
368 
369 	if (result != B_OK) {
370 		ServerHelper::NotifyTransportError(result);
371 		messenger.SendMessage(MSG_AGREE_FAILED);
372 	} else {
373 		int32 errorCode = WebAppInterface::ErrorCodeFromResponse(responsePayload);
374 		if (errorCode == ERROR_CODE_NONE) {
375 			AppUtils::NotifySimpleError(
376 				B_TRANSLATE("Usage conditions agreed"),
377 				B_TRANSLATE("The current usage conditions have been agreed "
378 					"to."));
379 			messenger.SendMessage(B_QUIT_REQUESTED);
380 		}
381 		else {
382 			AutoLocker<BLocker> locker(fModel.Lock());
383 			ServerHelper::NotifyServerJsonRpcError(responsePayload);
384 			messenger.SendMessage(MSG_AGREE_FAILED);
385 		}
386 	}
387 
388 	_SetWorkerThreadLocked(-1);
389 }
390 
391 
392 void
_HandleAgreeFailed()393 ToLatestUserUsageConditionsWindow::_HandleAgreeFailed()
394 {
395 	fWorkerIndicator->Stop();
396 	fMutableControlsEnabled = true;
397 	_EnableMutableControls();
398 }
399 
400 
401 void
_DisplayData(const UserUsageConditions & userUsageConditions)402 ToLatestUserUsageConditionsWindow::_DisplayData(
403 	const UserUsageConditions& userUsageConditions)
404 {
405 	fUserUsageConditions = userUsageConditions;
406 	fConfirmMinimumAgeCheckBox->SetLabel(
407 		LocaleUtils::CreateTranslatedIAmMinimumAgeSlug(
408 			fUserUsageConditions.MinimumAge()));
409 	fMutableControlsEnabled = true;
410 	_EnableMutableControls();
411 }
412 
413 
414 void
_HandleViewUserUsageConditions()415 ToLatestUserUsageConditionsWindow::_HandleViewUserUsageConditions()
416 {
417 	if (!fUserUsageConditions.Code().IsEmpty()) {
418 		UserUsageConditionsWindow* window = new UserUsageConditionsWindow(
419 			fModel, fUserUsageConditions);
420 		window->Show();
421 	}
422 }
423 
424 void
_HandleLogout()425 ToLatestUserUsageConditionsWindow::_HandleLogout()
426 {
427 	AutoLocker<BLocker> locker(fModel.Lock());
428 	fModel.SetNickname("");
429 	BMessenger(this).SendMessage(B_QUIT_REQUESTED);
430 }
431 
432 
433 void
_HandleAgree()434 ToLatestUserUsageConditionsWindow::_HandleAgree()
435 {
436 	bool ageChecked = fConfirmMinimumAgeCheckBox->Value() == 1;
437 	bool conditionsChecked = fConfirmUserUsageConditionsCheckBox->Value() == 1;
438 
439 	// precondition that the user has checked both of the checkboxes.
440 	if (!ageChecked || !conditionsChecked)
441 		debugger("the user has not agreed to the age and conditions");
442 
443 	_Agree();
444 }
445