package app.fedilab.fedilabtube.helper; /* Copyright 2020 Thomas Schneider * * This file is a part of TubeLab * * 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. * * TubeLab 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 TubeLab; if not, * see . */ import android.annotation.SuppressLint; import android.app.Activity; import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.util.TypedValue; import android.view.View; import android.view.WindowManager; import android.webkit.CookieManager; import android.webkit.URLUtil; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.widget.ImageView; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.browser.customtabs.CustomTabsIntent; import androidx.core.content.ContextCompat; import androidx.core.graphics.drawable.DrawableCompat; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.bitmap.CenterCrop; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.request.RequestOptions; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import java.util.regex.Pattern; import app.fedilab.fedilabtube.MainActivity; import app.fedilab.fedilabtube.R; import app.fedilab.fedilabtube.WebviewActivity; import app.fedilab.fedilabtube.client.entities.Account; import app.fedilab.fedilabtube.sqlite.AccountDAO; import app.fedilab.fedilabtube.sqlite.Sqlite; import app.fedilab.fedilabtube.webview.CustomWebview; import app.fedilab.fedilabtube.webview.ProxyHelper; import es.dmoral.toasty.Toasty; import static android.content.Context.DOWNLOAD_SERVICE; public class Helper { public static final int RELOAD_MYVIDEOS = 10; public static final String SET_VIDEO_MODE = "set_video_mode"; public static final int VIDEO_MODE_TORRENT = 0; public static final int VIDEO_MODE_WEBVIEW = 1; public static final int VIDEO_MODE_DIRECT = 2; public static final int ADD_USER_INTENT = 5; public static final String SET_SHARE_DETAILS = "set_share_details"; public static final int DEFAULT_VIDEO_CACHE_MB = 100; public static final String TAG = "mastodon_etalab"; public static final String CLIENT_NAME_VALUE = "Fedilab"; public static final String OAUTH_SCOPES_PEERTUBE = "user"; public static final String PREF_KEY_OAUTH_TOKEN = "oauth_token"; public static final Pattern urlPattern = Pattern.compile( "(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,10}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); public static final Pattern hashtagPattern = Pattern.compile("(#[\\w_A-zÀ-ÿ]+)"); public static final String LAST_NOTIFICATION_MAX_ID = "last_notification_max_id"; public static final String SET_VIDEO_CACHE = "set_video_cache"; //Proxy public static final String SET_PROXY_ENABLED = "set_proxy_enabled"; public static final String SET_PROXY_TYPE = "set_proxy_type"; public static final String SET_PROXY_HOST = "set_proxy_host"; public static final String SET_PROXY_PORT = "set_proxy_port"; public static final String SET_PROXY_LOGIN = "set_proxy_login"; public static final String SET_PROXY_PASSWORD = "set_proxy_password"; public static final String INTENT_ACTION = "intent_action"; public static final String PREF_KEY_ID = "userID"; public static final String PREF_IS_MODERATOR = "is_moderator"; public static final String PREF_IS_ADMINISTRATOR = "is_administrator"; public static final String PREF_INSTANCE = "instance"; public static final String REDIRECT_CONTENT = "urn:ietf:wg:oauth:2.0:oob"; public static final int EXTERNAL_STORAGE_REQUEST_CODE = 84; public static final String SET_VIDEOS_PER_PAGE = "set_videos_per_page"; public static final String VIDEO_ID = "video_id_update"; public static final String CLIENT_NAME = "client_name"; public static final String APP_PREFS = "app_prefs"; public static final String ID = "id"; public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; public static final String REDIRECT_URIS = "redirect_uris"; public static final String SCOPES = "scopes"; public static final String WEBSITE = "website"; public static final String WEBSITE_VALUE = "https://fedilab.app"; public static final int VIDEOS_PER_PAGE = 40; public static final String SET_VIDEO_NSFW = "set_video_nsfw"; public static final String SET_EMBEDDED_BROWSER = "set_embedded_browser"; public static final String SET_CUSTOM_TABS = "set_custom_tabs"; public static final String INTENT_ADD_UPLOADED_MEDIA = "intent_add_uploaded_media"; public static final String RECEIVE_ACTION = "receive_action"; public static final String SET_UNFOLLOW_VALIDATION = "set_unfollow_validation"; //List of available academies public static String[] academies = { "ac-aix-marseille.fr", "ac-amiens.fr", "ac-besancon.fr", "ac-bordeaux.fr", "clermont-ferrand.fr", "ac-corse.fr", "ac-creteil.fr", "ac-dijon.fr", "ac-grenoble.fr", "education.fr", "ac-lille.fr", "ac-limoges.fr", "ac-lyon.fr", "ac-mayotte.fr", "ac-montpellier.fr", "ac-nancy.fr", "ac-nantes.fr", "ac-normandie.fr", "ac-orleans-tours.fr", "ac-paris.fr", "ac-poitiers.fr", "outremer.fr", "ac-rennes.fr", "ac-strasbourg.fr", "ac-toulouse.fr", "ac-versailles.fr" }; public static String[] valideEmails = { "ac-aix-marseille.fr", "ac-amiens.fr", "ac-besancon.fr", "ac-bordeaux.fr", "clermont-ferrand.fr", "ac-corse.fr", "ac-creteil.fr", "ac-dijon.fr", "ac-grenoble.fr", "education.fr", "ac-guadeloupe.fr", "ac-guyane.fr", "ac-reunion.fr", "ac-lille.fr", "ac-limoges.fr", "ac-lyon.fr", "ac-martinique.fr", "ac-mayotte.fr", "ac-montpellier.fr", "ac-nancy.fr", "ac-nantes.fr", "ac-normandie.fr", "ac-orleans-tours.fr", "ac-paris.fr", "ac-poitiers.fr", "ac-rennes.fr", "ac-spm.fr", "ac-strasbourg.fr", "ac-toulouse.fr", "ac-versailles.fr", "ac-wf.wf", "monvr.pf", "ac-noumea.nc", "education.gouv.fr", "igesr.gouv.fr" }; /** * Returns the peertube URL depending of the academic domain name * * @param acad String academic domain name * @return String the peertube URL */ public static String getPeertubeUrl(String acad) { if (acad.compareTo("education.gouv.fr") == 0 || acad.compareTo("igesr.gouv.fr") == 0) { acad = "education.fr"; } else if (acad.compareTo("ac-nancy-metz.fr") == 0) { acad = "ac-nancy.fr"; } else if (acad.compareTo("clermont.fr") == 0) { acad = "clermont-ferrand.fr"; } else if (acad.compareTo("ac-wf.wf") == 0 || acad.compareTo("ac-mayotte.fr") == 0 || acad.compareTo("ac-noumea.nc") == 0 || acad.compareTo("ac-guadeloupe.fr") == 0 || acad.compareTo("monvr.pf") == 0 || acad.compareTo("ac-reunion.fr") == 0 || acad.compareTo("ac-martinique.fr") == 0 || acad.compareTo("ac-guyane.fr") == 0 ) { acad = "outremer.fr"; } if (!acad.contains("ac-lyon.fr")) { return "tube-" + acad.replaceAll("ac-|\\.fr", "") + ".beta.education.fr"; } else { return "tube.ac-lyon.fr"; } } /** * Returns the instance of the authenticated user * * @param context Context * @return String domain instance */ public static String getLiveInstance(Context context) { final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); String acad = sharedpreferences.getString(Helper.PREF_INSTANCE, "tube.ac-lyon.fr"); return getPeertubeUrl(acad); } public static String instanceWithProtocol(Context context) { return "https://" + getLiveInstance(context); } /** * Convert String date from Mastodon * * @param date String * @return Date */ public static Date mstStringToDate(String date) throws ParseException { if (date == null) return null; String STRING_DATE_FORMAT; Locale local = Locale.getDefault(); if (!date.contains("+")) { STRING_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; } else { //GNU date format STRING_DATE_FORMAT = "EEE MMM dd HH:mm:ss ZZZZZ yyyy"; local = Locale.ENGLISH; } SimpleDateFormat simpleDateFormat = new SimpleDateFormat(STRING_DATE_FORMAT, local); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("gmt")); simpleDateFormat.setLenient(true); try { return simpleDateFormat.parse(date); } catch (Exception e) { String newdate = date.split("\\+")[0].trim(); simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", local); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("gmt")); simpleDateFormat.setLenient(true); return simpleDateFormat.parse(newdate); } } /** * Convert a date in String -> format yyyy-MM-dd HH:mm:ss * * @param date Date * @return String */ public static String dateToString(Date date) { if (date == null) return null; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); return dateFormat.format(date); } /** * Convert String date from db to Date Object * * @param stringDate date to convert * @return Date */ public static Date stringToDate(Context context, String stringDate) { if (stringDate == null) return null; Locale userLocale; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { userLocale = context.getResources().getConfiguration().getLocales().get(0); } else { userLocale = context.getResources().getConfiguration().locale; } SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", userLocale); Date date = null; try { date = dateFormat.parse(stringDate); } catch (java.text.ParseException ignored) { } return date; } public static String secondsToString(int pTime) { int hour = pTime / 3600; int min = (pTime - (hour * 3600)) / 60; int sec = pTime - (hour * 3600) - (min * 60); String strHour = "0", strMin = "0", strSec; if (hour > 0) strHour = String.format(Locale.getDefault(), "%02d", hour); if (min > 0) strMin = String.format(Locale.getDefault(), "%02d", min); strSec = String.format(Locale.getDefault(), "%02d", sec); if (hour > 0) return String.format(Locale.getDefault(), "%s:%s:%s", strHour, strMin, strSec); else return String.format(Locale.getDefault(), "%s:%s", strMin, strSec); } /*** * Returns a String depending of the date * @param context Context * @param dateToot Date * @return String */ public static String dateDiff(Context context, Date dateToot) { Date now = new Date(); long diff = now.getTime() - dateToot.getTime(); long seconds = diff / 1000; long minutes = seconds / 60; long hours = minutes / 60; long days = hours / 24; long months = days / 30; long years = days / 365; String format = DateFormat.getDateInstance(DateFormat.SHORT).format(dateToot); if (years > 0) { return format; } else if (months > 0 || days > 7) { //Removes the year depending of the locale from DateFormat.SHORT format SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()); df.applyPattern(df.toPattern().replaceAll("[^\\p{Alpha}]*y+[^\\p{Alpha}]*", "")); return df.format(dateToot); } else if (days > 0) return context.getString(R.string.date_day, days); else if (hours > 0) return context.getResources().getString(R.string.date_hours, (int) hours); else if (minutes > 0) return context.getResources().getString(R.string.date_minutes, (int) minutes); else { if (seconds < 0) seconds = 0; return context.getResources().getString(R.string.date_seconds, (int) seconds); } } /** * Convert a date in String -> format yyyy-MM-dd HH:mm:ss * * @param date Date * @return String */ public static String shortDateToString(Date date) { SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()); return df.format(date); } public static String dateDiffFull(Date dateToot) { SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.getDefault()); try { return df.format(dateToot); } catch (Exception e) { return ""; } } public static String withSuffix(long count) { if (count < 1000) return "" + count; int exp = (int) (Math.log(count) / Math.log(1000)); Locale locale = null; try { locale = Locale.getDefault(); } catch (Exception ignored) { } if (locale != null) return String.format(locale, "%.1f %c", count / Math.pow(1000, exp), "kMGTPE".charAt(exp - 1)); else return String.format(Locale.getDefault(), "%.1f %c", count / Math.pow(1000, exp), "kMGTPE".charAt(exp - 1)); } public static void loadGiF(final Context context, Account account, final ImageView imageView) { if (account == null || account.getAvatar() == null || account.getAvatar().compareTo("null") == 0) { Glide.with(imageView.getContext()) .asDrawable() .load(R.drawable.missing_peertube) .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) .into(imageView); return; } String url = account.getAvatar(); if (url.startsWith("/")) { url = Helper.getLiveInstance(context) + url; } if (!url.startsWith("http")) { url = "https://" + url; } try { Glide.with(imageView.getContext()) .load(url) .thumbnail(0.1f) .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) .into(imageView); } catch (Exception e) { try { Glide.with(imageView.getContext()) .asDrawable() .load(R.drawable.missing_peertube) .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) .into(imageView); } catch (Exception ignored) { } } } public static void loadGiF(final Context context, String url, final ImageView imageView) { SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); if (url.startsWith("/")) { url = Helper.getLiveInstance(context) + url; } if (!url.startsWith("http")) { url = "https://" + url; } try { Glide.with(imageView.getContext()) .load(url) .thumbnail(0.1f) .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) .into(imageView); } catch (Exception e) { try { Glide.with(imageView.getContext()) .asDrawable() .load(R.drawable.missing_peertube) .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))) .into(imageView); } catch (Exception ignored) { } } } /** * Manage URLs to open (built-in or external app) * * @param context Context * @param url String url to open */ public static void openBrowser(Context context, String url) { SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, android.content.Context.MODE_PRIVATE); boolean embedded_browser = sharedpreferences.getBoolean(Helper.SET_EMBEDDED_BROWSER, true); if (embedded_browser) { Intent intent = new Intent(context, WebviewActivity.class); Bundle b = new Bundle(); String finalUrl = url; if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) finalUrl = "http://" + url; b.putString("url", finalUrl); intent.putExtras(b); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } else { boolean custom_tabs = sharedpreferences.getBoolean(Helper.SET_CUSTOM_TABS, true); if (custom_tabs) { CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); CustomTabsIntent customTabsIntent = builder.build(); builder.setToolbarColor(ContextCompat.getColor(context, R.color.colorPrimary)); try { customTabsIntent.launchUrl(context, Uri.parse(url)); } catch (Exception ignored) { Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show(); } } else { Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setData(Uri.parse(url)); try { context.startActivity(intent); } catch (Exception e) { Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show(); } } } } @SuppressLint("SetJavaScriptEnabled") public static CustomWebview initializeWebview(Activity activity, int webviewId, View rootView) { CustomWebview webView; if (rootView == null) { webView = activity.findViewById(webviewId); } else { webView = rootView.findViewById(webviewId); } final SharedPreferences sharedpreferences = activity.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setUseWideViewPort(true); webView.getSettings().setLoadWithOverviewMode(true); webView.getSettings().setSupportZoom(true); webView.getSettings().setDisplayZoomControls(false); webView.getSettings().setBuiltInZoomControls(true); webView.getSettings().setAllowContentAccess(true); webView.getSettings().setLoadsImagesAutomatically(true); webView.getSettings().setSupportMultipleWindows(false); webView.getSettings().setMediaPlaybackRequiresUserGesture(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptThirdPartyCookies(webView, false); } webView.setBackgroundColor(Color.TRANSPARENT); webView.getSettings().setAppCacheEnabled(true); webView.getSettings().setDatabaseEnabled(true); webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT); webView.setWebChromeClient(new WebChromeClient() { @Override public Bitmap getDefaultVideoPoster() { return Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888); } }); boolean proxyEnabled = sharedpreferences.getBoolean(Helper.SET_PROXY_ENABLED, false); if (proxyEnabled) { String host = sharedpreferences.getString(Helper.SET_PROXY_HOST, "127.0.0.1"); int port = sharedpreferences.getInt(Helper.SET_PROXY_PORT, 8118); ProxyHelper.setProxy(activity, webView, host, port, WebviewActivity.class.getName()); } return webView; } /** * Manage downloads with URLs * * @param context Context * @param url String download url */ public static void manageDownloads(final Context context, final String url) { final AlertDialog.Builder builder = new AlertDialog.Builder(context); final DownloadManager.Request request; try { request = new DownloadManager.Request(Uri.parse(url.trim())); } catch (Exception e) { Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show(); return; } final String fileName = URLUtil.guessFileName(url, null, null); builder.setMessage(context.getResources().getString(R.string.download_file, fileName)); builder.setCancelable(false) .setPositiveButton(context.getString(R.string.yes), (dialog, id) -> { request.allowScanningByMediaScanner(); request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); DownloadManager dm = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE); assert dm != null; dm.enqueue(request); dialog.dismiss(); }) .setNegativeButton(context.getString(R.string.cancel), (dialog, id) -> dialog.cancel()); AlertDialog alert = builder.create(); if (alert.getWindow() != null) alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); alert.show(); } /** * Log out the authenticated user by removing its token * * @param activity Activity */ public static void logoutCurrentUser(Activity activity, Account account) { SharedPreferences sharedpreferences = activity.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); SQLiteDatabase db = Sqlite.getInstance(activity.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); if (account != null) { new AccountDAO(activity, db).removeUser(account); } SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, null); editor.putString(Helper.CLIENT_ID, null); editor.putString(Helper.CLIENT_SECRET, null); editor.putString(Helper.PREF_KEY_ID, null); editor.putBoolean(Helper.PREF_IS_MODERATOR, false); editor.putBoolean(Helper.PREF_IS_ADMINISTRATOR, false); editor.putString(Helper.PREF_INSTANCE, null); editor.putString(Helper.ID, null); editor.apply(); Intent mainActivity = new Intent(activity, MainActivity.class); mainActivity.putExtra(Helper.INTENT_ACTION, Helper.ADD_USER_INTENT); activity.startActivity(mainActivity); activity.finish(); } public static int getAttColor(Context context, int attColor) { TypedValue typedValue = new TypedValue(); context.getTheme().resolveAttribute(attColor, typedValue, true); return ContextCompat.getColor(context, typedValue.resourceId); } /** * change color of a drawable * * @param drawable int the drawable * @param hexaColor example 0xffff00 */ public static Drawable changeDrawableColor(Context context, int drawable, int hexaColor) { Drawable mDrawable = ContextCompat.getDrawable(context, drawable); int color; try { color = Color.parseColor(context.getString(hexaColor)); } catch (Resources.NotFoundException e) { try { TypedValue typedValue = new TypedValue(); Resources.Theme theme = context.getTheme(); theme.resolveAttribute(hexaColor, typedValue, true); color = typedValue.data; } catch (Resources.NotFoundException ed) { color = hexaColor; } } assert mDrawable != null; mDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); DrawableCompat.setTint(mDrawable, color); return mDrawable; } /** * Returns boolean depending if the user is authenticated * * @param context Context * @return boolean */ public static boolean isLoggedIn(Context context) { SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); String prefKeyOauthTokenT = sharedpreferences.getString(PREF_KEY_OAUTH_TOKEN, null); return (prefKeyOauthTokenT != null); } }