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