123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- /*
- * DSBDirect
- * Copyright (C) 2019 Fynn Godau
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * This software is not affiliated with heinekingmedia GmbH, the
- * developer of the DSB platform.
- */
- package godau.fynn.dsbdirect;
- import android.app.Activity;
- import android.app.AlertDialog;
- import android.app.job.JobInfo;
- import android.app.job.JobScheduler;
- import android.content.*;
- import android.net.Uri;
- import android.os.Build;
- import android.support.annotation.Nullable;
- import android.support.annotation.RequiresApi;
- import android.support.annotation.StyleRes;
- import android.support.design.widget.Snackbar;
- import android.util.Log;
- import android.view.View;
- import android.view.ViewGroup;
- import godau.fynn.dsbdirect.manager.DownloadManager;
- import godau.fynn.dsbdirect.table.PollingService;
- import godau.fynn.dsbdirect.table.reader.Reader;
- import godau.fynn.dsbdirect.table.reader.Roentgen;
- import godau.fynn.dsbdirect.table.reader.Untis;
- import godau.fynn.dsbdirect.table.reader.Untis4;
- import org.json.JSONException;
- import org.json.JSONObject;
- import org.jsoup.nodes.Element;
- import org.jsoup.select.Elements;
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.*;
- import static android.content.Context.MODE_PRIVATE;
- public class Utility {
- public static final String NULL = "automatic";
- public static final String UNTIS = "untis";
- public static final String UNTIS4 = "untis4"; // technically a SUPER SECRET SETTING
- public static final String ROENTGEN = "roentgen";
- public static final String DATE_FORMAT = "EEEE, d.M.yyyy";
- // For Gadgetbridge compatibility: Gadgetbridge seems to only push content text, not big text text
- public static final String SUPER_SECRET_SETTING_TEXT_AS_CONTENT_TEXT = "textAsContentText";
- // For debugging: display messages even if the exact same content has already been displayed
- public static final String SUPER_SECRET_SETTING_NOTIFY_ABOUT_NOTHING_NEW = "ignoreAlreadyDisplayed";
- // For debugging: also poll on weekends
- public static final String SUPER_SECRET_SETTING_POLL_ON_WEEKENDS = "ignoreWeekend";
- // For convenience: in case you find sharing annoying, you can disable it
- public static final String SUPER_SECRET_SETTING_HOLD_TO_SHARE = "holdToShare";
- private Context mContext;
- public Utility(Context context) {
- this.mContext = context;
- }
- public String formatDate(Date date) {
- Calendar today = Calendar.getInstance();
- Calendar then = Calendar.getInstance();
- then.setTime(date);
- if (then.get(Calendar.YEAR) == today.get(Calendar.YEAR)
- && then.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR)) {
- return mContext.getString(R.string.today);
- }
- Calendar tomorrow = Calendar.getInstance();
- tomorrow.add(Calendar.DAY_OF_YEAR, 1);
- if (then.get(Calendar.YEAR) == tomorrow.get(Calendar.YEAR)
- && then.get(Calendar.DAY_OF_YEAR) == tomorrow.get(Calendar.DAY_OF_YEAR)) {
- return mContext.getString(R.string.tomorrow);
- }
- Calendar yesterday = Calendar.getInstance();
- yesterday.add(Calendar.DAY_OF_YEAR, -1);
- if (then.get(Calendar.YEAR) == yesterday.get(Calendar.YEAR)
- && then.get(Calendar.DAY_OF_YEAR) == yesterday.get(Calendar.DAY_OF_YEAR)) {
- return mContext.getString(R.string.yesterday);
- }
- // Date is neither today, yesterday nor tomorrow, format it
- SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT, Locale.getDefault());
- return simpleDateFormat.format(date);
- }
- public @Nullable
- Reader getReader(String html, String id) {
- // User preference overwrite hardcoded preference overwrite dynamic decision
- switch (getParserIdentifier(id)) {
- case UNTIS:
- return new Untis(html);
- case UNTIS4:
- return new Untis4(html);
- case ROENTGEN:
- return new Roentgen(html);
- }
- // Dynamic decision: check whether this might be a Untis table
- if (html.contains("Untis Stundenplan Software")) {
- return new Untis(html);
- }
- // There is no reader if we got here
- return null;
- }
- public String getParserIdentifier(String id) {
- // Consider user setting
- String userSetting = getParserUserSetting();
- if (!userSetting.equals(NULL)) {
- // userSetting overwrites hardcoded preference by id
- id = userSetting;
- }
- switch (id) {
- case "162161": // Alexander-von-Humboldt-Gymnasium Schweinfurt
- case UNTIS:
- return UNTIS;
- case UNTIS4: // Please don't use this
- return UNTIS4;
- case ROENTGEN:
- case "219261": // Röntgen-Gymnasium Würzburg
- return ROENTGEN;
- default:
- return NULL;
- }
- }
- public String getParserUserSetting() {
- return getSharedPreferences().getString("parser", "automatic");
- }
- public static Date parseLastUpdatedDate(String input) {
- String[] dateParts = input.split(" ");
- String[] dateDigits = dateParts[0].split("\\.");
- String[] timeDigits = dateParts[1].split(":");
- return new Date(Integer.parseInt(dateDigits[2]) - 1900 /* years start at 1900 */, Integer.parseInt(dateDigits[1]) - 1 /* months start with 0 */,
- Integer.parseInt(dateDigits[0]), Integer.parseInt(timeDigits[0]), Integer.parseInt(timeDigits[1])); // because SimpleDateTime did deliver good results, again
- }
- public void checkForUpdate(final boolean verbose, final DownloadManager downloadManager) {
- // Thanks, https://stackoverflow.com/a/5069354
- final Activity activity = (Activity) mContext;
- final View rootView = ((ViewGroup) activity
- .findViewById(android.R.id.content)).getChildAt(0);
- Log.d("LOCALE", Locale.getDefault().toString());
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- // Request current version metadata
- JSONObject jsonObject = downloadManager.downloadUpdateCheck();
- int currentVersion = BuildConfig.VERSION_CODE;
- // Let's parse
- int latestVersion = jsonObject.getInt("versionCode");
- final String versionName = jsonObject.getString("versionName");
- final String whatsNew;
- // Localize changelog to German (hardcoded and uncool)
- if (Locale.getDefault().toString().contains("de")) {
- whatsNew = jsonObject.getString("whatsNewDE");
- } else {
- whatsNew = jsonObject.getString("whatsNew");
- }
- final String uri = jsonObject.getString("url");
- if (latestVersion > currentVersion) {
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- new AlertDialog.Builder(mContext)
- .setPositiveButton(R.string.download, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Intent intent;
- switch (mContext.getString(R.string.update_method)) {
- default:
- case "self":
- // Download file provided in updateCheck
- intent = new Intent(Intent.ACTION_VIEW)
- .setDataAndType(Uri.parse(uri), "application/vnd.android.package-archive");
- break;
- case "fdroid":
- // Launch fdroid website link and hope fdroid client handles it
- String fdroidUri = mContext.getString(R.string.uri_fdroid, mContext.getPackageName());
- intent = new Intent(Intent.ACTION_VIEW, Uri.parse(fdroidUri));
- break;
- case "market":
- // Launch market to download update
- String marketUri = mContext.getString(R.string.uri_market, mContext.getPackageName());
- intent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
- break;
- }
- mContext.startActivity(intent);
- }
- })
- .setNegativeButton(R.string.dismiss, null)
- .setTitle(mContext.getString(R.string.update_popup_title, versionName))
- .setMessage(whatsNew)
- .show();
- }
- });
- } else {
- if (verbose) {
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Snackbar
- .make(rootView, R.string.up_to_date, Snackbar.LENGTH_SHORT)
- .show();
- }
- });
- }
- }
- } catch (IOException | JSONException e) {
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Snackbar.make(rootView, R.string.network_versioncheck_failed, Snackbar.LENGTH_SHORT)
- .show();
- }
- });
- e.printStackTrace();
- }
- }
- }).start();
- }
- public SharedPreferences getSharedPreferences() {
- return mContext.getSharedPreferences("default", MODE_PRIVATE);
- }
- public String getUpdateCheckFrequency() {
- return getSharedPreferences().getString("update_check_frequency",
- mContext.getString(R.string.update_check_frequency_default));
- }
- public boolean shouldCheckForUpdates() {
- switch (getUpdateCheckFrequency()) {
- case "always":
- return true;
- case "sometimes":
- return new Random().nextInt(10) == 0;
- case "never":
- default:
- return false;
- }
- }
- public void resetToken() {
- // Reset token
- getSharedPreferences().edit().putString("token", null).apply();
- }
- @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
- public void schedulePolling() {
- // Check whether polling is enabled
- SharedPreferences sharedPreferences = getSharedPreferences();
- if (sharedPreferences.getBoolean("poll", false)) {
- // Polling is enabled
- ComponentName serviceComponent = new ComponentName(mContext, PollingService.class);
- JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
- builder.setMinimumLatency(PollingService.getNextPollingTimeDelay(sharedPreferences)); // Poll after at least this time
- builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); // Only execute once network is connected
- builder.setPersisted(true);
- JobScheduler jobScheduler = (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- jobScheduler.schedule(builder.build());
- } else {
- // Cancel potential job
- JobScheduler jobScheduler = (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- jobScheduler.cancel(0);
- }
- }
- public void saveAuthToken(String authToken) {
- final SharedPreferences sharedPreferences = getSharedPreferences();
- sharedPreferences.edit()
- .putString("token", authToken)
- .apply();
- }
- public static String smartConcatenate(String[] strings, String comma) {
- StringBuilder result = new StringBuilder();
- // Remove empty strings
- ArrayList<String> list = new ArrayList<>(Arrays.asList(strings));
- for (int i = list.size() - 1; i >= 0; i--) {
- if (list.get(i) == null || list.get(i).isEmpty()) {
- list.remove(i);
- }
- }
- // Append remaining strings to result
- for (int i = 0; i < list.size(); i++) {
- String string = list.get(i);
- result.append(string);
- if (i != list.size() - 1) {
- // This is not the last iteration yet
- result.append(comma);
- }
- }
- return String.valueOf(result);
- }
- public static Calendar zeroOClock(Calendar calendar) {
- calendar.set(Calendar.HOUR_OF_DAY, 0);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);
- return calendar;
- }
- public static String smartConcatenate(int[] integers, String comma) {
- String[] strings = new String[integers.length];
- for (int i = 0; i < integers.length; i++) {
- strings[i] = String.valueOf(integers[i]);
- }
- return smartConcatenate(strings, comma);
- }
- public static String smartConcatenate(Object[] objects, String comma) {
- // Probably a little inefficient, but it probably doesn't matter
- String[] strings = new String[objects.length];
- for (int i = 0; i < objects.length; i++) {
- strings[i] = String.valueOf(objects[i]);
- }
- return smartConcatenate(strings, comma);
- }
- public static String smartConcatenate(Elements elements, String comma) {
- String[] strings = new String[elements.size()];
- for (int i = 0; i < elements.size(); i++) {
- Element e = elements.get(i);
- strings[i] = e.text().replaceAll("\\\\n", "\n");
- }
- return smartConcatenate(strings, comma);
- }
- public @StyleRes
- int getStyle() {
- String preference = getSharedPreferences().getString("style", "light");
- switch (preference) {
- case "light":
- default:
- return R.style.Light;
- case "dark":
- return R.style.Dark;
- case "black":
- return R.style.Black;
- }
- }
- }
|