forked from medic/cht-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathandroid-api.service.ts
More file actions
237 lines (208 loc) · 7.1 KB
/
android-api.service.ts
File metadata and controls
237 lines (208 loc) · 7.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import { Injectable, NgZone } from '@angular/core';
import { AndroidAppLauncherService } from '@mm-services/android-app-launcher.service';
import { GeolocationService } from '@mm-services/geolocation.service';
import { MRDTService } from '@mm-services/mrdt.service';
import { SessionService } from '@mm-services/session.service';
import { NavigationService } from '@mm-services/navigation.service';
import { TasksNotificationService } from '@mm-services/task-notifications.service';
/**
* An API to provide integration with the medic-android app.
*
* This service must maintain backwards compatibility as we cannot
* guarantee the all clients will be on a recent version of the app.
*/
@Injectable({
providedIn: 'root',
})
export class AndroidApiService {
constructor(
private androidAppLauncherService:AndroidAppLauncherService,
private geolocationService:GeolocationService,
private mrdtService:MRDTService,
private sessionService:SessionService,
private zone:NgZone,
private navigationService:NavigationService,
private readonly tasksNotificationService: TasksNotificationService,
) { }
private runInZone(property:string, args:any[]=[]) {
if (!this[property] || typeof this[property] !== 'function') {
return;
}
if (NgZone.isInAngularZone()) {
return this[property](...args);
}
return this.zone.run(() => this[property](...args));
}
/**
* Close all select2 dropdowns
* @return {boolean} `true` if any select2s were closed.
*/
private closeSelect2($container) {
// If there are any select2 dropdowns open, close them. The select
// boxes are closed while they are checked - this saves us having to
// iterate over them twice
let closed = false;
$container
.find('select.select2-hidden-accessible')
.each(function() {
// @ts-expect-error Intentionally referencing shadowed `this` variable.
const elem = <any>$(this);
if (elem.select2('isOpen')) {
elem.select2('close');
closed = true;
}
});
return closed;
}
/**
* Close the highest-priority dropdown within a particular container.
* @return {boolean} `true` if a dropdown was closed; `false` otherwise.
*/
private closeDropdownsIn($container) {
if (this.closeSelect2($container)) {
return true;
}
// todo: this probably won't work because dropdowns are now angular directives!
// If there is a dropdown menu open, close it
const $dropdown = $container.find('.filter.dropdown.open:visible');
if ($dropdown.length) {
$dropdown.removeClass('open');
return true;
}
// On an Enketo form, go to the previous page (if there is one)
if ($container.find('.enketo .btn.previous-page:visible:enabled:not(".disabled")').length) {
window.history.back();
return true;
}
return false;
}
/*
* Find the modal with highest z-index, and ignore the rest
*/
private closeTopModal($modals) {
let $topModal;
$modals.each(function() {
// @ts-expect-error Intentionally referencing shadowed `this` variable.
const $modal = $(this);
if (!$topModal) {
$topModal = $modal;
return;
}
if ($topModal.css('z-index') <= $modal.css('z-index')) {
$topModal = $modal;
}
});
if (!this.closeDropdownsIn($topModal)) {
// Try to close by clicking modal's top-right `X` or `[ Cancel ]`
// button.
$topModal
.find('.btn.cancel:visible:not(:disabled), button.close:visible:not(:disabled)')
.click();
}
}
private closeUserInterfaceElements() {
// If there's a modal open, close any dropdowns inside it, or try to close the modal itself.
const $modals = $('.modal:visible');
if ($modals.length) {
this.closeTopModal($modals);
return true;
}
// If the hotdog hamburger options menu is open, close it
const $optionsMenu = $('.dropdown.options.open');
if ($optionsMenu.length) {
$optionsMenu.removeClass('open');
return true;
}
if (this.closeDropdownsIn($('body'))) {
return true;
}
// Nothing to close.
return false;
}
/**
* Handle hardware back-button when it's pressed inside the Android app.
*
* This function is intended to always return 'true' and not give back the control of back-button to the Android app.
* This prevents Android from minimizing the app when the primary tab is active. Ref: #6698.
*
* Warning: If this function returns a falsy value, the Android app will handle the back-button
* and possibly minimize the app.
*
* @return {boolean}
*/
back() {
if (this.closeUserInterfaceElements()) {
return true;
}
if (this.navigationService.goBack()) {
return true;
}
this.navigationService.goToPrimaryTab();
// Not giving back the control to the Android app so it doesn't minimize the app. Ref: #6698
return true;
}
/**
* Kill the session.
*/
logout() {
this.sessionService.logout();
}
/**
* Gets notifcations for tasks
* returns {Promise} A promise that resolves to Notification[].
*/
taskNotifications() {
return this.tasksNotificationService.get();
}
/**
* Handle the response from the MRDT app
* @param response The stringified JSON response from the MRDT app.
*/
mrdtResponse(response) {
try {
this.mrdtService.respond(JSON.parse(response));
} catch (e) {
return console.error(
new Error(`Unable to parse JSON response from android app: "${response}", error message: "${e.message}"`)
);
}
}
/**
* Handle the response from the MRDT app
* @param response The stringified JSON response from the MRDT app.
*/
mrdtTimeTakenResponse(response) {
try {
this.mrdtService.respondTimeTaken(JSON.parse(response));
} catch (e) {
return console.error(
new Error(`Unable to parse JSON response from android app: "${response}", error message: "${e.message}"`)
);
}
}
smsStatusUpdate(id, destination, content, status, detail) {
console.debug('smsStatusUpdate() :: ' +
' id=' + id +
', destination=' + destination +
', content=' + content +
', status=' + status +
', detail=' + detail);
// TODO storing status updates for SMS should be implemented as part of #4812
}
locationPermissionRequestResolve() {
this.geolocationService.permissionRequestResolved();
}
resolveCHTExternalAppResponse(response) {
this.androidAppLauncherService.resolveAndroidAppResponse(response);
}
v1 = {
back: () => this.runInZone('back'),
logout: () => this.runInZone('logout'),
mrdtResponse: (...args) => this.runInZone('mrdtResponse', args),
mrdtTimeTakenResponse: (...args) => this.runInZone('mrdtTimeTakenResponse', args),
smsStatusUpdate: (...args) => this.runInZone('smsStatusUpdate', args),
locationPermissionRequestResolved: () => this.runInZone('locationPermissionRequestResolve'),
resolveCHTExternalAppResponse: (...args) => this.runInZone('resolveCHTExternalAppResponse', args),
taskNotifications: () => this.runInZone('taskNotifications'),
};
}