2019-09-18 15:29:09 -05:00
|
|
|
package pl.szczodrzynski.edziennik;
|
|
|
|
|
|
|
|
import android.app.IntentService;
|
|
|
|
import android.app.Notification;
|
|
|
|
import android.app.NotificationChannel;
|
|
|
|
import android.app.NotificationManager;
|
|
|
|
import android.app.PendingIntent;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.os.Build;
|
2019-10-18 15:12:40 -05:00
|
|
|
import android.util.Log;
|
|
|
|
|
2019-09-18 15:29:09 -05:00
|
|
|
import androidx.core.app.NotificationCompat;
|
|
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.List;
|
|
|
|
|
2019-09-28 08:17:03 -05:00
|
|
|
import pl.szczodrzynski.edziennik.data.db.modules.profiles.ProfileFull;
|
2019-09-18 15:29:09 -05:00
|
|
|
import pl.szczodrzynski.edziennik.receivers.BootReceiver;
|
2019-10-18 15:12:40 -05:00
|
|
|
import pl.szczodrzynski.edziennik.utils.models.Date;
|
|
|
|
import pl.szczodrzynski.edziennik.utils.models.Time;
|
2019-09-18 15:29:09 -05:00
|
|
|
|
|
|
|
import static androidx.core.app.NotificationCompat.PRIORITY_DEFAULT;
|
|
|
|
import static androidx.core.app.NotificationCompat.PRIORITY_MAX;
|
|
|
|
|
|
|
|
public class Notifier {
|
|
|
|
|
|
|
|
private static final String TAG = "Notifier";
|
|
|
|
public static final int ID_GET_DATA = 1337000;
|
|
|
|
public static final int ID_GET_DATA_ERROR = 1337001;
|
|
|
|
private static String CHANNEL_GET_DATA_NAME;
|
|
|
|
private static String CHANNEL_GET_DATA_DESC;
|
|
|
|
private static final String GROUP_KEY_GET_DATA = "pl.szczodrzynski.edziennik.GET_DATA";
|
|
|
|
|
2019-10-18 15:12:40 -05:00
|
|
|
public static final int ID_NOTIFICATIONS = 1337002;
|
|
|
|
public static String CHANNEL_NOTIFICATIONS_NAME;
|
|
|
|
public static String CHANNEL_NOTIFICATIONS_DESC;
|
2019-09-18 15:29:09 -05:00
|
|
|
public static final String GROUP_KEY_NOTIFICATIONS = "pl.szczodrzynski.edziennik.NOTIFICATIONS";
|
|
|
|
|
2019-10-18 15:12:40 -05:00
|
|
|
public static final int ID_NOTIFICATIONS_QUIET = 1337002;
|
|
|
|
public static String CHANNEL_NOTIFICATIONS_QUIET_NAME;
|
|
|
|
public static String CHANNEL_NOTIFICATIONS_QUIET_DESC;
|
2019-09-18 15:29:09 -05:00
|
|
|
public static final String GROUP_KEY_NOTIFICATIONS_QUIET = "pl.szczodrzynski.edziennik.NOTIFICATIONS_QUIET";
|
|
|
|
|
|
|
|
private static final int ID_UPDATES = 1337003;
|
|
|
|
private static String CHANNEL_UPDATES_NAME;
|
|
|
|
private static String CHANNEL_UPDATES_DESC;
|
|
|
|
private static final String GROUP_KEY_UPDATES = "pl.szczodrzynski.edziennik.UPDATES";
|
|
|
|
|
|
|
|
private App app;
|
2019-10-18 15:12:40 -05:00
|
|
|
public NotificationManager notificationManager;
|
2019-09-18 15:29:09 -05:00
|
|
|
private NotificationCompat.Builder getDataNotificationBuilder;
|
2019-10-18 15:12:40 -05:00
|
|
|
public int notificationColor;
|
2019-09-18 15:29:09 -05:00
|
|
|
|
|
|
|
Notifier(App _app) {
|
|
|
|
this.app = _app;
|
|
|
|
|
|
|
|
CHANNEL_GET_DATA_NAME = app.getString(R.string.notification_channel_get_data_name);
|
|
|
|
CHANNEL_GET_DATA_DESC = app.getString(R.string.notification_channel_get_data_desc);
|
|
|
|
CHANNEL_NOTIFICATIONS_NAME = app.getString(R.string.notification_channel_notifications_name);
|
|
|
|
CHANNEL_NOTIFICATIONS_DESC = app.getString(R.string.notification_channel_notifications_desc);
|
|
|
|
CHANNEL_NOTIFICATIONS_QUIET_NAME = app.getString(R.string.notification_channel_notifications_quiet_name);
|
|
|
|
CHANNEL_NOTIFICATIONS_QUIET_DESC = app.getString(R.string.notification_channel_notifications_quiet_desc);
|
|
|
|
CHANNEL_UPDATES_NAME = app.getString(R.string.notification_channel_updates_name);
|
|
|
|
CHANNEL_UPDATES_DESC = app.getString(R.string.notification_channel_updates_desc);
|
|
|
|
|
|
|
|
notificationColor = ContextCompat.getColor(app.getContext(), R.color.colorPrimary);
|
|
|
|
notificationManager = (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
2019-10-01 14:27:09 -05:00
|
|
|
NotificationChannel channelGetData = new NotificationChannel(GROUP_KEY_GET_DATA, CHANNEL_GET_DATA_NAME, NotificationManager.IMPORTANCE_MIN);
|
2019-09-18 15:29:09 -05:00
|
|
|
channelGetData.setDescription(CHANNEL_GET_DATA_DESC);
|
|
|
|
notificationManager.createNotificationChannel(channelGetData);
|
|
|
|
|
|
|
|
NotificationChannel channelNotifications = new NotificationChannel(GROUP_KEY_NOTIFICATIONS, CHANNEL_NOTIFICATIONS_NAME, NotificationManager.IMPORTANCE_HIGH);
|
|
|
|
channelNotifications.setDescription(CHANNEL_NOTIFICATIONS_DESC);
|
|
|
|
channelNotifications.enableLights(true);
|
|
|
|
channelNotifications.setLightColor(notificationColor);
|
|
|
|
notificationManager.createNotificationChannel(channelNotifications);
|
|
|
|
|
|
|
|
NotificationChannel channelNotificationsQuiet = new NotificationChannel(GROUP_KEY_NOTIFICATIONS_QUIET, CHANNEL_NOTIFICATIONS_QUIET_NAME, NotificationManager.IMPORTANCE_DEFAULT);
|
|
|
|
channelNotificationsQuiet.setDescription(CHANNEL_NOTIFICATIONS_QUIET_DESC);
|
|
|
|
channelNotificationsQuiet.setSound(null, null);
|
|
|
|
channelNotificationsQuiet.enableVibration(false);
|
|
|
|
notificationManager.createNotificationChannel(channelNotificationsQuiet);
|
|
|
|
|
|
|
|
NotificationChannel channelUpdates = new NotificationChannel(GROUP_KEY_UPDATES, CHANNEL_UPDATES_NAME, NotificationManager.IMPORTANCE_HIGH);
|
|
|
|
channelUpdates.setDescription(CHANNEL_UPDATES_DESC);
|
|
|
|
notificationManager.createNotificationChannel(channelUpdates);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean shouldBeQuiet() {
|
|
|
|
long now = Time.getNow().getInMillis();
|
|
|
|
long start = app.appConfig.quietHoursStart;
|
|
|
|
long end = app.appConfig.quietHoursEnd;
|
|
|
|
if (start > end) {
|
|
|
|
end += 1000 * 60 * 60 * 24;
|
|
|
|
//Log.d(TAG, "Night passing");
|
|
|
|
}
|
|
|
|
if (start > now) {
|
|
|
|
now += 1000 * 60 * 60 * 24;
|
|
|
|
//Log.d(TAG, "Now is smaller");
|
|
|
|
}
|
|
|
|
//Log.d(TAG, "Start is "+start+", now is "+now+", end is "+end);
|
|
|
|
return app.appConfig.quietHoursStart > 0 && now >= start && now <= end;
|
|
|
|
}
|
|
|
|
|
2019-10-18 15:12:40 -05:00
|
|
|
public int getNotificationDefaults() {
|
2019-09-18 15:29:09 -05:00
|
|
|
return (shouldBeQuiet() ? 0 : Notification.DEFAULT_ALL);
|
|
|
|
}
|
2019-10-18 15:12:40 -05:00
|
|
|
public String getNotificationGroup() {
|
2019-09-18 15:29:09 -05:00
|
|
|
return shouldBeQuiet() ? GROUP_KEY_NOTIFICATIONS_QUIET : GROUP_KEY_NOTIFICATIONS;
|
|
|
|
}
|
2019-10-18 15:12:40 -05:00
|
|
|
public int getNotificationPriority() {
|
2019-09-18 15:29:09 -05:00
|
|
|
return shouldBeQuiet() ? PRIORITY_DEFAULT : PRIORITY_MAX;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* _____ _ _____ _
|
|
|
|
| __ \ | | / ____| | |
|
|
|
|
| | | | __ _| |_ __ _ | | __ ___| |_
|
|
|
|
| | | |/ _` | __/ _` | | | |_ |/ _ \ __|
|
|
|
|
| |__| | (_| | || (_| | | |__| | __/ |_
|
|
|
|
|_____/ \__,_|\__\__,_| \_____|\___|\_*/
|
|
|
|
public Notification notificationGetDataShow(int maxProgress) {
|
2019-11-03 08:01:12 -06:00
|
|
|
/*Intent notificationIntent = new Intent(app.getContext(), SyncService.class);
|
2019-09-18 15:29:09 -05:00
|
|
|
notificationIntent.setAction(ACTION_CANCEL);
|
|
|
|
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
|
2019-11-03 08:01:12 -06:00
|
|
|
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);*/
|
2019-09-18 15:29:09 -05:00
|
|
|
|
|
|
|
getDataNotificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_GET_DATA)
|
|
|
|
.setSmallIcon(android.R.drawable.stat_sys_download)
|
|
|
|
.setColor(notificationColor)
|
|
|
|
.setContentTitle(app.getString(R.string.notification_get_data_title))
|
|
|
|
.setContentText(app.getString(R.string.notification_get_data_text))
|
2019-11-03 08:01:12 -06:00
|
|
|
//.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_cancel), pendingIntent)
|
2019-09-18 15:29:09 -05:00
|
|
|
//.setGroup(GROUP_KEY_GET_DATA)
|
|
|
|
.setOngoing(true)
|
|
|
|
.setProgress(maxProgress, 0, false)
|
|
|
|
.setTicker(app.getString(R.string.notification_get_data_summary))
|
|
|
|
.setPriority(NotificationCompat.PRIORITY_LOW);
|
|
|
|
return getDataNotificationBuilder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Notification notificationGetDataProgress(int progress, int maxProgress) {
|
|
|
|
getDataNotificationBuilder.setProgress(maxProgress, progress, false);
|
|
|
|
return getDataNotificationBuilder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Notification notificationGetDataAction(int stringResId) {
|
|
|
|
getDataNotificationBuilder.setContentTitle(app.getString(R.string.sync_action_format, app.getString(stringResId)));
|
|
|
|
return getDataNotificationBuilder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Notification notificationGetDataProfile(String profileName) {
|
|
|
|
getDataNotificationBuilder.setContentText(profileName);
|
|
|
|
return getDataNotificationBuilder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Notification notificationGetDataError(String profileName, String error, int failedProfileId) {
|
|
|
|
Intent notificationIntent = new Intent(app.getContext(), Notifier.GetDataRetryService.class);
|
|
|
|
notificationIntent.putExtra("failedProfileId", failedProfileId);
|
|
|
|
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
|
|
|
|
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
|
|
|
|
|
getDataNotificationBuilder.mActions.clear();
|
|
|
|
/*try {
|
|
|
|
//Use reflection clean up old actions
|
|
|
|
Field f = getDataNotificationBuilder.getClass().getDeclaredField("mActions");
|
|
|
|
f.setAccessible(true);
|
|
|
|
f.set(getDataNotificationBuilder, new ArrayList<NotificationCompat.Action>());
|
|
|
|
} catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}*/
|
|
|
|
|
|
|
|
getDataNotificationBuilder.setProgress(0, 0, false)
|
|
|
|
.setTicker(app.getString(R.string.notification_get_data_error_summary))
|
|
|
|
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
|
|
|
.addAction(R.drawable.ic_notification, app.getString(R.string.notification_get_data_once_again), pendingIntent)
|
|
|
|
.setContentTitle(app.getString(R.string.notification_get_data_error_title, profileName))
|
|
|
|
.setContentText(error)
|
|
|
|
.setStyle(new NotificationCompat.BigTextStyle().bigText(error))
|
|
|
|
.setOngoing(false);
|
|
|
|
return getDataNotificationBuilder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void notificationPost(int id, Notification notification) {
|
|
|
|
notificationManager.notify(id, notification);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void notificationCancel(int id) {
|
|
|
|
notificationManager.cancel(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
//public void notificationGetDataHide() {
|
|
|
|
// notificationManager.cancel(ID_GET_DATA);
|
|
|
|
// }
|
|
|
|
|
|
|
|
public static class GetDataRetryService extends IntentService {
|
|
|
|
private static final String TAG = "Notifier/GetDataRetry";
|
|
|
|
|
|
|
|
public GetDataRetryService() {
|
|
|
|
super(Notifier.GetDataRetryService.class.getSimpleName());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void onHandleIntent(Intent intent) {
|
2019-11-03 08:01:12 -06:00
|
|
|
|
|
|
|
|
2019-09-18 15:29:09 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* _ _ _ _ __ _ _ _
|
|
|
|
| \ | | | | (_)/ _(_) | | (_)
|
|
|
|
| \| | ___ | |_ _| |_ _ ___ __ _| |_ _ ___ _ __
|
|
|
|
| . ` |/ _ \| __| | _| |/ __/ _` | __| |/ _ \| '_ \
|
|
|
|
| |\ | (_) | |_| | | | | (_| (_| | |_| | (_) | | | |
|
|
|
|
|_| \_|\___/ \__|_|_| |_|\___\__,_|\__|_|\___/|_| |*/
|
2019-09-28 07:37:05 -05:00
|
|
|
public void add(pl.szczodrzynski.edziennik.utils.models.Notification notification) {
|
2019-09-18 15:29:09 -05:00
|
|
|
app.appConfig.notifications.add(notification);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void postAll(ProfileFull profile) {
|
|
|
|
Collections.sort(app.appConfig.notifications, (o1, o2) -> (o2.addedDate - o1.addedDate > 0) ? 1 : (o2.addedDate - o1.addedDate < 0) ? -1 : 0);
|
|
|
|
if (profile != null && !profile.getSyncNotifications())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (app.appConfig.notifications.size() > 40) {
|
|
|
|
app.appConfig.notifications.subList(40, app.appConfig.notifications.size() - 1).clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
int unreadCount = 0;
|
2019-09-28 07:37:05 -05:00
|
|
|
List<pl.szczodrzynski.edziennik.utils.models.Notification> notificationList = new ArrayList<>();
|
|
|
|
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: app.appConfig.notifications) {
|
2019-09-18 15:29:09 -05:00
|
|
|
if (!notification.notified) {
|
|
|
|
notification.seen = false;
|
|
|
|
notification.notified = true;
|
|
|
|
unreadCount++;
|
|
|
|
if (notificationList.size() < 10) {
|
|
|
|
notificationList.add(notification);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
notification.seen = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-28 07:37:05 -05:00
|
|
|
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: notificationList) {
|
2019-09-18 15:29:09 -05:00
|
|
|
Intent intent = new Intent(app, MainActivity.class);
|
|
|
|
notification.fillIntent(intent);
|
|
|
|
PendingIntent pendingIntent = PendingIntent.getActivity(app, notification.id, intent, 0);
|
|
|
|
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, getNotificationGroup())
|
|
|
|
// title, text, type, date
|
|
|
|
.setContentTitle(notification.title)
|
|
|
|
.setContentText(notification.text)
|
2019-09-28 07:37:05 -05:00
|
|
|
.setSubText(pl.szczodrzynski.edziennik.utils.models.Notification.stringType(app, notification.type))
|
2019-09-18 15:29:09 -05:00
|
|
|
.setWhen(notification.addedDate)
|
2019-09-28 07:37:05 -05:00
|
|
|
.setTicker(app.getString(R.string.notification_ticker_format, pl.szczodrzynski.edziennik.utils.models.Notification.stringType(app, notification.type)))
|
2019-09-18 15:29:09 -05:00
|
|
|
// icon, color, lights, priority
|
|
|
|
.setSmallIcon(R.drawable.ic_notification)
|
|
|
|
.setColor(notificationColor)
|
|
|
|
.setLights(0xFF00FFFF, 2000, 2000)
|
|
|
|
.setPriority(getNotificationPriority())
|
|
|
|
// channel, group, style
|
|
|
|
.setChannelId(getNotificationGroup())
|
|
|
|
.setGroup(getNotificationGroup())
|
|
|
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
|
|
|
.setStyle(new NotificationCompat.BigTextStyle().bigText(notification.text))
|
|
|
|
// intent, auto cancel
|
|
|
|
.setContentIntent(pendingIntent)
|
|
|
|
.setAutoCancel(true);
|
|
|
|
if (!shouldBeQuiet()) {
|
|
|
|
notificationBuilder.setDefaults(getNotificationDefaults());
|
|
|
|
}
|
|
|
|
notificationManager.notify(notification.id, notificationBuilder.build());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (notificationList.size() > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
|
|
Intent intent = new Intent(app, MainActivity.class);
|
|
|
|
intent.setAction("android.intent.action.MAIN");
|
|
|
|
intent.putExtra("fragmentId", MainActivity.DRAWER_ITEM_NOTIFICATIONS);
|
|
|
|
PendingIntent pendingIntent = PendingIntent.getActivity(app, ID_NOTIFICATIONS,
|
|
|
|
intent, 0);
|
|
|
|
|
|
|
|
NotificationCompat.Builder groupBuilder =
|
|
|
|
new NotificationCompat.Builder(app, getNotificationGroup())
|
|
|
|
.setSmallIcon(R.drawable.ic_notification)
|
|
|
|
.setColor(notificationColor)
|
|
|
|
.setContentTitle(app.getString(R.string.notification_new_notification_title_format, unreadCount))
|
|
|
|
.setGroupSummary(true)
|
|
|
|
.setAutoCancel(true)
|
|
|
|
.setChannelId(getNotificationGroup())
|
|
|
|
.setGroup(getNotificationGroup())
|
|
|
|
.setLights(0xFF00FFFF, 2000, 2000)
|
|
|
|
.setPriority(getNotificationPriority())
|
|
|
|
.setContentIntent(pendingIntent)
|
|
|
|
.setStyle(new NotificationCompat.BigTextStyle());
|
|
|
|
if (!shouldBeQuiet()) {
|
|
|
|
groupBuilder.setDefaults(getNotificationDefaults());
|
|
|
|
}
|
|
|
|
notificationManager.notify(ID_NOTIFICATIONS, groupBuilder.build());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* _ _ _ _
|
|
|
|
| | | | | | | |
|
|
|
|
| | | |_ __ __| | __ _| |_ ___ ___
|
|
|
|
| | | | '_ \ / _` |/ _` | __/ _ \/ __|
|
|
|
|
| |__| | |_) | (_| | (_| | || __/\__ \
|
|
|
|
\____/| .__/ \__,_|\__,_|\__\___||___/
|
|
|
|
| |
|
|
|
|
|*/
|
|
|
|
public void notificationUpdatesShow(String updateVersion, String updateUrl, String updateFilename) {
|
|
|
|
if (!app.appConfig.notifyAboutUpdates)
|
|
|
|
return;
|
|
|
|
Intent notificationIntent = new Intent(app.getContext(), BootReceiver.NotificationActionService.class)
|
|
|
|
.putExtra("update_version", updateVersion)
|
|
|
|
.putExtra("update_url", updateUrl)
|
|
|
|
.putExtra("update_filename", updateFilename);
|
|
|
|
|
|
|
|
PendingIntent pendingIntent = PendingIntent.getService(app.getContext(), 0,
|
|
|
|
notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
|
|
|
|
|
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(app, GROUP_KEY_UPDATES)
|
|
|
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
|
|
|
.setColor(notificationColor)
|
|
|
|
.setContentTitle(app.getString(R.string.notification_updates_title))
|
|
|
|
.setContentText(app.getString(R.string.notification_updates_text, updateVersion))
|
|
|
|
.setLights(0xFF00FFFF, 2000, 2000)
|
|
|
|
.setContentIntent(pendingIntent)
|
|
|
|
.setTicker(app.getString(R.string.notification_updates_summary))
|
|
|
|
.setPriority(PRIORITY_MAX)
|
|
|
|
.setAutoCancel(true);
|
|
|
|
if (!shouldBeQuiet()) {
|
|
|
|
notificationBuilder.setDefaults(getNotificationDefaults());
|
|
|
|
}
|
|
|
|
notificationManager.notify(ID_UPDATES, notificationBuilder.build());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void notificationUpdatesHide() {
|
|
|
|
if (!app.appConfig.notifyAboutUpdates)
|
|
|
|
return;
|
|
|
|
notificationManager.cancel(ID_UPDATES);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void dump() {
|
2019-09-28 07:37:05 -05:00
|
|
|
for (pl.szczodrzynski.edziennik.utils.models.Notification notification: app.appConfig.notifications) {
|
2019-09-18 15:29:09 -05:00
|
|
|
Log.d(TAG, "Profile"+notification.profileId+" Notification from "+ Date.fromMillis(notification.addedDate).getFormattedString()+" "+ Time.fromMillis(notification.addedDate).getStringHMS()+" - "+notification.text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|