diff --git a/app/src/main/java/com/github/dfa/diaspora_android/util/AppSettings.java b/app/src/main/java/com/github/dfa/diaspora_android/util/AppSettings.java index 28914097..1b3d2bff 100644 --- a/app/src/main/java/com/github/dfa/diaspora_android/util/AppSettings.java +++ b/app/src/main/java/com/github/dfa/diaspora_android/util/AppSettings.java @@ -26,7 +26,7 @@ import com.github.dfa.diaspora_android.data.DiasporaAspect; import com.github.dfa.diaspora_android.data.DiasporaPodList.DiasporaPod; import com.github.dfa.diaspora_android.web.ProxyHandler; -import net.gsantner.opoc.util.AppSettingsBase; +import net.gsantner.opoc.preference.SharedPreferencesPropertyBackend; import org.json.JSONException; import org.json.JSONObject; @@ -38,7 +38,7 @@ import java.util.List; * Created by gsantner (http://gsantner.net/) on 20.03.16. Part of dandelion*. */ @SuppressWarnings("ConstantConditions") -public class AppSettings extends AppSettingsBase { +public class AppSettings extends SharedPreferencesPropertyBackend { private final SharedPreferences _prefPod; private DiasporaPod currentPod0Cached; diff --git a/app/src/main/java/net/gsantner/opoc/format/markdown/SimpleMarkdownParser.java b/app/src/main/java/net/gsantner/opoc/format/markdown/SimpleMarkdownParser.java index 089910c0..744bd0b4 100644 --- a/app/src/main/java/net/gsantner/opoc/format/markdown/SimpleMarkdownParser.java +++ b/app/src/main/java/net/gsantner/opoc/format/markdown/SimpleMarkdownParser.java @@ -33,7 +33,7 @@ import java.io.InputStreamReader; /** * Simple Markdown Parser */ -@SuppressWarnings({"WeakerAccess", "CaughtExceptionImmediatelyRethrown", "SameParameterValue", "unused", "SpellCheckingInspection", "RepeatedSpace", "SingleCharAlternation"}) +@SuppressWarnings({"WeakerAccess", "CaughtExceptionImmediatelyRethrown", "SameParameterValue", "unused", "SpellCheckingInspection", "RepeatedSpace", "SingleCharAlternation", "Convert2Lambda"}) public class SimpleMarkdownParser { //######################## //## Statics diff --git a/app/src/main/java/net/gsantner/opoc/util/PropertyBackend.java b/app/src/main/java/net/gsantner/opoc/preference/PropertyBackend.java similarity index 94% rename from app/src/main/java/net/gsantner/opoc/util/PropertyBackend.java rename to app/src/main/java/net/gsantner/opoc/preference/PropertyBackend.java index d618955e..c27d9bce 100644 --- a/app/src/main/java/net/gsantner/opoc/util/PropertyBackend.java +++ b/app/src/main/java/net/gsantner/opoc/preference/PropertyBackend.java @@ -8,11 +8,11 @@ * https://www.apache.org/licenses/LICENSE-2.0 * #########################################################*/ -package net.gsantner.opoc.util; +package net.gsantner.opoc.preference; import java.util.List; -@SuppressWarnings({"UnusedReturnValue", "SpellCheckingInspection"}) +@SuppressWarnings({"UnusedReturnValue", "SpellCheckingInspection", "unused", "SameParameterValue"}) public interface PropertyBackend { String getString(TKEY key, String defaultValue); diff --git a/app/src/main/java/net/gsantner/opoc/util/AppSettingsBase.java b/app/src/main/java/net/gsantner/opoc/preference/SharedPreferencesPropertyBackend.java similarity index 89% rename from app/src/main/java/net/gsantner/opoc/util/AppSettingsBase.java rename to app/src/main/java/net/gsantner/opoc/preference/SharedPreferencesPropertyBackend.java index 755e320d..275d88a5 100644 --- a/app/src/main/java/net/gsantner/opoc/util/AppSettingsBase.java +++ b/app/src/main/java/net/gsantner/opoc/preference/SharedPreferencesPropertyBackend.java @@ -31,7 +31,7 @@ } */ -package net.gsantner.opoc.util; +package net.gsantner.opoc.preference; import android.annotation.SuppressLint; import android.content.Context; @@ -48,36 +48,36 @@ import java.util.List; /** - * Wrapper for settings based on SharedPreferences with keys in resources + * Wrapper for settings based on SharedPreferences, optionally with keys in resources * Default SharedPreference (_prefApp) will be taken if no SP is specified, else the first one */ @SuppressWarnings({"WeakerAccess", "unused", "SpellCheckingInspection", "SameParameterValue"}) -public class AppSettingsBase implements PropertyBackend { +public class SharedPreferencesPropertyBackend implements PropertyBackend { protected static final String ARRAY_SEPARATOR = "%%%"; protected static final String ARRAY_SEPARATOR_SUBSTITUTE = "ยงยงยง"; public static final String SHARED_PREF_APP = "app"; - //######################## - //## Members, Constructors - //######################## + // + // Members, Constructors + // protected final SharedPreferences _prefApp; protected final String _prefAppName; protected final Context _context; - public AppSettingsBase(final Context context) { + public SharedPreferencesPropertyBackend(final Context context) { this(context, SHARED_PREF_APP); } - public AppSettingsBase(final Context context, final String prefAppName) { + public SharedPreferencesPropertyBackend(final Context context, final String prefAppName) { _context = context.getApplicationContext(); _prefAppName = TextUtils.isEmpty(prefAppName) ? _context.getPackageName() + "_preferences" : prefAppName; _prefApp = _context.getSharedPreferences(_prefAppName, Context.MODE_PRIVATE); } - //##################### - //## Methods - //##################### + // + // Methods + // public Context getContext() { return _context; } @@ -136,9 +136,9 @@ public class AppSettingsBase implements PropertyBackend return (pref != null && pref.length > 0 ? pref[0] : _prefApp); } - //################################# - //## Getter for resources - //################################# + // + // Getter for resources + // public String rstr(@StringRes int stringKeyResourceId) { return _context.getString(stringKeyResourceId); } @@ -148,9 +148,9 @@ public class AppSettingsBase implements PropertyBackend } - //################################# - //## Getter & Setter for String - //################################# + // + // Getter & Setter for String + // public void setString(@StringRes int keyResourceId, String value, final SharedPreferences... pref) { gp(pref).edit().putString(rstr(keyResourceId), value).apply(); } @@ -235,9 +235,9 @@ public class AppSettingsBase implements PropertyBackend return getStringListOne(key, gp(pref)); } - //################################# - //## Getter & Setter for integer - //################################# + // + // Getter & Setter for integer + // public void setInt(@StringRes int keyResourceId, int value, final SharedPreferences... pref) { gp(pref).edit().putInt(rstr(keyResourceId), value).apply(); } @@ -319,9 +319,9 @@ public class AppSettingsBase implements PropertyBackend } - //################################# - //## Getter & Setter for Long - //################################# + // + // Getter & Setter for Long + // public void setLong(@StringRes int keyResourceId, long value, final SharedPreferences... pref) { gp(pref).edit().putLong(rstr(keyResourceId), value).apply(); } @@ -338,9 +338,9 @@ public class AppSettingsBase implements PropertyBackend return gp(pref).getLong(key, defaultValue); } - //################################# - //## Getter & Setter for Float - //################################# + // + // Getter & Setter for Float + // public void setFloat(@StringRes int keyResourceId, float value, final SharedPreferences... pref) { gp(pref).edit().putFloat(rstr(keyResourceId), value).apply(); } @@ -357,9 +357,9 @@ public class AppSettingsBase implements PropertyBackend return gp(pref).getFloat(key, defaultValue); } - //################################# - //## Getter & Setter for Double - //################################# + // + // Getter & Setter for Double + // public void setDouble(@StringRes int keyResourceId, double value, final SharedPreferences... pref) { setLong(rstr(keyResourceId), Double.doubleToRawLongBits(value)); } @@ -376,9 +376,9 @@ public class AppSettingsBase implements PropertyBackend return Double.longBitsToDouble(getLong(key, Double.doubleToRawLongBits(defaultValue), gp(pref))); } - //################################# - //## Getter & Setter for boolean - //################################# + // + // Getter & Setter for boolean + // public void setBool(@StringRes int keyResourceId, boolean value, final SharedPreferences... pref) { gp(pref).edit().putBoolean(rstr(keyResourceId), value).apply(); } @@ -395,9 +395,9 @@ public class AppSettingsBase implements PropertyBackend return gp(pref).getBoolean(key, defaultValue); } - //################################# - //## Getter & Setter for Color - //################################# + // + // Getter & Setter for Color + // public int getColor(String key, @ColorRes int defaultColor, final SharedPreferences... pref) { return gp(pref).getInt(key, rcolor(defaultColor)); } @@ -450,49 +450,49 @@ public class AppSettingsBase implements PropertyBackend } @Override - public AppSettingsBase setString(String key, String value) { + public SharedPreferencesPropertyBackend setString(String key, String value) { setString(key, value, _prefApp); return this; } @Override - public AppSettingsBase setInt(String key, int value) { + public SharedPreferencesPropertyBackend setInt(String key, int value) { setInt(key, value, _prefApp); return this; } @Override - public AppSettingsBase setLong(String key, long value) { + public SharedPreferencesPropertyBackend setLong(String key, long value) { setLong(key, value, _prefApp); return this; } @Override - public AppSettingsBase setBool(String key, boolean value) { + public SharedPreferencesPropertyBackend setBool(String key, boolean value) { setBool(key, value, _prefApp); return this; } @Override - public AppSettingsBase setFloat(String key, float value) { + public SharedPreferencesPropertyBackend setFloat(String key, float value) { setFloat(key, value, _prefApp); return this; } @Override - public AppSettingsBase setDouble(String key, double value) { + public SharedPreferencesPropertyBackend setDouble(String key, double value) { setDouble(key, value, _prefApp); return this; } @Override - public AppSettingsBase setIntList(String key, List value) { + public SharedPreferencesPropertyBackend setIntList(String key, List value) { setIntListOne(key, value, _prefApp); return this; } @Override - public AppSettingsBase setStringList(String key, List value) { + public SharedPreferencesPropertyBackend setStringList(String key, List value) { setStringListOne(key, value, _prefApp); return this; } diff --git a/app/src/main/java/net/gsantner/opoc/util/ContextUtils.java b/app/src/main/java/net/gsantner/opoc/util/ContextUtils.java index 79cc1686..68c0c4d7 100644 --- a/app/src/main/java/net/gsantner/opoc/util/ContextUtils.java +++ b/app/src/main/java/net/gsantner/opoc/util/ContextUtils.java @@ -14,7 +14,6 @@ import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.ActivityNotFoundException; -import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -28,6 +27,7 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; @@ -65,8 +65,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -74,9 +72,9 @@ import static android.graphics.Bitmap.CompressFormat; @SuppressWarnings({"WeakerAccess", "unused", "SameParameterValue", "ObsoleteSdkInt", "deprecation", "SpellCheckingInspection"}) public class ContextUtils { - //######################## - //## Members, Constructors - //######################## + // + // Members, Constructors + // protected Context _context; public ContextUtils(Context context) { @@ -87,57 +85,81 @@ public class ContextUtils { return _context; } - //######################## - //## Resources - //######################## - static class ResType { - public static final String DRAWABLE = "drawable"; - public static final String STRING = "string"; - public static final String PLURAL = "plural"; - public static final String COLOR = "color"; - public static final String STYLE = "style"; - public static final String ARRAY = "array"; - public static final String DIMEN = "dimen"; - public static final String MENU = "menu"; - public static final String BOOL = "bool"; - public static final String RAW = "raw"; + + // + // Class Methods + // + public enum ResType { + ID, BOOL, INTEGER, COLOR, STRING, ARRAY, DRAWABLE, PLURALS, + ANIM, ATTR, DIMEN, LAYOUT, MENU, RAW, STYLE, XML, } + /** + * Find out the nuermical ressource id by given {@link ResType} + * + * @return A valid id if the id could be found, else 0 + */ + public int getResId(ResType resType, final String name) { + return _context.getResources().getIdentifier(name, resType.name().toLowerCase(), _context.getPackageName()); + } + + /** + * Get String by given string ressource id (nuermic) + */ public String rstr(@StringRes int strResId) { return _context.getString(strResId); } + /** + * Get String by given string ressource identifier (textual) + */ public String rstr(String strResKey) { - return rstr(getResId(ResType.STRING, strResKey)); + try { + return rstr(getResId(ResType.STRING, strResKey)); + } catch (Resources.NotFoundException e) { + return null; + } } + /** + * Get drawable from given ressource identifier + */ public Drawable rdrawable(@DrawableRes int resId) { return ContextCompat.getDrawable(_context, resId); } + /** + * Get color by given color ressource id + */ public int rcolor(@ColorRes int resId) { return ContextCompat.getColor(_context, resId); } - public int getResId(final String type, final String name) { - return _context.getResources().getIdentifier(name, type, _context.getPackageName()); - } - - public boolean areResIdsAvailable(final String type, final String... names) { - for (String name : names) { - if (getResId(type, name) == 0) { + /** + * Checks if all given (textual) ressource ids are available + * + * @param resType A {@link ResType} + * @param resIdsTextual A (textual) identifier to be awaited at R.restype.resIdsTextual + * @return True if all given ids are available + */ + public boolean areRessourcesAvailable(final ResType resType, final String... resIdsTextual) { + for (String name : resIdsTextual) { + if (getResId(resType, name) == 0) { return false; } } return true; } - //######################## - //## Methods - //######################## - - public String colorToHexString(int intColor) { - return String.format("#%06X", 0xFFFFFF & intColor); + /** + * Convert an int color to a hex string. Optionally including alpha value. + * + * @param intColor The color coded in int + * @param withAlpha Optional; Set first bool parameter to true to also include alpha value + */ + public String colorToHexString(int intColor, boolean... withAlpha) { + boolean a = withAlpha != null && withAlpha.length >= 1 && withAlpha[0]; + return String.format(a ? "#%08X" : "#%06X", (a ? 0xFFFFFFFF : 0xFFFFFF) & intColor); } public String getAppVersionName() { @@ -151,6 +173,10 @@ public class ContextUtils { } } + /** + * Send a {@link Intent#ACTION_VIEW} Intent with given paramter + * If the parameter is an string a browser will get triggered + */ public void openWebpageInExternalBrowser(final String url) { Uri uri = Uri.parse(url); Intent intent = new Intent(Intent.ACTION_VIEW, uri); @@ -171,13 +197,8 @@ public class ContextUtils { * Falls back to applicationId of the app which may differ from manifest. */ public Object getBuildConfigValue(String fieldName) { - String pkg; - try { - pkg = rstr("manifest_package_id"); - } catch (Resources.NotFoundException ex) { - pkg = _context.getPackageName(); - } - pkg += ".BuildConfig"; + String pkg = rstr("manifest_package_id"); + pkg = (pkg != null ? pkg : _context.getPackageName()) + ".BuildConfig"; try { Class c = Class.forName(pkg); return c.getField(fieldName).get(null); @@ -187,7 +208,10 @@ public class ContextUtils { } } - public boolean bcbool(String fieldName, boolean defaultValue) { + /** + * Get a BuildConfig bool value + */ + public Boolean bcbool(String fieldName, Boolean defaultValue) { Object field = getBuildConfigValue(fieldName); if (field != null && field instanceof Boolean) { return (Boolean) field; @@ -195,6 +219,9 @@ public class ContextUtils { return defaultValue; } + /** + * Get a BuildConfig string value + */ public String bcstr(String fieldName, String defaultValue) { Object field = getBuildConfigValue(fieldName); if (field != null && field instanceof String) { @@ -203,27 +230,36 @@ public class ContextUtils { return defaultValue; } + /** + * Check if this is a gplay build (requires BuildConfig field) + */ public boolean isGooglePlayBuild() { return bcbool("IS_GPLAY_BUILD", true); } + /** + * Check if this is a foss build (requires BuildConfig field) + */ public boolean isFossBuild() { return bcbool("IS_FOSS_BUILD", false); } - // Requires donate__bitcoin_* resources (see below) to be available as string resource - public void showDonateBitcoinRequest(@StringRes final int strResBitcoinId, @StringRes final int strResBitcoinAmount, @StringRes final int strResBitcoinMessage, @StringRes final int strResAlternativeDonateUrl) { + /** + * Request a bitcoin donation with given details. + * All parameters are awaited as string resource ids + */ + public void showDonateBitcoinRequest(@StringRes final int srBitcoinId, @StringRes final int srBitcoinAmount, @StringRes final int srBitcoinMessage, @StringRes final int srAlternativeDonateUrl) { if (!isGooglePlayBuild()) { String btcUri = String.format("bitcoin:%s?amount=%s&label=%s&message=%s", - rstr(strResBitcoinId), rstr(strResBitcoinAmount), - rstr(strResBitcoinMessage), rstr(strResBitcoinMessage)); + rstr(srBitcoinId), rstr(srBitcoinAmount), + rstr(srBitcoinMessage), rstr(srBitcoinMessage)); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(btcUri)); intent.addFlags(FLAG_ACTIVITY_NEW_TASK); try { _context.startActivity(intent); } catch (ActivityNotFoundException e) { - openWebpageInExternalBrowser(rstr(strResAlternativeDonateUrl)); + openWebpageInExternalBrowser(rstr(srAlternativeDonateUrl)); } } } @@ -272,6 +308,9 @@ public class ContextUtils { } } + /** + * Check if app with given {@code packageName} is installed + */ public boolean isAppInstalled(String packageName) { PackageManager pm = _context.getApplicationContext().getPackageManager(); try { @@ -282,6 +321,9 @@ public class ContextUtils { } } + /** + * Restart the current app. Supply the class to start on startup + */ public void restartApp(Class classToStart) { Intent inte = new Intent(_context, classToStart); PendingIntent inteP = PendingIntent.getActivity(_context, 555, inte, PendingIntent.FLAG_CANCEL_CURRENT); @@ -295,6 +337,10 @@ public class ContextUtils { Runtime.getRuntime().exit(0); } + /** + * Load a markdown file from a {@link RawRes}, prepend each line with {@code prepend} text + * and convert markdown to html using {@link SimpleMarkdownParser} + */ public String loadMarkdownForTextViewFromRaw(@RawRes int rawMdFile, String prepend) { try { return new SimpleMarkdownParser() @@ -308,52 +354,74 @@ public class ContextUtils { } } + /** + * Load html into a {@link Spanned} object and set the + * {@link TextView}'s text using {@link TextView#setText(CharSequence)} + */ public void setHtmlToTextView(TextView textView, String html) { textView.setMovementMethod(LinkMovementMethod.getInstance()); textView.setText(new SpannableString(htmlToSpanned(html))); } + /** + * Estimate this device's screen diagonal size in inches + */ public double getEstimatedScreenSizeInches() { DisplayMetrics dm = _context.getResources().getDisplayMetrics(); - double density = dm.density * 160; - double x = Math.pow(dm.widthPixels / density, 2); - double y = Math.pow(dm.heightPixels / density, 2); - double screenInches = Math.sqrt(x + y) * 1.16; // 1.16 = est. Nav/Statusbar - screenInches = screenInches < 4.0 ? 4.0 : screenInches; - screenInches = screenInches > 12.0 ? 12.0 : screenInches; - return screenInches; + double calc = dm.density * 160d; + double x = Math.pow(dm.widthPixels / calc, 2); + double y = Math.pow(dm.heightPixels / calc, 2); + calc = Math.sqrt(x + y) * 1.16; // 1.16 = est. Nav/Statusbar + return Math.min(12, Math.max(4, calc)); } + /** + * Check if the device is currently in portrait orientation + */ public boolean isInPortraitMode() { return _context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; } - public Locale getLocaleByAndroidCode(String code) { - if (!TextUtils.isEmpty(code)) { - return code.contains("-r") - ? new Locale(code.substring(0, 2), code.substring(4, 6)) // de-rAt - : new Locale(code); // de + /** + * Get an {@link Locale} out of a android language code + * The {@code androidLC} may be in any of the forms: de, en, de-rAt + */ + public Locale getLocaleByAndroidCode(String androidLC) { + if (!TextUtils.isEmpty(androidLC)) { + return androidLC.contains("-r") + ? new Locale(androidLC.substring(0, 2), androidLC.substring(4, 6)) // de-rAt + : new Locale(androidLC); // de } return Resources.getSystem().getConfiguration().locale; } - // en/de/de-rAt ; Empty string -> default locale - public void setAppLanguage(String androidLocaleString) { - Locale locale = getLocaleByAndroidCode(androidLocaleString); + /** + * Set the apps language + * {@code androidLC} may be in any of the forms: en, de, de-rAt + * If given an empty string, the default (system) locale gets loaded + */ + public void setAppLanguage(String androidLC) { + Locale locale = getLocaleByAndroidCode(androidLC); Configuration config = _context.getResources().getConfiguration(); - config.locale = (locale != null && !androidLocaleString.isEmpty()) + config.locale = (locale != null && !androidLC.isEmpty()) ? locale : Resources.getSystem().getConfiguration().locale; _context.getResources().updateConfiguration(config, null); } - // Find out if color above the given color should be light or dark. true if light + /** + * Try to guess if the color on top of the given {@code colorOnBottomInt} + * should be light or dark. Returns true if top color should be light + */ public boolean shouldColorOnTopBeLight(@ColorInt int colorOnBottomInt) { return 186 > (((0.299 * Color.red(colorOnBottomInt)) + ((0.587 * Color.green(colorOnBottomInt)) + (0.114 * Color.blue(colorOnBottomInt))))); } + /** + * Convert a html string to an android {@link Spanned} object + */ public Spanned htmlToSpanned(String html) { Spanned result; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { @@ -364,61 +432,34 @@ public class ContextUtils { return result; } - public boolean setClipboard(String text) { - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) { - android.text.ClipboardManager cm = ((android.text.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)); - if (cm != null) { - cm.setText(text); - return true; - } - } else { - android.content.ClipboardManager cm = ((android.content.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)); - if (cm != null) { - ClipData clip = ClipData.newPlainText(_context.getPackageName(), text); - cm.setPrimaryClip(clip); - return true; - } - } - return false; - } - - public List getClipboard() { - List clipper = new ArrayList<>(); - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) { - android.text.ClipboardManager cm = ((android.text.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)); - if (cm != null) { - clipper.add(cm.getText().toString()); - } - - } else { - android.content.ClipboardManager cm = ((android.content.ClipboardManager) _context.getSystemService(Context.CLIPBOARD_SERVICE)); - if (cm != null && cm.hasPrimaryClip()) { - ClipData data = cm.getPrimaryClip(); - for (int i = 0; i < data.getItemCount() && i < data.getItemCount(); i++) { - clipper.add(data.getItemAt(i).getText().toString()); - } - } - - } - return clipper; - } - + /** + * Convert pixel unit do android dp unit + */ public float convertPxToDp(final float px) { return px / _context.getResources().getDisplayMetrics().density; } + /** + * Convert android dp unit to pixel unit + */ public float convertDpToPx(final float dp) { return dp * _context.getResources().getDisplayMetrics().density; } + /** + * Load an image into a {@link ImageView} and apply a color filter + */ public static void setDrawableWithColorToImageView(ImageView imageView, @DrawableRes int drawableResId, @ColorRes int colorResId) { imageView.setImageResource(drawableResId); imageView.setColorFilter(ContextCompat.getColor(imageView.getContext(), colorResId)); } + /** + * Get a {@link Bitmap} out of a {@link Drawable} + */ public Bitmap drawableToBitmap(Drawable drawable) { Bitmap bitmap = null; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (drawable instanceof VectorDrawable || drawable instanceof VectorDrawableCompat)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (drawable instanceof VectorDrawable || drawable instanceof VectorDrawableCompat || drawable instanceof AdaptiveIconDrawable)) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { drawable = (DrawableCompat.wrap(drawable)).mutate(); } @@ -434,6 +475,18 @@ public class ContextUtils { return bitmap; } + /** + * Get a {@link Bitmap} out of a {@link DrawableRes} + */ + public Bitmap drawableToBitmap(@DrawableRes int drawableId) { + return drawableToBitmap(ContextCompat.getDrawable(_context, drawableId)); + } + + /** + * Get a {@link Bitmap} from a given {@code imagePath} on the filesystem + * Specifying a {@code maxDimen} is also possible and a value below 2000 + * is recommended, otherwise a {@link OutOfMemoryError} may occur + */ public Bitmap loadImageFromFilesystem(File imagePath, int maxDimen) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; @@ -462,6 +515,10 @@ public class ContextUtils { return inSampleSize; } + /** + * Scale the bitmap so both dimensions are lower or equal to {@code maxDimen} + * This keeps the aspect ratio + */ public Bitmap scaleBitmap(Bitmap bitmap, int maxDimen) { int picSize = Math.min(bitmap.getHeight(), bitmap.getWidth()); float scale = 1.f * maxDimen / picSize; @@ -470,12 +527,15 @@ public class ContextUtils { return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } + /** + * Write the given {@link Bitmap} to {@code imageFile}, in {@link CompressFormat#JPEG} format + */ public boolean writeImageToFileJpeg(File imageFile, Bitmap image) { return writeImageToFile(imageFile, image, Bitmap.CompressFormat.JPEG, 95); } /** - * Write bitmap to the filesystem + * Write the given {@link Bitmap} to filesystem * * @param targetFile The file to be written in * @param image The image as android {@link Bitmap} @@ -517,10 +577,14 @@ public class ContextUtils { return false; } - public Bitmap drawTextOnDrawable(@DrawableRes int resId, String text, int textSize) { + /** + * Draw text in the center of the given {@link DrawableRes} + * This may be useful for e.g. badge counts + */ + public Bitmap drawTextOnDrawable(@DrawableRes int drawableRes, String text, int textSize) { Resources resources = _context.getResources(); float scale = resources.getDisplayMetrics().density; - Bitmap bitmap = bitmapToDrawable(resId); + Bitmap bitmap = drawableToBitmap(drawableRes); bitmap = bitmap.copy(bitmap.getConfig(), true); Canvas canvas = new Canvas(bitmap); @@ -538,24 +602,9 @@ public class ContextUtils { return bitmap; } - public Bitmap bitmapToDrawable(@DrawableRes int drawableId) { - Bitmap bitmap = null; - Drawable drawable = ContextCompat.getDrawable(_context, drawableId); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (drawable instanceof VectorDrawable || drawable instanceof VectorDrawableCompat)) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - drawable = (DrawableCompat.wrap(drawable)).mutate(); - } - - bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - } else if (drawable instanceof BitmapDrawable) { - bitmap = ((BitmapDrawable) drawable).getBitmap(); - } - return bitmap; - } - + /** + * Try to tint all {@link Menu}s {@link MenuItem}s with given color + */ @SuppressWarnings("ConstantConditions") public void tintMenuItems(Menu menu, boolean recurse, @ColorInt int iconColor) { for (int i = 0; i < menu.size(); i++) { @@ -567,10 +616,16 @@ public class ContextUtils { } } + /** + * Loads {@link Drawable} by given {@link DrawableRes} and applies a color + */ public Drawable tintDrawable(@DrawableRes int drawableRes, @ColorInt int color) { - return tintDrawable(_context.getResources().getDrawable(drawableRes), color); + return tintDrawable(rdrawable(drawableRes), color); } + /** + * Tint a {@link Drawable} with given {@code color} + */ public Drawable tintDrawable(@Nullable Drawable drawable, @ColorInt int color) { if (drawable != null) { drawable = DrawableCompat.wrap(drawable); @@ -579,6 +634,10 @@ public class ContextUtils { return drawable; } + /** + * Try to make icons in Toolbar/ActionBars SubMenus visible + * This may not work on some devices and it maybe won't work on future android updates + */ public void setSubMenuIconsVisiblity(Menu menu, boolean visible) { if (menu.getClass().getSimpleName().equals("MenuBuilder")) { try {