/*
* Overchan Android (Meta Imageboard Client)
* Copyright (C) 2014-2016 miku-nyan <https://github.com/miku-nyan>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nya.miku.wishmaster.ui.theme;
import java.util.Locale;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseIntArray;
import nya.miku.wishmaster.R;
import nya.miku.wishmaster.common.Logger;
import nya.miku.wishmaster.lib.org_json.JSONObject;
public class GenericThemeEntry implements Parcelable {
private static final int BASE_THEME_LIGHT = R.style.Theme_Futaba;
private static final int BASE_THEME_DARK = R.style.Theme_Neutron;
private static GenericThemeEntry cachedInstance = null;
private static String cachedJsonString = null;
public final int themeId;
public final int fontSizeStyleId;
public final SparseIntArray customAttrs;
private GenericThemeEntry(int themeId, int fontSizeStyleId, SparseIntArray customAttrs) {
this.themeId = themeId;
this.fontSizeStyleId = fontSizeStyleId;
this.customAttrs = customAttrs;
}
private GenericThemeEntry(Parcel in) {
this.themeId = in.readInt();
this.fontSizeStyleId = in.readInt();
int n = in.readInt();
if (n == -1) {
this.customAttrs = null;
} else {
SparseIntArray attrs = new SparseIntArray();
for (int i=0; i<n; ++i) {
int key = in.readInt();
int value = in.readInt();
attrs.put(key, value);
}
this.customAttrs = attrs;
}
}
public static final Parcelable.Creator<GenericThemeEntry> CREATOR = new Parcelable.Creator<GenericThemeEntry>() {
@Override
public GenericThemeEntry createFromParcel(Parcel source) {
return new GenericThemeEntry(source);
}
@Override
public GenericThemeEntry[] newArray(int size) {
return new GenericThemeEntry[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(themeId);
dest.writeInt(fontSizeStyleId);
if (customAttrs == null) {
dest.writeInt(-1);
} else {
int n = customAttrs.size();
dest.writeInt(n);
for (int i=0; i<n; ++i) {
dest.writeInt(customAttrs.keyAt(i));
dest.writeInt(customAttrs.valueAt(i));
}
}
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o instanceof GenericThemeEntry) {
GenericThemeEntry other = (GenericThemeEntry) o;
return other.themeId == this.themeId &&
other.fontSizeStyleId == this.fontSizeStyleId &&
sparseIntArrayEquals(other.customAttrs, this.customAttrs);
} else {
return false;
}
}
public static GenericThemeEntry standartTheme(int themeId, int fontSizeStyleId) {
return new GenericThemeEntry(themeId, fontSizeStyleId, null);
}
public static GenericThemeEntry customTheme(String jsonData, int fontSizeStyleId) {
GenericThemeEntry cached = cachedInstance;
if (cached != null && cached.fontSizeStyleId == fontSizeStyleId && jsonData.equals(cachedJsonString)) return cached;
SparseIntArray customAttrs = new SparseIntArray();
int themeId = parseTheme(jsonData, customAttrs);
GenericThemeEntry theme = new GenericThemeEntry(themeId, fontSizeStyleId, customAttrs);
cachedInstance = theme;
cachedJsonString = jsonData;
return theme;
}
public void setTo(Context context, int... applyStyles) {
setBaseStyle(context, themeId, fontSizeStyleId);
if (customAttrs != null) {
Resources.Theme theme = context.getTheme();
if (themeId == BASE_THEME_LIGHT) theme.applyStyle(R.style.Custom_Theme_Light, true);
else if (themeId == BASE_THEME_DARK) theme.applyStyle(R.style.Custom_Theme_Dark, true);
}
if (applyStyles != null) {
Resources.Theme theme = context.getTheme();
for (int i : applyStyles) {
theme.applyStyle(i, true);
}
}
CustomThemeHelper.setCustomTheme(context, customAttrs);
}
public void setToPreferencesActivity(Context context) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2 || Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR_0_1) {
setTo(context);
} else {
setBaseStyle(context, BASE_THEME_DARK, fontSizeStyleId);
}
}
private static void setBaseStyle(Context context, int themeId, int fontSizeStyleId) {
context.setTheme(themeId);
context.getTheme().applyStyle(fontSizeStyleId, true);
}
public String toJsonString() {
if (customAttrs == null) throw new IllegalStateException("this is not a custom theme");
JSONObject theme = new JSONObject();
theme.put("baseTheme", themeId == BASE_THEME_DARK ? "dark" : "light");
for (int i=0, size=customAttrs.size(); i<size; ++i) {
switch (customAttrs.keyAt(i)) {
case android.R.attr.textColorPrimary: theme.put("textColorPrimary", colorToString(customAttrs.valueAt(i))); break;
case R.attr.materialPrimary: theme.put("materialPrimary", colorToString(customAttrs.valueAt(i))); break;
case R.attr.materialPrimaryDark: theme.put("materialPrimaryDark", colorToString(customAttrs.valueAt(i))); break;
case R.attr.materialNavigationBar: theme.put("materialNavigationBar", colorToString(customAttrs.valueAt(i))); break;
case R.attr.activityRootBackground: theme.put("activityRootBackground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.sidebarBackground: theme.put("sidebarBackground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.sidebarSelectedItem: theme.put("sidebarSelectedItem", colorToString(customAttrs.valueAt(i))); break;
case R.attr.listSeparatorBackground: theme.put("listSeparatorBackground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postUnreadOverlay: theme.put("postUnreadOverlay", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postBackground: theme.put("postBackground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postForeground: theme.put("postForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postIndexForeground: theme.put("postIndexForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postIndexOverBumpLimit: theme.put("postIndexOverBumpLimit", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postNumberForeground: theme.put("postNumberForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postNameForeground: theme.put("postNameForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postOpForeground: theme.put("postOpForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postSageForeground: theme.put("postSageForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postTripForeground: theme.put("postTripForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postTitleForeground: theme.put("postTitleForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.postQuoteForeground: theme.put("postQuoteForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.spoilerForeground: theme.put("spoilerForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.spoilerBackground: theme.put("spoilerBackground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.urlLinkForeground: theme.put("urlLinkForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.refererForeground: theme.put("refererForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.itemInfoForeground: theme.put("itemInfoForeground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.searchHighlightBackground: theme.put("searchHighlightBackground", colorToString(customAttrs.valueAt(i))); break;
case R.attr.subscriptionBackground: theme.put("subscriptionBackground", colorToString(customAttrs.valueAt(i))); break;
default: Logger.e("TAG", "unknown attribute: " + customAttrs.keyAt(i));
}
}
return theme.toString();
}
private static int parseTheme(String customThemeJson, SparseIntArray attrs) {
JSONObject theme = new JSONObject(customThemeJson);
String baseTheme = null;
for (String key : theme.keySet()) {
switch (key.toLowerCase(Locale.US)) {
case "basetheme":
if (baseTheme != null) throwDefinedMoreThenOnce("baseTheme");
String value = theme.getString(key);
switch (value.toLowerCase(Locale.US)) {
case "light": baseTheme = "light"; break;
case "dark": baseTheme = "dark"; break;
default: throw new IllegalArgumentException("Illegal value for baseTheme: " + value);
}
break;
case "textcolorprimary": parseColor(key, theme.getString(key), android.R.attr.textColorPrimary, attrs); break;
case "materialprimary": parseColor(key, theme.getString(key), R.attr.materialPrimary, attrs); break;
case "materialprimarydark": parseColor(key, theme.getString(key), R.attr.materialPrimaryDark, attrs); break;
case "materialnavigationbar": parseColor(key, theme.getString(key), R.attr.materialNavigationBar, attrs); break;
case "activityrootbackground": parseColor(key, theme.getString(key), R.attr.activityRootBackground, attrs); break;
case "sidebarbackground": parseColor(key, theme.getString(key), R.attr.sidebarBackground, attrs); break;
case "sidebarselecteditem": parseColor(key, theme.getString(key), R.attr.sidebarSelectedItem, attrs); break;
case "listseparatorbackground": parseColor(key, theme.getString(key), R.attr.listSeparatorBackground, attrs); break;
case "postunreadoverlay": parseColor(key, theme.getString(key), R.attr.postUnreadOverlay, attrs); break;
case "postbackground": parseColor(key, theme.getString(key), R.attr.postBackground, attrs); break;
case "postforeground": parseColor(key, theme.getString(key), R.attr.postForeground, attrs); break;
case "postindexforeground": parseColor(key, theme.getString(key), R.attr.postIndexForeground, attrs); break;
case "postindexoverbumplimit": parseColor(key, theme.getString(key), R.attr.postIndexOverBumpLimit, attrs); break;
case "postnumberforeground": parseColor(key, theme.getString(key), R.attr.postNumberForeground, attrs); break;
case "postnameforeground": parseColor(key, theme.getString(key), R.attr.postNameForeground, attrs); break;
case "postopforeground": parseColor(key, theme.getString(key), R.attr.postOpForeground, attrs); break;
case "postsageforeground": parseColor(key, theme.getString(key), R.attr.postSageForeground, attrs); break;
case "posttripforeground": parseColor(key, theme.getString(key), R.attr.postTripForeground, attrs); break;
case "posttitleforeground": parseColor(key, theme.getString(key), R.attr.postTitleForeground, attrs); break;
case "postquoteforeground": parseColor(key, theme.getString(key), R.attr.postQuoteForeground, attrs); break;
case "spoilerforeground": parseColor(key, theme.getString(key), R.attr.spoilerForeground, attrs); break;
case "spoilerbackground": parseColor(key, theme.getString(key), R.attr.spoilerBackground, attrs); break;
case "urllinkforeground": parseColor(key, theme.getString(key), R.attr.urlLinkForeground, attrs); break;
case "refererforeground": parseColor(key, theme.getString(key), R.attr.refererForeground, attrs); break;
case "iteminfoforeground": parseColor(key, theme.getString(key), R.attr.itemInfoForeground, attrs); break;
case "searchhighlightbackground": parseColor(key, theme.getString(key), R.attr.searchHighlightBackground, attrs); break;
case "subscriptionbackground": parseColor(key, theme.getString(key), R.attr.subscriptionBackground, attrs); break;
default: throw new IllegalArgumentException("Unknown key: " + key);
}
}
if (baseTheme == null) throw new IllegalArgumentException("Base theme not set");
return baseTheme.equals("light") ? BASE_THEME_LIGHT : BASE_THEME_DARK;
}
private static void parseColor(String key, String value, int attrId, SparseIntArray array) {
if (array.indexOfKey(attrId) >= 0) throwDefinedMoreThenOnce(key);
try {
array.put(attrId, Color.parseColor(value));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Unknown color: "+value);
}
}
private static String colorToString(int color) {
if (Color.alpha(color) == 0xFF) return String.format("#%06X", color & 0xFFFFFF);
return String.format("#%08X", color);
}
private static void throwDefinedMoreThenOnce(String key) {
throw new IllegalArgumentException(String.format("Key %s is defined more than once", key));
}
private static boolean sparseIntArrayEquals(SparseIntArray a, SparseIntArray b) {
if (a == b) return true;
if (a == null) return b == null;
if (b == null) return false;
int size = a.size();
if (size != b.size()) return false;
for (int i=0; i<size; ++i) {
if (a.keyAt(i) != b.keyAt(i)) return false;
if (a.valueAt(i) != b.valueAt(i)) return false;
}
return true;
}
}