package yuku.alkitab.base; import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.graphics.ColorUtils; import android.widget.TextView; import yuku.afw.storage.Preferences; import yuku.alkitab.base.storage.NoBackupSharedPreferences; import yuku.alkitab.base.storage.Prefkey; import yuku.alkitab.debug.BuildConfig; import yuku.alkitab.debug.R; import yuku.alkitab.model.Label; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; import java.util.Locale; import java.util.UUID; public class U { /** * If text is null, this returns null. * If verse doesn't start with @: don't do anything. * Otherwise, remove all @'s and one character after that and also text between @< and @>. */ @Nullable public static String removeSpecialCodes(@Nullable final String text) { return removeSpecialCodes(text, false); } /** * If text is null, this returns null. * If verse doesn't start with @: don't do anything, except when force is set to true. * Otherwise, remove all @'s and one character after that and also text between @< and @>. */ @Nullable public static String removeSpecialCodes(@Nullable final String text, final boolean force) { if (text == null) return null; if (text.length() == 0) return text; if (!force && text.charAt(0) != '@') return text; final StringBuilder sb = new StringBuilder(text.length()); int pos = 0; while (true) { final int p = text.indexOf('@', pos); if (p == -1) { break; } sb.append(text, pos, p); pos = p + 2; if (p + 1 < text.length()) { final char skipped = text.charAt(p + 1); switch (skipped) { // did we skip "@<"? case '<': { // look for matching "@>" int q = text.indexOf("@>", pos); if (q != -1) { pos = q + 2; } } break; // did we skip a paragraph marker, new paragraph, or newline? // if so, add a space if needed case '0': case '1': case '2': case '3': case '4': case '^': case '8': { // only add if the last character output is not already a whitespace if (sb.length() == 0 || Character.isWhitespace(sb.charAt(sb.length() - 1))) { // we do not need to put extra space } else { sb.append(' '); } } break; } } } sb.append(text, pos, text.length()); return sb.toString(); } public static String encodeLabelBackgroundColor(int colorRgb_background) { StringBuilder sb = new StringBuilder(10); sb.append('b'); // 'b': background color String h = Integer.toHexString(colorRgb_background); for (int x = h.length(); x < 6; x++) { sb.append('0'); } sb.append(h); return sb.toString(); } /** * @return colorRgb (without alpha) or -1 if can't decode */ public static int decodeLabelBackgroundColor(String backgroundColor) { if (backgroundColor == null || backgroundColor.length() == 0) return -1; if (backgroundColor.length() >= 7 && backgroundColor.charAt(0) == 'b') { // 'b': background color return Integer.parseInt(backgroundColor.substring(1, 7), 16); } else { return -1; } } public static int getLabelForegroundColorBasedOnBackgroundColor(int colorRgb) { float[] hsl = {0.f, 0.f, 0.f}; rgbToHsl(colorRgb, hsl); if (hsl[2] > 0.5f) hsl[2] -= 0.44f; else hsl[2] += 0.44f; return hslToRgb(hsl); } public static void rgbToHsl(int rgb, float[] hsl) { float r = ((0x00ff0000 & rgb) >> 16) / 255.f; float g = ((0x0000ff00 & rgb) >> 8) / 255.f; float b = ((0x000000ff & rgb)) / 255.f; float max = Math.max(Math.max(r, g), b); float min = Math.min(Math.min(r, g), b); float c = max - min; float h_ = 0.f; if (c == 0) { h_ = 0; } else if (max == r) { h_ = (g-b) / c; if (h_ < 0) h_ += 6.f; } else if (max == g) { h_ = (b-r) / c + 2.f; } else if (max == b) { h_ = (r-g) / c + 4.f; } float h = 60.f * h_; float l = (max + min) * 0.5f; float s; if (c == 0) { s = 0.f; } else { s = c / (1 - Math.abs(2.f * l - 1.f)); } hsl[0] = h; hsl[1] = s; hsl[2] = l; } public static int hslToRgb(float[] hsl) { float h = hsl[0]; float s = hsl[1]; float l = hsl[2]; float c = (1 - Math.abs(2.f * l - 1.f)) * s; float h_ = h / 60.f; float h_mod2 = h_; if (h_mod2 >= 4.f) h_mod2 -= 4.f; else if (h_mod2 >= 2.f) h_mod2 -= 2.f; float x = c * (1 - Math.abs(h_mod2 - 1)); float r_, g_, b_; if (h_ < 1) { r_ = c; g_ = x; b_ = 0; } else if (h_ < 2) { r_ = x; g_ = c; b_ = 0; } else if (h_ < 3) { r_ = 0; g_ = c; b_ = x; } else if (h_ < 4) { r_ = 0; g_ = x; b_ = c; } else if (h_ < 5) { r_ = x; g_ = 0; b_ = c; } else { r_ = c; g_ = 0; b_ = x; } float m = l - (0.5f * c); int r = (int)((r_ + m) * (255.f) + 0.5f); int g = (int)((g_ + m) * (255.f) + 0.5f); int b = (int)((b_ + m) * (255.f) + 0.5f); return r << 16 | g << 8 | b; } @SuppressWarnings("deprecation") public static void copyToClipboard(CharSequence text) { android.text.ClipboardManager clipboardManager = (android.text.ClipboardManager) App.context.getSystemService(Context.CLIPBOARD_SERVICE); clipboardManager.setText(text); } public static int getForegroundColorOnDarkBackgroundByBookId(int bookId) { if (bookId >= 0 && bookId < 39) { // OT return 0xff_ef5350; // Pink } else if (bookId >= 39 && bookId < 66) { // NT return 0xff_42a5f5; // Blue 400 } else { // others return 0xff_eeeeee; // Grey 200 } } public static int getBackgroundColorByBookId(int bookId) { if (bookId >= 0 && bookId < 39) { // OT return 0xff_e53935; // Red 600 } else if (bookId >= 39 && bookId < 66) { // NT return 0xff_1e88e5; // Blue 600 } else { // others return 0xff_212121; // Grey 900 } } public static int getSearchKeywordTextColorByBrightness(float brightness) { if (brightness < 0.5f) { return 0xff_69f0ae; // Green A200 } else { return 0xff_00c853; // Green A700 } } public static boolean equals(Object a, Object b) { if (a == b) return true; if (a == null) return false; return a.equals(b); } public static int applyLabelColor(Label label, TextView view) { int bgColorRgb = U.decodeLabelBackgroundColor(label.backgroundColor); if (bgColorRgb == -1) { bgColorRgb = 0x212121; // default color Grey 900 } GradientDrawable grad = null; Drawable bg = view.getBackground(); if (bg instanceof GradientDrawable) { grad = (GradientDrawable) bg; } else if (bg instanceof StateListDrawable) { StateListDrawable states = (StateListDrawable) bg; Drawable current = states.getCurrent(); if (current instanceof GradientDrawable) { grad = (GradientDrawable) current; } } if (grad != null) { grad.setColor(0xff000000 | bgColorRgb); final int labelColor = 0xff000000 | U.getLabelForegroundColorBasedOnBackgroundColor(bgColorRgb); view.setTextColor(labelColor); return labelColor; } return 0; } public static String inputStreamUtf8ToString(InputStream input) throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final byte[] buf = new byte[1024]; while (true) { final int read = input.read(buf); if (read < 0) break; baos.write(buf, 0, read); } return new String(baos.toByteArray(), Charset.forName("utf-8")); } /** * The reason we use an installation id instead of just the simpleToken for sync * to identify originating device, is so that the GCM messages does not contain * simpleToken, which is sensitive. */ public synchronized static String getInstallationId() { final NoBackupSharedPreferences nbsp = NoBackupSharedPreferences.get(); String res = Preferences.getString(Prefkey.installation_id, null); if (res == null) { res = nbsp.getString(Prefkey.installation_id.name()); if (res == null) { res = "i1:" + UUID.randomUUID().toString(); nbsp.setString(Prefkey.installation_id.name(), res); } } else { // we need to remove it from the backed up folder and move it to the nonbacked up folder Preferences.remove(Prefkey.installation_id); nbsp.setString(Prefkey.installation_id.name(), res); } return res; } public static int getTextColorForSelectedVerse(final int selectedVerseBgColor) { if (ColorUtils.calculateLuminance(selectedVerseBgColor) > 0.4) { return 0xff000000; } else { return 0xffffffff; } } static class InstallationInfoJson { public String installation_id; public String app_packageName; public int app_versionCode; public boolean app_debug; public String build_manufacturer; public String build_model; public String build_device; public String build_product; public int os_sdk_int; public String os_release; public String locale; public String last_commit_hash; } /** * Return a JSON string that contains information about the app installation on this particular device. */ public static String getInstallationInfoJson() { final InstallationInfoJson obj = new InstallationInfoJson(); obj.installation_id = getInstallationId(); obj.app_packageName = App.context.getPackageName(); obj.app_versionCode = App.getVersionCode(); obj.app_debug = BuildConfig.DEBUG; obj.build_manufacturer = Build.MANUFACTURER; obj.build_model = Build.MODEL; obj.build_device = Build.DEVICE; obj.build_product = Build.PRODUCT; obj.os_sdk_int = Build.VERSION.SDK_INT; obj.os_release = Build.VERSION.RELEASE; final Locale locale = App.context.getResources().getConfiguration().locale; obj.locale = locale == null ? null : locale.toString(); obj.last_commit_hash = App.context.getString(R.string.last_commit_hash); return App.getDefaultGson().toJson(obj); } public interface ThrowEverythingRunnable { void run() throws Exception; } public static void wontThrow(ThrowEverythingRunnable r) { try { r.run(); } catch (Exception e) { throw new RuntimeException("ThrowEverythingRunnable is passed but caused exception: " + r.toString(), e); } } }