fedilab-Android-App/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java

2169 lines
99 KiB
Java

package app.fedilab.android.mastodon.helper;
/* Copyright 2021 Thomas Schneider
*
* This file is a part of Fedilab
*
* 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.
*
* Fedilab 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 Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import static android.content.Context.DOWNLOAD_SERVICE;
import static app.fedilab.android.mastodon.activities.BaseActivity.currentThemeId;
import static app.fedilab.android.mastodon.helper.LogoHelper.getNotificationIcon;
import static app.fedilab.android.mastodon.helper.ThemeHelper.fetchAccentColor;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.DownloadManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.RingtoneManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.text.Html;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.MimeTypeMap;
import android.webkit.URLUtil;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.avatarfirst.avatargenlib.AvatarGenerator;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.load.resource.gif.GifDrawable;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.fedilab.android.BaseMainActivity;
import app.fedilab.android.BuildConfig;
import app.fedilab.android.R;
import app.fedilab.android.activities.LoginActivity;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.databinding.PopupReleaseNotesBinding;
import app.fedilab.android.mastodon.activities.ComposeActivity;
import app.fedilab.android.mastodon.activities.ProfileActivity;
import app.fedilab.android.mastodon.broadcastreceiver.ToastMessage;
import app.fedilab.android.mastodon.client.entities.api.Attachment;
import app.fedilab.android.mastodon.client.entities.api.Status;
import app.fedilab.android.mastodon.client.entities.app.Account;
import app.fedilab.android.mastodon.client.entities.app.BaseAccount;
import app.fedilab.android.mastodon.client.entities.app.CachedBundle;
import app.fedilab.android.mastodon.client.entities.app.ReleaseNote;
import app.fedilab.android.mastodon.client.entities.app.Timeline;
import app.fedilab.android.mastodon.exception.DBException;
import app.fedilab.android.mastodon.interfaces.OnDownloadInterface;
import app.fedilab.android.mastodon.ui.drawer.ReleaseNoteAdapter;
import app.fedilab.android.mastodon.viewmodel.mastodon.AccountsVM;
import app.fedilab.android.mastodon.viewmodel.mastodon.OauthVM;
import app.fedilab.android.mastodon.watermark.androidwm.WatermarkBuilder;
import app.fedilab.android.mastodon.watermark.androidwm.bean.WatermarkText;
import app.fedilab.android.peertube.client.data.AccountData;
import es.dmoral.toasty.Toasty;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
public class Helper {
public static final String TAG = "fedilab_app";
public static final String CLIP_BOARD = "CLIP_BOARD";
public static final String INSTANCE_SOCIAL_KEY = "jGj9gW3z9ptyIpB8CMGhAlTlslcemMV6AgoiImfw3vPP98birAJTHOWiu5ZWfCkLvcaLsFZw9e3Pb7TIwkbIyrj3z6S7r2oE6uy6EFHvls3YtapP8QKNZ980p9RfzTb4";
public static final String WEBSITE_VALUE = "https://fedilab.app";
public static final String RECEIVE_TOAST_MESSAGE = "RECEIVE_TOAST_MESSAGE";
public static final String RECEIVE_TOAST_TYPE = "RECEIVE_TOAST_TYPE";
public static final String RECEIVE_TOAST_CONTENT = "RECEIVE_TOAST_CONTENT";
public static final String RECEIVE_TOAST_TYPE_ERROR = "RECEIVE_TOAST_TYPE_ERROR";
public static final String RECEIVE_TOAST_TYPE_INFO = "RECEIVE_TOAST_TYPE_INFO";
public static final String RECEIVE_TOAST_TYPE_SUCCESS = "RECEIVE_TOAST_TYPE_SUCCESS";
public static final String RECEIVE_TOAST_TYPE_WARNING = "RECEIVE_TOAST_TYPE_WARNING";
//Intent
public static final String INTENT_ACTION = "intent_action";
public static final String BROADCAST_DATA = "BROADCAST_DATA";
public static final String RECEIVE_REDRAW_TOPBAR = "RECEIVE_REDRAW_TOPBAR";
public static final String RECEIVE_REDRAW_BOTTOM = "RECEIVE_REDRAW_BOTTOM";
public static final String RECEIVE_STATUS_ACTION = "RECEIVE_STATUS_ACTION";
public static final String RECEIVE_REFRESH_NOTIFICATIONS_ACTION = "RECEIVE_REFRESH_NOTIFICATIONS_ACTION";
public static final String RECEIVE_ERROR_MESSAGE = "RECEIVE_ERROR_MESSAGE";
public static final String RECEIVE_RECREATE_ACTIVITY = "RECEIVE_RECREATE_ACTIVITY";
public static final String RECEIVE_RECREATE_PEERTUBE_ACTIVITY = "RECEIVE_RECREATE_PEERTUBE_ACTIVITY";
public static final String RECEIVE_NEW_MESSAGE = "RECEIVE_NEW_MESSAGE";
public static final String RECEIVE_COMPOSE_ERROR_MESSAGE = "RECEIVE_COMPOSE_ERROR_MESSAGE";
public static final String RECEIVE_MASTODON_LIST = "RECEIVE_MASTODON_LIST";
public static final String RECEIVE_REDRAW_PROFILE = "RECEIVE_REDRAW_PROFILE";
public static final String ARG_TIMELINE_TYPE = "ARG_TIMELINE_TYPE";
public static final String ARG_INTENT_ID = "ARG_INTENT_ID";
public static final String ARG_PEERTUBE_NAV_REMOTE = "ARG_PEERTUBE_NAV_REMOTE";
public static final String ARG_REMOTE_INSTANCE_STRING = "ARG_REMOTE_INSTANCE_STRING";
public static final String ARG_NOTIFICATION_TYPE = "ARG_NOTIFICATION_TYPE";
public static final String ARG_EXCLUDED_NOTIFICATION_TYPE = "ARG_EXCLUDED_NOTIFICATION_TYPE";
public static final String ARG_STATUS = "ARG_STATUS";
public static final String ARG_FOCUSED_STATUS_URI = "ARG_FOCUSED_STATUS_URI";
public static final String ARG_TIMELINE_REFRESH_ALL = "ARG_TIMELINE_REFRESH_ALL";
public static final String ARG_REFRESH_NOTFICATION = "ARG_REFRESH_NOTFICATION";
public static final String ARG_STATUS_DELETED = "ARG_STATUS_DELETED";
public static final String ARG_STATUS_UPDATED = "ARG_STATUS_UPDATED";
public static final String ARG_STATUS_POSTED = "ARG_STATUS_POSTED";
public static final String ARG_STATUS_ACTION = "ARG_STATUS_ACTION";
public static final String ARG_DELETE_ALL_FOR_ACCOUNT_ID = "ARG_DELETE_ALL_FOR_ACCOUNT_ID";
public static final String ARG_STATUS_ACCOUNT_ID_DELETED = "ARG_STATUS_ACCOUNT_ID_DELETED";
public static final String ARG_STATUS_DRAFT = "ARG_STATUS_DRAFT";
public static final String ARG_EDIT_STATUS_ID = "ARG_EDIT_STATUS_ID";
public static final String ARG_STATUS_SCHEDULED = "ARG_STATUS_SCHEDULED";
public static final String ARG_SLUG_OF_FIRST_FRAGMENT = "ARG_SLUG_OF_FIRST_FRAGMENT";
public static final String ARG_STATUS_DRAFT_ID = "ARG_STATUS_DRAFT_ID";
public static final String ARG_STATUS_REPLY = "ARG_STATUS_REPLY";
public static final String ARG_MENTION_BOOSTER = "ARG_MENTION_BOOSTER";
public static final String ARG_QUOTED_MESSAGE = "ARG_QUOTED_MESSAGE";
public static final String ARG_STATUS_REPLY_ID = "ARG_STATUS_REPLY_ID";
public static final String ARG_ACCOUNT = "ARG_ACCOUNT";
public static final String ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID";
public static final String ARG_CACHED_ACCOUNT_ID = "ARG_CACHED_ACCOUNT_ID";
public static final String ARG_CACHED_STATUS_ID = "ARG_CACHED_STATUS_ID";
public static final String ARG_ADMIN_DOMAINBLOCK = "ARG_ADMIN_DOMAINBLOCK";
public static final String ARG_ADMIN_DOMAINBLOCK_DELETE = "ARG_ADMIN_DOMAINBLOCK_DELETE";
public static final String FEDILAB_MUTED_HASHTAGS = "Fedilab muted hashtags";
public static final String ARG_REPORT = "ARG_REPORT";
public static final String ARG_ACCOUNT_MENTION = "ARG_ACCOUNT_MENTION";
public static final String ARG_MINIFIED = "ARG_MINIFIED";
public static final String ARG_STATUS_REPORT = "ARG_STATUS_REPORT";
public static final String ARG_STATUS_MENTION = "ARG_STATUS_MENTION";
public static final String ARG_SHARE_URL_MEDIA = "ARG_SHARE_URL_MEDIA";
public static final String ARG_SHARE_URL = "ARG_SHARE_URL";
public static final String ARG_SHARE_TITLE = "ARG_SHARE_TITLE";
public static final String ARG_SHARE_SUBJECT = "ARG_SHARE_SUBJECT";
public static final String ARG_SHARE_DESCRIPTION = "ARG_SHARE_DESCRIPTION";
public static final String ARG_SHARE_CONTENT = "ARG_SHARE_CONTENT";
public static final String ARG_FOLLOW_TYPE = "ARG_FOLLOW_TYPE";
public static final String ARG_TYPE_OF_INFO = "ARG_TYPE_OF_INFO";
public static final String ARG_TOKEN = "ARG_TOKEN";
public static final String ARG_INSTANCE = "ARG_INSTANCE";
public static final String ARG_REMOTE_INSTANCE = "ARG_REMOTE_INSTANCE";
public static final String ARG_STATUS_ID = "ARG_STATUS_ID";
public static final String ARG_WORK_ID = "ARG_WORK_ID";
public static final String ARG_LIST_ID = "ARG_LIST_ID";
public static final String ARG_LEMMY_POST_ID = "ARG_LEMMY_POST_ID";
public static final String ARG_SEARCH_KEYWORD = "ARG_SEARCH_KEYWORD";
public static final String ARG_DIRECTORY_ORDER = "ARG_DIRECTORY_ORDER";
public static final String ARG_DIRECTORY_LOCAL = "ARG_DIRECTORY_LOCAL";
public static final String ARG_SEARCH_TYPE = "ARG_SEARCH_TYPE";
public static final String ARG_SEARCH_KEYWORD_CACHE = "ARG_SEARCH_KEYWORD_CACHE";
public static final String ARG_VIEW_MODEL_KEY = "ARG_VIEW_MODEL_KEY";
public static final String ARG_TAG_TIMELINE = "ARG_TAG_TIMELINE";
public static final String ARG_BUBBLE_TIMELINE = "ARG_BUBBLE_TIMELINE";
public static final String ARG_MEDIA_POSITION = "ARG_MEDIA_POSITION";
public static final String ARG_MEDIA_ATTACHMENT = "ARG_MEDIA_ATTACHMENT";
public static final String ARG_MEDIA_ATTACHMENTS = "ARG_MEDIA_ATTACHMENTS";
public static final String ARG_SHOW_REPLIES = "ARG_SHOW_REPLIES";
public static final String ARG_SHOW_REBLOGS = "ARG_SHOW_REBLOGS";
public static final String ARG_INITIALIZE_VIEW = "ARG_INITIALIZE_VIEW";
public static final String ARG_SHOW_PINNED = "ARG_SHOW_PINNED";
public static final String ARG_SHOW_MEDIA_ONY = "ARG_SHOW_MEDIA_ONY";
public static final String ARG_MENTION = "ARG_MENTION";
public static final String ARG_CHECK_REMOTELY = "ARG_CHECK_REMOTELY";
public static final String ARG_USER_ID = "ARG_USER_ID";
public static final String ARG_MEDIA_ARRAY = "ARG_MEDIA_ARRAY";
public static final String ARG_MEDIA_ARRAY_PROFILE = "ARG_MEDIA_ARRAY_PROFILE";
public static final String ARG_VISIBILITY = "ARG_VISIBILITY";
public static final String ARG_SCHEDULED_DATE = "ARG_SCHEDULED_DATE";
public static final String WORKER_REFRESH_NOTIFICATION = "WORKER_REFRESH_NOTIFICATION";
public static final String WORKER_REFRESH_HOME = "WORKER_REFRESH_HOME";
public static final String WORKER_SCHEDULED_STATUSES = "WORKER_SCHEDULED_STATUSES";
public static final String WORKER_SCHEDULED_REBLOGS = "WORKER_SCHEDULED_REBLOGS";
public static final String VALUE_TRENDS = "VALUE_TRENDS";
//public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; rv:60.0) Gecko/20100101 Firefox/60.0";
public static final String REDIRECT_CONTENT_WEB = "fedilab://backtofedilab";
public static final String REDIRECT_CONTENT = "urn:ietf:wg:oauth:2.0:oob";
public static final String APP_OAUTH_SCOPES = "read write";
public static final String OAUTH_SCOPES = "read write follow push";
public static final String OAUTH_SCOPES_ADMIN = "read write follow push admin:read admin:write";
public static final int DEFAULT_VIDEO_CACHE_MB = 100;
public static final int LED_COLOUR = 0;
public static final String SCHEDULE_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
public static final String PREF_USER_TOKEN = "PREF_USER_TOKEN";
public static final String PREF_USER_ID = "PREF_USER_ID";
public static final String PREF_USER_INSTANCE = "PREF_USER_INSTANCE";
public static final String PREF_USER_INSTANCE_PEERTUBE_BROWSING = "PREF_USER_INSTANCE_PEERTUBE_BROWSING";
public static final String PREF_USER_SOFTWARE = "PREF_USER_SOFTWARE";
public static final String PREF_IS_MODERATOR = "PREF_IS_MODERATOR";
public static final String PREF_IS_ADMINISTRATOR = "PREF_IS_ADMINISTRATOR";
public static final String PREF_MESSAGE_URL = "PREF_MESSAGE_URL";
public static final String SET_SECURITY_PROVIDER = "SET_SECURITY_PROVIDER";
public static final int NOTIFICATION_INTENT = 1;
public static final int OPEN_NOTIFICATION = 2;
public static final int OPEN_WITH_ANOTHER_ACCOUNT = 3;
public static final String INTENT_TARGETED_ACCOUNT = "INTENT_TARGETED_ACCOUNT";
public static final String INTENT_TARGETED_STATUS = "INTENT_TARGETED_STATUS";
public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE";
public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE";
public static final String TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY";
public static final String TEMP_EXPORT_DATA = "TEMP_EXPORT_DATA";
public static final int EXTERNAL_STORAGE_REQUEST_CODE = 84;
public static final int EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE = 85;
public static final int EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SHARE = 86;
//Some regex
/*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 Pattern groupPattern = Pattern.compile("(![\\w_]+)");
public static final Pattern mentionPattern = Pattern.compile("(@[\\w_.-]?[\\w]+)");
public static final Pattern mentionLongPattern = Pattern.compile("(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)");
public static final Pattern mentionPatternALL = Pattern.compile("(@[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)|(@[\\w_.-]?[\\w]+)");
public static final Pattern mathsPattern = Pattern.compile("\\\\\\(|\\\\\\[");
public static final Pattern mathsComposePattern = Pattern.compile("\\\\\\(.*\\\\\\)|\\\\\\[.*\\\\\\]");
public static final Pattern twitterPattern = Pattern.compile("((@[\\w]+)@twitter\\.com)");
public static final Pattern youtubePattern = Pattern.compile("(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/(((?!([\"'<])).)*)");
public static final Pattern nitterPattern = Pattern.compile("(mobile\\.|www\\.)?twitter\\.com([\\w/-]+)");
public static final Pattern bibliogramPattern = Pattern.compile("(m\\.|www\\.)?instagram.com(/p/[\\w-/]+)");
public static final Pattern libredditPattern = Pattern.compile("(www\\.|m\\.)?(reddit\\.com|preview\\.redd\\.it|i\\.redd\\.it|redd\\.it)/(((?!([\"'<])).)*)");
public static final Pattern ouichesPattern = Pattern.compile("https?://ouich\\.es/tag/(\\w+)");
public static final Pattern geminiPattern = Pattern.compile("(gemini://.*)\\b");
public static final Pattern xmppPattern = Pattern.compile("xmpp:[-a-zA-Z0-9+$&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
public static final Pattern peertubePattern = Pattern.compile("(https?://([\\da-z.-]+\\.[a-z.]{2,10}))/videos/watch/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})$");
public static final Pattern mediumPattern = Pattern.compile("([\\w@-]*)?\\.?medium.com/@?([/\\w-]+)");
public static final Pattern wikipediaPattern = Pattern.compile("([\\w_-]+)\\.wikipedia.org/(((?!([\"'<])).)*)");
public static final Pattern codePattern = Pattern.compile("code=([\\w-]+)");
public static final Pattern nitterIDPattern = Pattern.compile("/status/(\\d+)");
public static final Pattern emailPattern = Pattern.compile("(\\s+[\\w_.-]+@[a-zA-Z0-9][a-zA-Z0-9.-]{1,61}[a-zA-Z0-9](?:\\.[a-zA-Z]{2,})+)");
public static final Pattern statusIdInUrl = Pattern.compile("statuses/(\\w+)");
/*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 urlPatternSimple = Pattern.compile(
"https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);*/
public static final Pattern aLink = Pattern.compile("<a((?!href).)*href=\"([^\"]*)\"[^>]*(((?!</a).)*)</a>");
public static final Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>");
// --- Static Map of patterns used in spannable status content
public static final Map<PatternType, Pattern> patternHashMap;
public static final int NOTIFICATION_MEDIA = 451;
public static final int NOTIFICATION_USER_NOTIF = 411;
public static final int NOTIFICATION_THEMING = 412;
/*
* List from ClearUrls
* https://gitlab.com/KevinRoebert/ClearUrls/blob/master/data/data.min.json#L106
*/
private static final String[] UTM_PARAMS = {
"utm_\\w+",
"ga_source",
"ga_medium",
"ga_term",
"ga_content",
"ga_campaign",
"ga_place",
"yclid",
"_openstat",
"fb_action_ids",
"fb_action_types",
"fb_source",
"fb_ref",
"fbclid",
"action_object_map",
"action_type_map",
"action_ref_map",
"gs_l",
"mkt_tok",
"hmb_campaign",
"hmb_medium",
"hmb_source",
"[\\?|&]ref[\\_]?"
};
public static int counter = 1;
private static int notificationId = 1;
//Allow to store in shared preference first visible fragment when the app starts
private static String slugOfFirstFragment;
private static BaseAccount baseAccount;
static {
LinkedHashMap<PatternType, Pattern> aMap = new LinkedHashMap<>();
aMap.put(PatternType.MENTION_LONG, mentionLongPattern);
aMap.put(PatternType.MENTION, mentionPattern);
aMap.put(PatternType.TAG, hashtagPattern);
aMap.put(PatternType.GROUP, groupPattern);
patternHashMap = Collections.unmodifiableMap(aMap);
}
/**
* 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 MaterialAlertDialogBuilder(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();
}
public static void colorizeIconMenu(Menu menu, int toolbarIconsColor) {
final PorterDuffColorFilter colorFilter
= new PorterDuffColorFilter(toolbarIconsColor, PorterDuff.Mode.MULTIPLY);
for (int i = 0; i < menu.size(); i++) {
MenuItem v = menu.getItem(i);
v.getIcon().setColorFilter(colorFilter);
}
}
/***
* Check if the user is connected to Internet
* @return boolean
*/
public static BaseMainActivity.status isConnectedToInternet(Context context, String instance) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null)
return BaseMainActivity.status.CONNECTED;
NetworkInfo ni = cm.getActiveNetworkInfo();
if (ni != null && ni.isConnected()) {
try {
InetAddress ipAddr = InetAddress.getByName(instance);
return (!ipAddr.toString().equals("")) ? BaseMainActivity.status.CONNECTED : BaseMainActivity.status.DISCONNECTED;
} catch (Exception e) {
try {
InetAddress ipAddr = InetAddress.getByName("mastodon.social");
return (!ipAddr.toString().equals("")) ? BaseMainActivity.status.CONNECTED : BaseMainActivity.status.DISCONNECTED;
} catch (Exception ex) {
return BaseMainActivity.status.DISCONNECTED;
}
}
} else {
return BaseMainActivity.status.DISCONNECTED;
}
}
/**
* Returns boolean depending if the user is authenticated
*
* @param context Context
* @return boolean
*/
public static boolean isLoggedIn(Context context) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
String prefKeyOauthTokenT = sharedpreferences.getString(PREF_USER_TOKEN, null);
return (prefKeyOauthTokenT != null);
}
/***
* Returns a String depending of the date
* @param context Context
* @param date Date
* @return String
*/
public static String dateDiff(Context context, Date date) {
if (date == null) {
date = new Date();
}
Date now = new Date();
long diff = now.getTime() - date.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;
if (years > 0) {
return DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(date);
} 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(date);
} 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 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 a date in String
*
* @param date Date
* @return String
*/
public static String longDateToString(Date date) {
if (date == null) {
date = new Date();
}
DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault());
return df.format(date);
}
/**
* Convert a date in String
*
* @param date Date
* @return String
*/
public static String mediumDateToString(Date date) {
if (date == null) {
date = new Date();
}
DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());
return df.format(date);
}
/**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss
*
* @param date Date
* @return String
*/
public static String shortDateToString(Date date) {
if (date == null) {
date = new Date();
}
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault());
return df.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;
}
/**
* Convert String date from db to Date Object
*
* @param stringDate date to convert
* @return Date
*/
public static Date stringToDateWithFormat(Context context, String stringDate, String format) {
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(format, userLocale);
Date date = null;
try {
date = dateFormat.parse(stringDate);
} catch (java.text.ParseException ignored) {
}
return date;
}
/**
* Converts dp to pixel
*
* @param dp float - the value in dp to convert
* @param context Context
* @return float - the converted value in pixel
*/
public static float convertDpToPixel(float dp, Context context) {
Resources resources = context.getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return dp * ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
/**
* 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) {
if (url == null) {
return;
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean customTab = sharedpreferences.getBoolean(context.getString(R.string.SET_CUSTOM_TABS), true);
if (customTab) {
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
int colorInt = ThemeHelper.getAttColor(context, R.attr.statusBar);
CustomTabColorSchemeParams defaultColors = new CustomTabColorSchemeParams.Builder()
.setToolbarColor(colorInt)
.build();
builder.setDefaultColorSchemeParams(defaultColors);
CustomTabsIntent customTabsIntent = builder.build();
try {
customTabsIntent.launchUrl(context, Uri.parse(url).normalizeScheme());
} catch (Exception e) {
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);
if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://") && !url.toLowerCase().startsWith("gemini://")) {
url = "http://" + url;
}
intent.setData(Uri.parse(url).normalizeScheme());
try {
context.startActivity(intent);
} catch (Exception e) {
Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show();
}
}
}
/**
* Transform URLs to privacy frontend
*
* @param context Context
* @param url String
*/
public static String transformURL(Context context, String url) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
Matcher matcher;
boolean nitter = Helper.getSharedValue(context, context.getString(R.string.SET_NITTER));
if (nitter) {
matcher = Helper.nitterPattern.matcher(url);
if (matcher.find()) {
final String nitter_directory = matcher.group(2);
String nitterHost = sharedpreferences.getString(context.getString(R.string.SET_NITTER_HOST), context.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase();
if (nitterHost.trim().isEmpty()) {
nitterHost = context.getString(R.string.DEFAULT_NITTER_HOST);
}
return "https://" + nitterHost + nitter_directory;
}
}
boolean bibliogram = Helper.getSharedValue(context, context.getString(R.string.SET_BIBLIOGRAM));
if (bibliogram) {
matcher = Helper.bibliogramPattern.matcher(url);
if (matcher.find()) {
final String bibliogram_directory = matcher.group(2);
String bibliogramHost = sharedpreferences.getString(context.getString(R.string.SET_BIBLIOGRAM_HOST), context.getString(R.string.DEFAULT_BIBLIOGRAM_HOST)).toLowerCase();
if (bibliogramHost.trim().isEmpty()) {
bibliogramHost = context.getString(R.string.DEFAULT_BIBLIOGRAM_HOST);
}
return "https://" + bibliogramHost + bibliogram_directory;
}
}
boolean libreddit = Helper.getSharedValue(context, context.getString(R.string.SET_LIBREDDIT));
if (libreddit) {
matcher = Helper.libredditPattern.matcher(url);
if (matcher.find()) {
final String libreddit_directory = matcher.group(3);
String libreddit_host = sharedpreferences.getString(context.getString(R.string.SET_LIBREDDIT_HOST), context.getString(R.string.DEFAULT_LIBREDDIT_HOST)).toLowerCase();
if (libreddit_host.trim().isEmpty()) {
libreddit_host = context.getString(R.string.DEFAULT_LIBREDDIT_HOST);
}
return "https://" + libreddit_host + "/" + libreddit_directory;
}
}
boolean invidious = Helper.getSharedValue(context, context.getString(R.string.SET_INVIDIOUS));
if (invidious) {
matcher = Helper.youtubePattern.matcher(url);
if (matcher.find()) {
final String youtubeId = matcher.group(3);
String invidiousHost = sharedpreferences.getString(context.getString(R.string.SET_INVIDIOUS_HOST), context.getString(R.string.DEFAULT_INVIDIOUS_HOST)).toLowerCase();
if (invidiousHost.trim().isEmpty()) {
invidiousHost = context.getString(R.string.DEFAULT_INVIDIOUS_HOST);
}
if (matcher.group(2) != null && Objects.equals(matcher.group(2), "youtu.be")) {
return "https://" + invidiousHost + "/watch?v=" + youtubeId + "&local=true";
} else {
return "https://" + invidiousHost + "/" + youtubeId + "&local=true";
}
}
}
boolean medium = Helper.getSharedValue(context, context.getString(R.string.REPLACE_MEDIUM));
if (medium) {
matcher = Helper.mediumPattern.matcher(url);
if (matcher.find()) {
String path = matcher.group(2);
String user = matcher.group(1);
if (user != null && user.length() > 0 & !user.equals("www")) {
path = user + "/" + path;
}
String mediumReplaceHost = sharedpreferences.getString(context.getString(R.string.REPLACE_MEDIUM_HOST), context.getString(R.string.DEFAULT_REPLACE_MEDIUM_HOST)).toLowerCase();
if (mediumReplaceHost.trim().isEmpty()) {
mediumReplaceHost = context.getString(R.string.DEFAULT_REPLACE_MEDIUM_HOST);
}
return "https://" + mediumReplaceHost + "/" + path;
}
}
boolean wikipedia = Helper.getSharedValue(context, context.getString(R.string.REPLACE_WIKIPEDIA));
if (wikipedia) {
matcher = Helper.wikipediaPattern.matcher(url);
if (matcher.find()) {
String subdomain = matcher.group(1);
String path = matcher.group(2);
String wikipediaReplaceHost = sharedpreferences.getString(context.getString(R.string.REPLACE_WIKIPEDIA_HOST), context.getString(R.string.DEFAULT_REPLACE_WIKIPEDIA_HOST)).toLowerCase();
String lang = "";
if (path != null && subdomain != null && !subdomain.equals("www")) {
lang = (path.contains("?")) ? TextUtils.htmlEncode("&") : "?";
lang = lang + "lang=" + subdomain;
}
if (wikipediaReplaceHost.trim().isEmpty()) {
wikipediaReplaceHost = context.getString(R.string.DEFAULT_REPLACE_WIKIPEDIA_HOST);
}
return "https://" + wikipediaReplaceHost + "/" + path + lang;
}
}
boolean filterUTM = Helper.getSharedValue(context, context.getString(R.string.SET_FILTER_UTM));
if (filterUTM) {
return remove_tracking_param(context, url);
}
return url;
}
/**
* Remove tracking parameters
*
* @param context - Context
* @param original_content - String original URL
* @return cleaned URL
*/
private static String remove_tracking_param(Context context, String original_content) {
if (original_content == null)
return original_content;
String cleaned_content = original_content;
for (String utm : UTM_PARAMS) {
cleaned_content = cleaned_content.replaceAll("&amp;" + utm + "=[0-9a-zA-Z._-]*", "");
cleaned_content = cleaned_content.replaceAll("&" + utm + "=[0-9a-zA-Z._-]*", "");
cleaned_content = cleaned_content.replaceAll("\\?" + utm + "=[0-9a-zA-Z._-]*", "?");
}
if (cleaned_content.endsWith("?")) {
cleaned_content = cleaned_content.substring(0, cleaned_content.length() - 1);
}
return cleaned_content;
}
@SuppressLint("DefaultLocale")
public static String withSuffix(long count) {
if (count < 1000) return String.valueOf(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("%.1f %c",
count / Math.pow(1000, exp),
"kMGTPE".charAt(exp - 1));
}
/**
* Send a toast message to main activity
*
* @param context Context
* @param type String - type of the toast (error, warning, info, success)
* @param content String - message of the toast
*/
public static void sendToastMessage(Context context, String type, String content) {
Intent intentBC = new Intent(context, ToastMessage.class);
Bundle args = new Bundle();
args.putString(RECEIVE_TOAST_TYPE, type);
args.putString(RECEIVE_TOAST_CONTENT, content);
intentBC.setAction(Helper.RECEIVE_TOAST_MESSAGE);
new CachedBundle(context).insertBundle(args, Helper.getCurrentAccount(context), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBC.putExtras(bundle);
intentBC.setPackage(BuildConfig.APPLICATION_ID);
context.sendBroadcast(intentBC);
});
}
/**
* @param fragmentManager Fragment Manager
* @param containerViewId Id of the fragment container
* @param fragment Fragment to be added
* @param args Arguments to pass to the new fragment. null for none
* @param tag Tag to pass to the fragment
* @param backStackName An optional name to use when adding to back stack, or null.
*/
public static Fragment addFragment(@NonNull FragmentManager fragmentManager,
@IdRes int containerViewId,
@NonNull Fragment fragment,
@Nullable Bundle args,
@Nullable String tag,
@Nullable String backStackName) {
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit);
Fragment _fragment = fragmentManager.findFragmentByTag(tag);
if (_fragment != null && _fragment.isAdded()) {
ft.show(_fragment).commit();
fragment = _fragment;
} else {
if (args != null) fragment.setArguments(args);
ft = fragmentManager.beginTransaction();
ft.add(containerViewId, fragment, tag);
if (backStackName != null) {
try {
ft.addToBackStack(backStackName);
}catch (Exception ignored){}
}
if (!fragmentManager.isDestroyed()) {
ft.commit();
}
}
fragmentManager.executePendingTransactions();
return fragment;
}
/**
* Load a media into a view
*
* @param view ImageView - the view where the image will be loaded
* @param url - String
*/
public static void loadImage(ImageView view, String url) {
Context context = view.getContext();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false);
if (disableGif || (!url.endsWith(".gif"))) {
Glide.with(view.getContext())
.asDrawable()
.load(url)
.thumbnail(0.1f)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
} else {
Glide.with(view.getContext())
.asGif()
.load(url)
.thumbnail(0.1f)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
}
}
/**
* Log out the authenticated user by removing its token
*
* @param activity Activity
* @throws DBException Exception
*/
public static void removeAccount(Activity activity) throws DBException {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
//Current user
String userId = sharedpreferences.getString(PREF_USER_ID, null);
String instance = sharedpreferences.getString(PREF_USER_INSTANCE, null);
Account accountDB = new Account(activity);
OauthVM oauthVM = new ViewModelProvider((ViewModelStoreOwner) activity).get(OauthVM.class);
if (Helper.getCurrentAccount(activity) != null) {
//Revoke the token
oauthVM.revokeToken(Helper.getCurrentAccount(activity).instance, Helper.getCurrentAccount(activity).token, Helper.getCurrentAccount(activity).client_id, Helper.getCurrentAccount(activity).client_secret);
//Log out the current user
accountDB.removeUser(Helper.getCurrentAccount(activity));
}
BaseAccount newAccount = accountDB.getLastUsedAccount();
SharedPreferences.Editor editor = sharedpreferences.edit();
if (newAccount == null) {
editor.putString(PREF_USER_TOKEN, null);
editor.putString(PREF_USER_INSTANCE, null);
editor.putString(PREF_USER_SOFTWARE, null);
editor.putString(PREF_USER_ID, null);
editor.commit();
Intent loginActivity = new Intent(activity, LoginActivity.class);
activity.startActivity(loginActivity);
activity.finish();
} else {
Helper.setCurrentAccount(newAccount);
editor.putString(PREF_USER_TOKEN, newAccount.token);
editor.putString(PREF_USER_SOFTWARE, newAccount.software);
editor.putString(PREF_USER_INSTANCE, newAccount.instance);
editor.putString(PREF_USER_ID, newAccount.user_id);
BaseMainActivity.currentUserID = newAccount.user_id;
BaseMainActivity.currentToken = newAccount.token;
BaseMainActivity.currentInstance = newAccount.instance;
editor.commit();
Intent changeAccount = new Intent(activity, MainActivity.class);
changeAccount.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
activity.startActivity(changeAccount);
}
}
/**
* Check if the app is not finishing
*
* @param context - Context
* @return boolean - context is valid and image can be loaded
*/
public static boolean isValidContextForGlide(final Context context) {
if (context == null) {
return false;
}
if (context instanceof Activity activity) {
return !activity.isDestroyed() && !activity.isFinishing();
}
return true;
}
/**
* Get filename from uri
*
* @param context Context
* @param uri Uri
* @return String
*/
public static String getFileName(Context context, Uri uri) {
ContentResolver resolver = context.getContentResolver();
Cursor returnCursor = null;
try {
returnCursor =
resolver.query(uri, null, null, null, null);
} catch (Exception ignored) {
}
if (returnCursor != null) {
try {
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
String name = returnCursor.getString(nameIndex);
returnCursor.close();
Random r = new Random();
int suf = r.nextInt(9999 - 1000) + 1000;
return suf + name;
} catch (Exception e) {
Random r = new Random();
int suf = r.nextInt(9999 - 1000) + 1000;
ContentResolver cr = context.getContentResolver();
String mime = cr.getType(uri);
if (mime != null && mime.split("/").length > 1)
return "__" + suf + "." + mime.split("/")[1];
else
return "__" + suf + ".jpg";
}
} else {
Random r = new Random();
int suf = r.nextInt(9999 - 1000) + 1000;
ContentResolver cr = context.getContentResolver();
String mime = cr.getType(uri);
if (mime != null && mime.split("/").length > 1)
return "__" + suf + "." + mime.split("/")[1];
else
return "__" + suf + ".jpg";
}
}
/**
* Return shared value
*
* @param context Context
* @param type String
* @return boolean
*/
public static boolean getSharedValue(Context context, String type) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
if (type.compareTo(context.getString(R.string.SET_INVIDIOUS)) == 0) {
return sharedpreferences.getBoolean(type, false);
} else if (type.compareTo(context.getString(R.string.SET_BIBLIOGRAM)) == 0) {
return sharedpreferences.getBoolean(type, false);
} else if (type.compareTo(context.getString(R.string.SET_NITTER)) == 0) {
return sharedpreferences.getBoolean(type, false);
} else if (type.compareTo(context.getString(R.string.REPLACE_MEDIUM)) == 0) {
return sharedpreferences.getBoolean(type, false);
} else if (type.compareTo(context.getString(R.string.REPLACE_WIKIPEDIA)) == 0) {
return sharedpreferences.getBoolean(type, false);
}
return sharedpreferences.getBoolean(type, false);
}
/**
* Get size from uri
*
* @param context Context
* @param uri Uri - uri to check
* @return long - file size
*/
public static long getRealSizeFromUri(Context context, Uri uri) {
Cursor cursor = null;
try {
String[] proj = {MediaStore.Audio.Media.SIZE};
cursor = context.getContentResolver().query(uri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE);
cursor.moveToFirst();
return Long.parseLong(cursor.getString(column_index));
} catch (Exception e) {
return 0;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/**
* Load a profile picture for the account
*
* @param view ImageView - the view where the image will be loaded
* @param account - {@link Account}
*/
public static void loadPP(Activity activity, ImageView view, BaseAccount account) {
loadPP(activity, view, account, false);
}
/**
* Load a profile picture for the account
*
* @param view ImageView - the view where the image will be loaded
* @param account - {@link Account}
*/
public static void loadPP(Activity activity, ImageView view, BaseAccount account, boolean crop) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
boolean disableGif = sharedpreferences.getBoolean(activity.getString(R.string.SET_DISABLE_GIF), false);
String targetedUrl = "";
if (account.mastodon_account != null) {
targetedUrl = disableGif ? account.mastodon_account.avatar_static : account.mastodon_account.avatar;
} else if (account.peertube_account != null) {
if (account.peertube_account.getAvatar() != null) {
targetedUrl = account.peertube_account.getAvatar().getPath();
if (targetedUrl != null && targetedUrl.startsWith("/")) {
targetedUrl = "https://" + account.instance + account.peertube_account.getAvatar().getPath();
}
} else {
BitmapDrawable avatar = new AvatarGenerator.AvatarBuilder(activity)
.setLabel(account.peertube_account.getAcct())
.setAvatarSize(120)
.setTextSize(30)
.toSquare()
.setBackgroundColor(fetchAccentColor(activity))
.build();
if (Helper.isValidContextForGlide(activity)) {
Glide.with(activity)
.asDrawable()
.load(avatar)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
}
return;
}
}
if (targetedUrl != null && Helper.isValidContextForGlide(activity)) {
if (disableGif || (!targetedUrl.endsWith(".gif"))) {
try {
RequestBuilder<Drawable> requestBuilder = Glide.with(activity)
.asDrawable()
.load(targetedUrl)
.thumbnail(0.1f);
if (crop) {
requestBuilder = requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)));
}
requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))).into(view);
} catch (Exception ignored) {
}
} else {
RequestBuilder<GifDrawable> requestBuilder = Glide.with(activity)
.asGif()
.load(targetedUrl)
.thumbnail(0.1f);
if (crop) {
requestBuilder = requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)));
}
requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))).into(view);
}
} else if (Helper.isValidContextForGlide(activity)) {
Glide.with(activity)
.asDrawable()
.load(R.drawable.ic_person)
.thumbnail(0.1f)
.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10)))
.into(view);
}
}
public static Proxy getProxy(Context context) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
String hostVal = sharedpreferences.getString(context.getString(R.string.SET_PROXY_HOST), "127.0.0.1");
int portVal = sharedpreferences.getInt(context.getString(R.string.SET_PROXY_PORT), 8118);
final String login = sharedpreferences.getString(context.getString(R.string.SET_PROXY_LOGIN), null);
final String pwd = sharedpreferences.getString(context.getString(R.string.SET_PROXY_PASSWORD), null);
final int type = sharedpreferences.getInt(context.getString(R.string.SET_PROXY_TYPE), 0);
boolean enable_proxy = sharedpreferences.getBoolean(context.getString(R.string.SET_PROXY_ENABLED), false);
if (!enable_proxy) {
return null;
}
Proxy proxy = new Proxy(type == 0 ? Proxy.Type.HTTP : Proxy.Type.SOCKS,
InetSocketAddress.createUnresolved(hostVal, portVal));
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestingHost().equalsIgnoreCase(hostVal)) {
if (portVal == getRequestingPort()) {
return new PasswordAuthentication(login, pwd.toCharArray());
}
}
return null;
}
});
return proxy;
}
/***
* Convert Uri to byte[]
* @param context Context
* @param uri Uri
* @return byte[]
*/
public static byte[] uriToByteArray(Context context, Uri uri) {
byte[] buffer = null;
try {
InputStream iStream = context.getContentResolver().openInputStream(uri);
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
buffer = new byte[bufferSize];
int len;
while ((len = iStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
return byteBuffer.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return buffer;
}
/**
* Creates MultipartBody.Part from Uri
*
* @return MultipartBody.Part for the given Uri
*/
public static MultipartBody.Part getMultipartBody(@NonNull String paramName, @NonNull Attachment attachment) {
RequestBody requestFile = RequestBody.create(MediaType.parse(attachment.mimeType), new File(attachment.local_path));
return MultipartBody.Part.createFormData(paramName, attachment.filename, requestFile);
}
/**
* Creates MultipartBody.Part from Uri
*
* @return MultipartBody.Part for the given Uri
*/
public static MultipartBody.Part getMultipartBodyWithWM(Context context, String waterMark, @NonNull String paramName, @NonNull Attachment attachment) {
File files = new File(attachment.local_path);
float textSize = 15;
Paint paint = new Paint();
float width = paint.measureText(waterMark, 0, waterMark.length());
try {
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap backgroundBitmap = BitmapFactory.decodeFile(files.getAbsolutePath(), options);
int w = backgroundBitmap.getWidth();
int h = backgroundBitmap.getHeight();
float valx = (float) 1.0 - ((Helper.convertDpToPixel(width, context) + 10)) / (float) w;
if (valx < 0)
valx = 0;
float valy = (h - Helper.convertDpToPixel(textSize, context) - 0) / (float) h;
WatermarkText watermarkText = new WatermarkText(waterMark)
.setPositionX(valx)
.setPositionY(valy)
.setTextColor(Color.WHITE)
.setTextShadow(0.1f, 1, 1, Color.LTGRAY)
.setTextAlpha(200)
.setRotation(0)
.setTextSize(textSize);
Bitmap bitmap = WatermarkBuilder
.create(context, backgroundBitmap)
.loadWatermarkText(watermarkText)
.getWatermark()
.getOutputImage();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
byte[] bitmapdata = bos.toByteArray();
RequestBody requestFile = RequestBody.create(MediaType.parse(attachment.mimeType), bitmapdata);
return MultipartBody.Part.createFormData(paramName, attachment.filename, requestFile);
} catch (Exception e) {
e.printStackTrace();
}
RequestBody requestFile = RequestBody.create(MediaType.parse(attachment.mimeType), new File(attachment.local_path));
return MultipartBody.Part.createFormData(paramName, attachment.filename, requestFile);
}
public static MultipartBody.Part getMultipartBody(Context context, @NonNull String paramName, @NonNull Uri uri) {
byte[] imageBytes = uriToByteArray(context, uri);
ContentResolver cR = context.getApplicationContext().getContentResolver();
String mimeType = cR.getType(uri);
RequestBody requestFile = RequestBody.create(MediaType.parse(mimeType), imageBytes);
return MultipartBody.Part.createFormData(paramName, Helper.getFileName(context, uri), requestFile);
}
public static void createAttachmentFromUri(Context context, List<Uri> uris, OnAttachmentCopied callBack) {
new Thread(() -> {
List<Attachment> attachments = new ArrayList<>();
for (Uri uri : uris) {
Attachment attachment = new Attachment();
attachment.filename = Helper.getFileName(context, uri);
attachment.size = Helper.getRealSizeFromUri(context, uri);
ContentResolver cR = context.getApplicationContext().getContentResolver();
attachment.mimeType = cR.getType(uri);
MimeTypeMap mime = MimeTypeMap.getSingleton();
String extension = mime.getExtensionFromMimeType(cR.getType(uri));
if (uri.toString().endsWith("fedilab_recorded_audio.ogg")) {
extension = "ogg";
attachment.mimeType = "audio/ogg";
}
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_" + counter, Locale.getDefault());
counter++;
Date now = new Date();
attachment.filename = formatter.format(now) + "." + extension;
Set<String> imageType = new HashSet<>(Arrays.asList("image/png", "image/jpeg", "image/jpg"));
if (imageType.contains(attachment.mimeType)) {
final File certCacheDir = new File(context.getCacheDir(), TEMP_MEDIA_DIRECTORY);
boolean isCertCacheDirExists = certCacheDir.exists();
if (!isCertCacheDirExists) {
certCacheDir.mkdirs();
}
String filePath = certCacheDir.getAbsolutePath() + "/" + attachment.filename;
MediaHelper.ResizedImageRequestBody(context, uri, new File(filePath));
attachment.local_path = filePath;
} else {
InputStream selectedFileInputStream;
try {
selectedFileInputStream = context.getContentResolver().openInputStream(uri);
if (selectedFileInputStream != null) {
final File certCacheDir = new File(context.getCacheDir(), TEMP_MEDIA_DIRECTORY);
boolean isCertCacheDirExists = certCacheDir.exists();
if (!isCertCacheDirExists) {
isCertCacheDirExists = certCacheDir.mkdirs();
}
if (isCertCacheDirExists) {
String filePath = certCacheDir.getAbsolutePath() + "/" + attachment.filename;
attachment.local_path = filePath;
OutputStream selectedFileOutPutStream = new FileOutputStream(filePath);
byte[] buffer = new byte[1024];
int length;
while ((length = selectedFileInputStream.read(buffer)) > 0) {
selectedFileOutPutStream.write(buffer, 0, length);
}
selectedFileOutPutStream.flush();
selectedFileOutPutStream.close();
}
selectedFileInputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
attachments.add(attachment);
}
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> callBack.onAttachmentCopied(attachments);
mainHandler.post(myRunnable);
}).start();
}
public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) {
new Thread(() -> {
InputStream selectedFileInputStream;
File file = null;
try {
String uriFullPath = uri.getPath();
String[] uriFullPathStr = uriFullPath.split(":");
String fullPath = uriFullPath;
if (uriFullPathStr.length > 1) {
fullPath = uriFullPathStr[1];
}
final String fileName = Helper.dateFileToString(context, new Date()) + ".zip";
selectedFileInputStream = context.getContentResolver().openInputStream(uri);
if (selectedFileInputStream != null) {
final File certCacheDir = new File(context.getCacheDir(), TEMP_EXPORT_DATA);
boolean isCertCacheDirExists = certCacheDir.exists();
if (!isCertCacheDirExists) {
isCertCacheDirExists = certCacheDir.mkdirs();
}
if (isCertCacheDirExists) {
String filePath = certCacheDir.getAbsolutePath() + "/" + fileName;
file = new File(filePath);
OutputStream selectedFileOutPutStream = new FileOutputStream(filePath);
byte[] buffer = new byte[1024];
int length;
while ((length = selectedFileInputStream.read(buffer)) > 0) {
selectedFileOutPutStream.write(buffer, 0, length);
}
selectedFileOutPutStream.flush();
selectedFileOutPutStream.close();
}
selectedFileInputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
Handler mainHandler = new Handler(Looper.getMainLooper());
File finalFile = file;
Runnable myRunnable = () -> callBack.onFileCopied(finalFile);
mainHandler.post(myRunnable);
}).start();
}
public static void createAttachmentFromPAth(String path, OnAttachmentCopied callBack) {
new Thread(() -> {
List<Attachment> attachmentList = new ArrayList<>();
Attachment attachment = new Attachment();
attachment.mimeType = "image/*";
String extension = "jpg";
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_" + counter, Locale.getDefault());
attachment.local_path = path;
Date now = new Date();
attachment.filename = formatter.format(now) + "." + extension;
Handler mainHandler = new Handler(Looper.getMainLooper());
attachmentList.add(attachment);
Runnable myRunnable = () -> callBack.onAttachmentCopied(attachmentList);
mainHandler.post(myRunnable);
}).start();
}
/**
* change color of a drawable
*
* @param imageView int the ImageView
* @param hexaColor example 0xffff00
*/
public static void changeDrawableColor(Context context, ImageView imageView, int hexaColor) {
if (imageView == null)
return;
int color;
try {
color = context.getResources().getColor(hexaColor);
} catch (Resources.NotFoundException e) {
color = hexaColor;
}
imageView.setColorFilter(color);
}
/**
* 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;
}
/**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss
*
* @param context Context
* @param date Date
* @return String
*/
public static String dateFileToString(Context context, Date date) {
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);
return dateFormat.format(date);
}
/**
* Change locale
*
* @param activity - Activity
*/
public static void setLocale(Activity activity) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
String defaultLocaleString = sharedpreferences.getString(activity.getString(R.string.SET_DEFAULT_LOCALE_NEW), null);
if (defaultLocaleString != null) {
Locale locale;
if (defaultLocaleString.equals("zh-CN")) {
locale = Locale.SIMPLIFIED_CHINESE;
} else if (defaultLocaleString.equals("zh-TW")) {
locale = Locale.TRADITIONAL_CHINESE;
} else {
locale = new Locale(defaultLocaleString);
}
Locale.setDefault(locale);
Resources resources = activity.getResources();
Configuration config = resources.getConfiguration();
config.setLocale(locale);
resources.updateConfiguration(config, resources.getDisplayMetrics());
}
}
/**
* Send broadcast to recreate Mainactivity
*
* @param activity - Activity
*/
public static void recreateMainActivity(Activity activity) {
Bundle args = new Bundle();
args.putBoolean(Helper.RECEIVE_RECREATE_ACTIVITY, true);
Intent intentBD = new Intent(Helper.BROADCAST_DATA);
new CachedBundle(activity).insertBundle(args, Helper.getCurrentAccount(activity), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intentBD.putExtras(bundle);
intentBD.setPackage(BuildConfig.APPLICATION_ID);
activity.sendBroadcast(intentBD);
});
}
public static void showKeyboard(Context context, View view) {
view.requestFocus();
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
/**
* Sends notification with intent
*
* @param context Context
* @param intent Intent associated to the notifcation
* @param icon Bitmap profile picture
* @param title String title of the notification
* @param message String message for the notification
*/
@SuppressLint("UnspecifiedImmutableFlag")
public static void notify_user(Context context, BaseAccount account, Intent intent, Bitmap icon, NotifType notifType, String title, String message) {
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
// prepare intent which is triggered if the user click on the notification
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
int requestCode = (int) System.currentTimeMillis();
PendingIntent pIntent;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
pIntent = PendingIntent.getActivity(context, requestCode, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT);
} else {
pIntent = PendingIntent.getActivity(context, requestCode, intent, PendingIntent.FLAG_ONE_SHOT);
}
intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
// build notification
String channelId;
String channelTitle;
switch (notifType) {
case FAV -> {
channelId = "channel_favourite";
channelTitle = context.getString(R.string.channel_notif_fav);
}
case FOLLLOW -> {
channelId = "channel_follow";
channelTitle = context.getString(R.string.channel_notif_follow);
}
case MENTION -> {
channelId = "channel_mention";
channelTitle = context.getString(R.string.channel_notif_mention);
}
case POLL -> {
channelId = "channel_poll";
channelTitle = context.getString(R.string.channel_notif_poll);
}
case BACKUP -> {
channelId = "channel_backup";
channelTitle = context.getString(R.string.channel_notif_backup);
}
case STORE -> {
channelId = "channel_media";
channelTitle = context.getString(R.string.channel_notif_media);
}
case TOOT -> {
channelId = "channel_status";
channelTitle = context.getString(R.string.channel_notif_status);
}
case UPDATE -> {
channelId = "channel_update";
channelTitle = context.getString(R.string.channel_notif_update);
}
case SIGN_UP -> {
channelId = "channel_signup";
channelTitle = context.getString(R.string.channel_notif_signup);
}
case REPORT -> {
channelId = "channel_report";
channelTitle = context.getString(R.string.channel_notif_report);
}
default -> {
channelId = "channel_boost";
channelTitle = context.getString(R.string.channel_notif_boost);
}
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channelId)
.setSmallIcon(getNotificationIcon(context)).setTicker(message);
/* if (notifType == NotifType.MENTION) {
if (message.length() > 500) {
message = message.substring(0, 499) + "…";
}
}*/
notificationBuilder.setGroup(account.mastodon_account != null ? account.mastodon_account.username + "@" + account.instance : "@" + account.instance)
.setContentIntent(pIntent)
.setContentText(message);
int ledColour = Color.BLUE;
int prefColor;
prefColor = Integer.parseInt(sharedpreferences.getString(context.getString(R.string.SET_LED_COLOUR_VAL_N), String.valueOf(LED_COLOUR)));
switch (prefColor) {
case 1 -> // CYAN
ledColour = Color.CYAN;
case 2 -> // MAGENTA
ledColour = Color.MAGENTA;
case 3 -> // GREEN
ledColour = Color.GREEN;
case 4 -> // RED
ledColour = Color.RED;
case 5 -> // YELLOW
ledColour = Color.YELLOW;
case 6 -> // WHITE
ledColour = Color.WHITE;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel;
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_SILENT), false)) {
channel = new NotificationChannel(channelId, channelTitle, NotificationManager.IMPORTANCE_LOW);
channel.setSound(null, null);
channel.setVibrationPattern(new long[]{500, 500, 500});
channel.enableVibration(true);
channel.setLightColor(ledColour);
} else {
channel = new NotificationChannel(channelId, channelTitle, NotificationManager.IMPORTANCE_DEFAULT);
/*String soundUri = sharedpreferences.getString(context.getString(R.string.SET_NOTIF_SOUND), ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/" + R.raw.boop);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build();
channel.setSound(Uri.parse(soundUri), audioAttributes);*/
}
assert mNotificationManager != null;
mNotificationManager.createNotificationChannel(channel);
} else {
if (sharedpreferences.getBoolean(context.getString(R.string.SET_NOTIF_SILENT), false)) {
notificationBuilder.setVibrate(new long[]{500, 500, 500});
} else {
String soundUri = sharedpreferences.getString(context.getString(R.string.SET_NOTIF_SOUND), ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/" + R.raw.boop);
notificationBuilder.setSound(Uri.parse(soundUri));
}
notificationBuilder.setLights(ledColour, 500, 1000);
}
notificationBuilder.setContentTitle(title);
notificationBuilder.setLargeIcon(icon);
notificationBuilder.setSubText(String.format("@%s@%s", account.mastodon_account.username, account.instance));
Notification summaryNotification = new NotificationCompat.Builder(context, channelId)
.setContentTitle(title)
.setContentText(channelTitle)
.setContentIntent(pIntent)
.setLargeIcon(icon)
.setSmallIcon(getNotificationIcon(context))
.setStyle(new NotificationCompat.BigTextStyle().bigText(message))
.setGroup(account.mastodon_account != null ? account.mastodon_account.username + "@" + account.instance : "@" + account.instance)
.setGroupSummary(true)
.build();
notificationManager.notify(notificationId++, notificationBuilder.build());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
notificationManager.notify(0, summaryNotification);
}
}
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 dateDiffFullShort(Date dateToot) {
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault());
try {
return df.format(dateToot);
} catch (Exception e) {
return "";
}
}
/**
* Makes the tvDate TextView field clickable, and displays the absolute date & time of a toot
* for 5 seconds.
*
* @param context Context
* @param tvDate TextView
* @param date Date
*/
public static void absoluteDateTimeReveal(final Context context, final TextView tvDate, final Date date, final Date dateEdit) {
tvDate.setOnClickListener(v -> {
if (dateEdit == null) {
tvDate.setText(dateDiffFull(date));
} else {
String dateEditText = context.getString(R.string.full_date_edited, dateDiffFull(date), dateDiffFull(dateEdit));
tvDate.setText(dateEditText);
}
new CountDownTimer((5 * 1000), 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
tvDate.setText(dateDiff(context, date));
}
}.start();
});
}
public static String generateString() {
String uuid = UUID.randomUUID().toString();
return "@fedilab_fetch_more_" + uuid;
}
public static String generateIdString() {
String uuid = UUID.randomUUID().toString();
return "@fedilab_compose_" + uuid;
}
public static Gson getDateBuilder() {
SimpleDateFormat[] formats = new SimpleDateFormat[]{
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault()),
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()),
};
return new GsonBuilder()
.registerTypeAdapter(Date.class, new TypeAdapter<Date>() {
@Override
public void write(JsonWriter out, Date value) {
}
@Override
public Date read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
String dateAsString = reader.nextString();
for (SimpleDateFormat format : formats) {
try {
format.setTimeZone(TimeZone.getTimeZone("UTC"));
return format.parse(dateAsString);
} catch (ParseException ignored) {
}
}
return null;
}
})
.create();
}
/***
* Download method which works for http and https connections
* @param downloadUrl String download url
* @param listener OnDownloadInterface, listener which manages progress
*/
public static void download(Context context, final String downloadUrl, final OnDownloadInterface listener) {
new Thread(() -> {
URL url;
int CHUNK_SIZE = 4096;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean enable_proxy = sharedpreferences.getBoolean(context.getString(R.string.SET_PROXY_ENABLED), false);
Proxy proxy = null;
if (enable_proxy) {
proxy = getProxy(context);
}
try {
url = new URL(downloadUrl);
HttpURLConnection httpURLConnection;
if (proxy != null)
httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
else
httpURLConnection = (HttpURLConnection) url.openConnection();
// httpURLConnection.setRequestProperty("User-Agent", USER_AGENT);
int responseCode = httpURLConnection.getResponseCode();
// always check HTTP response code first
if (responseCode == HttpURLConnection.HTTP_OK) {
String fileName = "";
String disposition = httpURLConnection.getHeaderField("Content-Disposition");
if (disposition != null) {
// extracts file name from header field
int index = disposition.indexOf("filename=");
if (index > 0) {
fileName = disposition.substring(index + 10,
disposition.length() - 1);
}
} else {
// extracts file name from URL
fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/") + 1);
}
fileName = FileNameCleaner.cleanFileName(fileName);
// opens input stream from the HTTP connection
InputStream inputStream = httpURLConnection.getInputStream();
final File saveDir = new File(context.getCacheDir(), TEMP_MEDIA_DIRECTORY);
boolean isCertCacheDirExists = saveDir.exists();
if (!isCertCacheDirExists) {
saveDir.mkdirs();
}
final String saveFilePath = saveDir + File.separator + fileName;
// opens an output stream to save into file
FileOutputStream outputStream = new FileOutputStream(saveFilePath);
int bytesRead;
byte[] buffer = new byte[CHUNK_SIZE];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
((ComposeActivity) context).runOnUiThread(() -> listener.onDownloaded(saveFilePath, downloadUrl, null));
} else {
((ComposeActivity) context).runOnUiThread(() -> listener.onDownloaded(null, downloadUrl, new Error()));
}
} catch (IOException e) {
e.printStackTrace();
((ComposeActivity) context).runOnUiThread(() -> listener.onDownloaded(null, downloadUrl, new Error()));
}
}).start();
}
public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
for (Map.Entry<T, E> entry : map.entrySet()) {
if (Objects.equals(value, entry.getValue())) {
return entry.getKey();
}
}
return null;
}
public static void displayReleaseNotesIfNeeded(Activity activity, boolean forced) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
int lastReleaseNoteRead = sharedpreferences.getInt(activity.getString(R.string.SET_POPUP_RELEASE_NOTES), 0);
int versionCode = BuildConfig.VERSION_CODE;
boolean disabled = sharedpreferences.getBoolean(activity.getString(R.string.SET_DISABLE_RELEASE_NOTES_ALERT), false);
if (disabled && !forced) {
return;
}
if (lastReleaseNoteRead != versionCode || forced) {
try {
InputStream is = activity.getAssets().open("release_notes/notes.json");
int size;
size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
String json = new String(buffer, StandardCharsets.UTF_8);
Gson gson = new Gson();
AlertDialog.Builder dialogBuilderOptin = new MaterialAlertDialogBuilder(activity);
PopupReleaseNotesBinding binding = PopupReleaseNotesBinding.inflate(activity.getLayoutInflater());
dialogBuilderOptin.setView(binding.getRoot());
try {
List<ReleaseNote.Note> releaseNotes = gson.fromJson(json, new TypeToken<List<ReleaseNote.Note>>() {
}.getType());
if (releaseNotes != null && releaseNotes.size() > 0) {
ReleaseNoteAdapter adapter = new ReleaseNoteAdapter(releaseNotes);
binding.releasenotes.setAdapter(adapter);
binding.releasenotes.setLayoutManager(new LinearLayoutManager(activity));
}
} catch (Exception ignored) {
}
if (BuildConfig.DONATIONS) {
binding.aboutSupport.setVisibility(View.VISIBLE);
binding.aboutSupportPaypal.setVisibility(View.VISIBLE);
} else {
binding.aboutSupport.setVisibility(View.GONE);
binding.aboutSupportPaypal.setVisibility(View.GONE);
}
binding.accountFollow.setIconResource(R.drawable.ic_baseline_person_add_24);
binding.aboutSupport.setOnClickListener(v -> {
Intent intentLiberapay = new Intent(Intent.ACTION_VIEW);
intentLiberapay.setData(Uri.parse("https://liberapay.com/tom79"));
try {
activity.startActivity(intentLiberapay);
} catch (Exception e) {
Helper.openBrowser(activity, "https://liberapay.com/tom79");
}
});
binding.aboutSupportPaypal.setOnClickListener(v -> Helper.openBrowser(activity, "https://www.paypal.me/Mastalab"));
CrossActionHelper.fetchRemoteAccount(activity, "@apps@toot.fedilab.app", new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override
public void federatedAccount(app.fedilab.android.mastodon.client.entities.api.Account account) {
if (account != null && account.username.equalsIgnoreCase("apps")) {
MastodonHelper.loadPPMastodon(binding.accountPp, account);
binding.accountDn.setText(account.display_name);
binding.accountUn.setText(account.acct);
binding.accountPp.setOnClickListener(v -> {
Intent intent = new Intent(activity, ProfileActivity.class);
Bundle args = new Bundle();
args.putSerializable(Helper.ARG_ACCOUNT, account);
new CachedBundle(activity).insertBundle(args, Helper.getCurrentAccount(activity), bundleId -> {
Bundle bundle = new Bundle();
bundle.putLong(Helper.ARG_INTENT_ID, bundleId);
intent.putExtras(bundle);
activity.startActivity(intent);
});
});
AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) activity).get(AccountsVM.class);
List<String> ids = new ArrayList<>();
ids.add(account.id);
accountsVM.getRelationships(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, ids)
.observe((LifecycleOwner) activity, relationShips -> {
if (relationShips != null && relationShips.size() > 0) {
if (!relationShips.get(0).following) {
binding.acccountContainer.setVisibility(View.VISIBLE);
binding.accountFollow.setVisibility(View.VISIBLE);
binding.accountFollow.setOnClickListener(v -> accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false, null)
.observe((LifecycleOwner) activity, relationShip -> binding.accountFollow.setVisibility(View.GONE)));
}
}
});
}
}
});
dialogBuilderOptin.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss());
try {
Handler handler = new Handler();
handler.postDelayed(() -> {
if (!activity.isFinishing()) {
dialogBuilderOptin.show();
}
}, 1000);
} catch (Exception e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putInt(activity.getString(R.string.SET_POPUP_RELEASE_NOTES), versionCode);
editor.apply();
}
}
public static String getSlugOfFirstFragment(Context context, String userId, String instance) {
if (slugOfFirstFragment != null) {
return slugOfFirstFragment;
}
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
return sharedpreferences.getString(Helper.ARG_SLUG_OF_FIRST_FRAGMENT + userId + instance, Timeline.TimeLineEnum.HOME.getValue());
}
public static void setSlugOfFirstFragment(Context context, String slug, String userId, String instance) {
if (slug != null) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedpreferences.edit();
slugOfFirstFragment = slug;
editor.putString(Helper.ARG_SLUG_OF_FIRST_FRAGMENT + userId + instance, slug);
editor.apply();
}
}
public static int compareTo(String value1, String value2) {
try {
long val1 = Long.parseLong(value1);
long val2 = Long.parseLong(value2);
return Long.compare(val1, val2);
} catch (Exception e) {
return value1.compareTo(value2);
}
}
/**
* Restart the app
*
* @param context
*/
public static void restart(Context context) {
Context ctx = context.getApplicationContext();
PackageManager pm = ctx.getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(ctx.getPackageName());
Intent mainIntent = Intent.makeRestartActivityTask(intent.getComponent());
ctx.startActivity(mainIntent);
Runtime.getRuntime().exit(0);
}
public static void forwardToBrowser(Activity activity, Intent i) {
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(i.getData(), i.getType());
List<ResolveInfo> activities = activity.getPackageManager().queryIntentActivities(intent, 0);
ArrayList<Intent> targetIntents = new ArrayList<>();
String thisPackageName = activity.getPackageName();
for (ResolveInfo currentInfo : activities) {
String packageName = currentInfo.activityInfo.packageName;
if (!thisPackageName.equals(packageName)) {
Intent targetIntent = new Intent(android.content.Intent.ACTION_VIEW);
targetIntent.setDataAndType(intent.getData(), intent.getType());
targetIntent.setPackage(intent.getPackage());
targetIntent.setComponent(new ComponentName(packageName, currentInfo.activityInfo.name));
targetIntents.add(targetIntent);
}
}
if (targetIntents.size() > 0) {
Intent chooserIntent = Intent.createChooser(targetIntents.remove(0), activity.getString(R.string.open_with));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetIntents.toArray(new Parcelable[]{}));
activity.startActivity(chooserIntent);
}
}
public static int dialogStyle() {
if (R.style.AppThemeBar == currentThemeId || R.style.AppTheme == currentThemeId) {
return R.style.AppThemeAlertDialog;
} else if (R.style.SolarizedAppThemeBar == currentThemeId || R.style.SolarizedAppTheme == currentThemeId) {
return R.style.SolarizedAlertDialog;
} else if (R.style.BlackAppThemeBar == currentThemeId || R.style.BlackAppTheme == currentThemeId) {
return R.style.BlackAlertDialog;
} else if (R.style.DraculaAppThemeBar == currentThemeId || R.style.DraculaAppTheme == currentThemeId) {
return R.style.DraculaAlertDialog;
}
return R.style.AppTheme;
}
public static void addMutedAccount(app.fedilab.android.mastodon.client.entities.api.Account target) {
if (MainActivity.filteredAccounts == null) {
MainActivity.filteredAccounts = new ArrayList<>();
}
if (!MainActivity.filteredAccounts.contains(target)) {
MainActivity.filteredAccounts.add(target);
}
}
public static void removeMutedAccount(app.fedilab.android.mastodon.client.entities.api.Account target) {
if (MainActivity.filteredAccounts != null) {
MainActivity.filteredAccounts.remove(target);
}
}
public static BaseAccount getCurrentAccount(Context context) {
if (baseAccount == null && context != null) {
baseAccount = new BaseAccount();
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
baseAccount.user_id = sharedpreferences.getString(PREF_USER_ID, null);
baseAccount.instance = sharedpreferences.getString(PREF_USER_INSTANCE, null);
baseAccount.token = sharedpreferences.getString(PREF_USER_TOKEN, null);
}
return baseAccount;
}
public static void setCurrentAccount(BaseAccount newBaseAccount) {
baseAccount = newBaseAccount;
}
public static void setCurrentAccountMastodonAccount(Context context, app.fedilab.android.mastodon.client.entities.api.Account newAccount) {
BaseAccount tempBaseAccount = getCurrentAccount(context);
tempBaseAccount.mastodon_account = newAccount;
setCurrentAccount(tempBaseAccount);
}
public static void setCurrentAccountPeertubeAccount(Context context, AccountData.PeertubeAccount newAccount) {
BaseAccount tempBaseAccount = getCurrentAccount(context);
tempBaseAccount.peertube_account = newAccount;
setCurrentAccount(tempBaseAccount);
}
public static boolean isNumeric(String str) {
try {
Double.parseDouble(str);
return true;
} catch (NumberFormatException e) {
return false;
}
}
public static OkHttpClient myOkHttpClient(Context context) {
return new OkHttpClient.Builder()
.addInterceptor(chain -> {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.header("User-Agent", context.getString(R.string.app_name) + "/" + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_CODE)
.build();
return chain.proceed(requestWithUserAgent);
})
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.callTimeout(60, TimeUnit.SECONDS)
.proxy(Helper.getProxy(context))
.build();
}
public static String parseHtml(String html) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY).toString();
} else {
//noinspection deprecation
return Html.fromHtml(html).toString();
}
}
//Enum that described actions to replace inside a toot content
public enum PatternType {
MENTION,
MENTION_LONG,
TAG,
GROUP
}
public enum NotifType {
FOLLLOW,
MENTION,
BOOST,
FAV,
POLL,
UPDATE,
SIGN_UP,
REPORT,
STATUS,
BACKUP,
STORE,
TOOT
}
public interface OnAttachmentCopied {
void onAttachmentCopied(List<Attachment> attachments);
}
public interface OnFileCopied {
void onFileCopied(File file);
}
}