/* * Copyright 2013 serso aka se.solovyev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Contact details * * Email: se.solovyev@gmail.com * Site: http://se.solovyev.org */ package org.solovyev.android.calculator; import android.app.Application; import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.Color; import android.provider.Settings; import android.support.annotation.*; import android.support.v7.view.ContextThemeWrapper; import android.text.TextUtils; import android.util.SparseArray; import org.solovyev.android.Check; import org.solovyev.android.calculator.about.AboutActivity; import org.solovyev.android.calculator.functions.FunctionsActivity; import org.solovyev.android.calculator.history.HistoryActivity; import org.solovyev.android.calculator.language.Languages; import org.solovyev.android.calculator.operators.OperatorsActivity; import org.solovyev.android.calculator.preferences.PreferenceEntry; import org.solovyev.android.calculator.preferences.PreferencesActivity; import org.solovyev.android.calculator.variables.VariablesActivity; import org.solovyev.android.calculator.wizard.WizardActivity; import org.solovyev.android.prefs.*; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.EnumMap; import java.util.Map; import static org.solovyev.android.prefs.IntegerPreference.DEF_VALUE; public final class Preferences { private static final Preference<Integer> version = IntegerPreference.of("version", 3); private Preferences() { throw new AssertionError(); } static void init(@Nonnull Application application, @Nonnull SharedPreferences preferences) { final int currentVersion = getVersion(preferences); if (currentVersion == 0) { final SharedPreferences.Editor editor = preferences.edit(); setInitialDefaultValues(application, preferences, editor); editor.apply(); } else if (currentVersion == 1) { final SharedPreferences.Editor editor = preferences.edit(); if (!Gui.vibrateOnKeypress.isSet(preferences)) { Gui.vibrateOnKeypress.putPreference(editor, Deleted.hapticFeedback.getPreference(preferences) > 0); } migratePreference(preferences, editor, Gui.keepScreenOn, Deleted.preventScreenFromFading); migratePreference(preferences, editor, Gui.theme, Deleted.theme); migratePreference(preferences, editor, Gui.useBackAsPrevious, Deleted.usePrevAsBack); migratePreference(preferences, editor, Gui.showReleaseNotes, Deleted.showReleaseNotes); migratePreference(preferences, editor, Gui.rotateScreen, Deleted.autoOrientation); final String layout = Deleted.layout.getPreference(preferences); if (TextUtils.equals(layout, "main_calculator")) { Gui.mode.putPreference(editor, Gui.Mode.engineer); } else if (TextUtils.equals(layout, "simple")) { Gui.mode.putPreference(editor, Gui.Mode.simple); } else if (!Gui.mode.isSet(preferences)) { Gui.mode.putDefault(editor); } version.putDefault(editor); editor.apply(); } else if (currentVersion == 2) { final SharedPreferences.Editor editor = preferences.edit(); Gui.highContrast.tryPutDefault(preferences, editor); version.putDefault(editor); editor.apply(); } } private static int getVersion(@Nonnull SharedPreferences preferences) { if (version.isSet(preferences)) { return version.getPreference(preferences); } else if (Deleted.appVersion.isSet(preferences)) { return 1; } return 0; } private static <T> void migratePreference(@Nonnull SharedPreferences preferences, @NonNull SharedPreferences.Editor editor, @NonNull Preference<T> to, @NonNull Preference<T> from) { if (!to.isSet(preferences)) { to.putPreference(editor, from.getPreferenceNoError(preferences)); } } private static void setInitialDefaultValues(@Nonnull Application application, @Nonnull SharedPreferences preferences, @Nonnull SharedPreferences.Editor editor) { Gui.theme.tryPutDefault(preferences, editor); Gui.mode.tryPutDefault(preferences, editor); Gui.showReleaseNotes.tryPutDefault(preferences, editor); Gui.useBackAsPrevious.tryPutDefault(preferences, editor); Gui.rotateScreen.tryPutDefault(preferences, editor); Gui.keepScreenOn.tryPutDefault(preferences, editor); Gui.language.tryPutDefault(preferences, editor); Gui.highContrast.tryPutDefault(preferences, editor); Calculations.calculateOnFly.tryPutDefault(preferences, editor); Onscreen.showAppIcon.tryPutDefault(preferences, editor); Onscreen.theme.tryPutDefault(preferences, editor); Widget.theme.tryPutDefault(preferences, editor); version.putDefault(editor); final ContentResolver cr = application.getContentResolver(); if (cr != null) { final boolean vibrateOnKeyPress = Settings.System.getInt(cr, Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) != 0; Gui.vibrateOnKeypress.putPreference(editor, vibrateOnKeyPress); } } public enum SimpleTheme { default_theme(0, 0, null), metro_blue_theme(R.layout.onscreen_layout, R.layout.widget_layout, Gui.Theme.metro_blue_theme), material_theme(R.layout.onscreen_layout_material, R.layout.widget_layout_material, Gui.Theme.material_theme), material_light_theme(R.layout.onscreen_layout_material_light, R.layout.widget_layout_material_light, Gui.Theme.material_light_theme, true); @LayoutRes private final int onscreenLayout; @LayoutRes private final int widgetLayout; @Nullable private final Gui.Theme appTheme; public final boolean light; @Nonnull private final Map<Gui.Theme, SimpleTheme> cache = new EnumMap<>(Gui.Theme.class); SimpleTheme(int onscreenLayout, int widgetLayout, @Nullable Gui.Theme appTheme) { this(onscreenLayout, widgetLayout, appTheme, false); } SimpleTheme(int onscreenLayout, int widgetLayout, @Nullable Gui.Theme appTheme, boolean light) { this.onscreenLayout = onscreenLayout; this.widgetLayout = widgetLayout; this.appTheme = appTheme; this.light = light; } public int getOnscreenLayout(@Nonnull Gui.Theme appTheme) { return resolveThemeFor(appTheme).onscreenLayout; } public int getWidgetLayout(@Nonnull Gui.Theme appTheme) { return resolveThemeFor(appTheme).widgetLayout; } @Nonnull public SimpleTheme resolveThemeFor(@Nonnull Gui.Theme appTheme) { if (this == default_theme) { SimpleTheme theme = cache.get(appTheme); if (theme == null) { theme = lookUpThemeFor(appTheme); cache.put(appTheme, theme); } return theme; } return this; } @Nonnull private SimpleTheme lookUpThemeFor(@Nonnull Gui.Theme appTheme) { Check.isTrue(this == default_theme); // find direct match for (SimpleTheme theme : values()) { if (theme.appTheme == appTheme) { return theme; } } // for metro themes return metro theme if (appTheme == Gui.Theme.metro_green_theme || appTheme == Gui.Theme.metro_purple_theme) { return metro_blue_theme; } // for old themes return dark material return material_theme; } @Nullable public Gui.Theme getAppTheme() { return appTheme; } @ColorRes public int getDisplayTextColor(boolean error) { if (error) { return light ? R.color.cpp_text_inverse_error : R.color.cpp_text_error; } return light ? R.color.cpp_text_inverse : R.color.cpp_text; } } public static class Widget { public static final Preference<SimpleTheme> theme = StringPreference.ofEnum("widget.theme", SimpleTheme.default_theme, SimpleTheme.class); @Nonnull public static SimpleTheme getTheme(@Nonnull SharedPreferences preferences) { return theme.getPreferenceNoError(preferences); } } public static class Onscreen { public static final Preference<Boolean> showAppIcon = BooleanPreference.of("onscreen_show_app_icon", true); public static final Preference<SimpleTheme> theme = StringPreference.ofEnum("onscreen.theme", SimpleTheme.default_theme, SimpleTheme.class); @Nonnull public static SimpleTheme getTheme(@Nonnull SharedPreferences preferences) { return theme.getPreferenceNoError(preferences); } } public static class Calculations { public static final Preference<Boolean> calculateOnFly = BooleanPreference.of("calculations_calculate_on_fly", true); } public static class App { } public static class Gui { public static final Preference<Theme> theme = StringPreference.ofEnum("gui.theme", Theme.material_theme, Theme.class); public static final Preference<Mode> mode = StringPreference.ofEnum("gui.mode", Mode.simple, Mode.class); public static final Preference<String> language = StringPreference.of("gui.language", Languages.SYSTEM_LANGUAGE_CODE); public static final Preference<Boolean> showReleaseNotes = BooleanPreference.of("gui.showReleaseNotes", true); public static final Preference<Boolean> useBackAsPrevious = BooleanPreference.of("gui.useBackAsPrevious", false); public static final Preference<Boolean> rotateScreen = BooleanPreference.of("gui.rotateScreen", true); public static final Preference<Boolean> keepScreenOn = BooleanPreference.of("gui.keepScreenOn", true); public static final Preference<Boolean> highContrast = BooleanPreference.of("gui.highContrast", false); public static final Preference<Boolean> vibrateOnKeypress = BooleanPreference.of("gui.vibrateOnKeypress", true); @Nonnull public static Theme getTheme(@Nonnull SharedPreferences preferences) { return theme.getPreferenceNoError(preferences); } @Nonnull public static Mode getMode(@Nonnull SharedPreferences preferences) { return mode.getPreferenceNoError(preferences); } public enum Theme implements PreferenceEntry { default_theme(R.style.Cpp_Theme_Gray), violet_theme(R.style.Cpp_Theme_Violet), light_blue_theme(R.style.Cpp_Theme_Blue), metro_blue_theme(R.string.cpp_theme_metro_blue, R.style.Cpp_Theme_Metro_Blue, R.style.Cpp_Theme_Metro_Blue_Calculator, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Metro_Blue_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert), metro_purple_theme(R.string.p_metro_purple_theme, R.style.Cpp_Theme_Metro_Purple, R.style.Cpp_Theme_Metro_Purple_Calculator, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Metro_Purple_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert), metro_green_theme(R.string.p_metro_green_theme, R.style.Cpp_Theme_Metro_Green, R.style.Cpp_Theme_Metro_Green_Calculator, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Metro_Green_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert), material_theme(R.string.cpp_theme_dark, R.style.Cpp_Theme_Material, R.style.Cpp_Theme_Material_Calculator), material_black_theme(R.string.cpp_theme_black, R.style.Cpp_Theme_Material_Black, R.style.Cpp_Theme_Material_Black_Calculator) { @NonNull @Override public String getName(@NonNull Context context) { return context.getString(name, material_theme.getName(context)); } }, material_light_theme(R.string.cpp_theme_light, R.style.Cpp_Theme_Material_Light, R.style.Cpp_Theme_Material_Light_Calculator, R.style.Cpp_Theme_Wizard_Light, R.style.Cpp_Theme_Material_Light_Dialog, R.style.Cpp_Theme_Material_Light_Dialog_Alert); private static final SparseArray<TextColor> textColors = new SparseArray<>(); @StringRes protected final int name; @StyleRes public final int theme; @StyleRes public final int calculatorTheme; @StyleRes public final int wizardTheme; @StyleRes public final int dialogTheme; @StyleRes public final int alertDialogTheme; public final boolean light; Theme(@StyleRes int theme) { this(R.string.cpp_theme_dark, theme, theme); } Theme(@StringRes int name, @StyleRes int theme, @StyleRes int calculatorTheme) { this(name, theme, calculatorTheme, R.style.Cpp_Theme_Wizard, R.style.Cpp_Theme_Material_Dialog, R.style.Cpp_Theme_Material_Dialog_Alert); } Theme(@StringRes int name, @StyleRes int theme, @StyleRes int calculatorTheme, @StyleRes int wizardTheme, @StyleRes int dialogTheme, @StyleRes int alertDialogTheme) { this.name = name; this.theme = theme; this.calculatorTheme = calculatorTheme; this.wizardTheme = wizardTheme; this.dialogTheme = dialogTheme; this.alertDialogTheme = alertDialogTheme; this.light = theme == R.style.Cpp_Theme_Material_Light; } public int getThemeFor(@Nonnull Context context) { if (context instanceof CalculatorActivity) { return calculatorTheme; } if (context instanceof WizardActivity) { return wizardTheme; } if (context instanceof FunctionsActivity.Dialog) { return dialogTheme; } if (context instanceof PreferencesActivity.Dialog) { return dialogTheme; } if (context instanceof VariablesActivity.Dialog) { return dialogTheme; } if (context instanceof OperatorsActivity.Dialog) { return dialogTheme; } if (context instanceof HistoryActivity.Dialog) { return dialogTheme; } if (context instanceof AboutActivity.Dialog) { return dialogTheme; } return theme; } @Nonnull public TextColor getTextColorFor(@Nonnull Context context) { final int themeId = getThemeFor(context); TextColor textColor = textColors.get(themeId); if (textColor == null) { final ContextThemeWrapper themeContext = new ContextThemeWrapper(context, themeId); final TypedArray a = themeContext.obtainStyledAttributes(themeId, new int[]{R.attr.cpp_text_color, R.attr.cpp_text_color_error}); final int normal = a.getColor(0, Color.BLACK); final int error = a.getColor(1, Color.WHITE); a.recycle(); textColor = new TextColor(normal, error); textColors.append(themeId, textColor); } return textColor; } @Override @NonNull public String getName(@NonNull Context context) { return context.getString(name); } @NonNull @Override public CharSequence getId() { return name(); } } public enum Mode { engineer(R.string.cpp_mode_engineer), simple(R.string.cpp_mode_simple); @StringRes public final int name; Mode(@StringRes int name) { this.name = name; } } public static final class TextColor { public final int normal; public final int error; TextColor(int normal, int error) { this.normal = normal; this.error = error; } } } @SuppressWarnings("unused") static class Deleted { static final Preference<Integer> appVersion = IntegerPreference.of("application.version", DEF_VALUE); static final Preference<Boolean> feedbackWindowShown = BooleanPreference.of("feedback_window_shown", false); static final Preference<Integer> appOpenedCounter = IntegerPreference.of("app_opened_counter", 0); static final Preference<Long> hapticFeedback = NumberToStringPreference.of("hapticFeedback", 60L, Long.class); static final Preference<Boolean> colorDisplay = BooleanPreference.of("org.solovyev.android.calculator.CalculatorModel_color_display", true); static final Preference<Boolean> preventScreenFromFading = BooleanPreference.of("preventScreenFromFading", true); static final Preference<Gui.Theme> theme = StringPreference.ofEnum("org.solovyev.android.calculator.CalculatorActivity_calc_theme", Gui.Theme.material_theme, Gui.Theme.class); static final StringPreference<String> layout = StringPreference.of("org.solovyev.android.calculator.CalculatorActivity_calc_layout", "simple"); static final Preference<Boolean> showReleaseNotes = BooleanPreference.of("org.solovyev.android.calculator.CalculatorActivity_show_release_notes", true); static final Preference<Boolean> usePrevAsBack = BooleanPreference.of("org.solovyev.android.calculator.CalculatorActivity_use_back_button_as_prev", false); static final Preference<Boolean> showEqualsButton = BooleanPreference.of("showEqualsButton", true); static final Preference<Boolean> autoOrientation = BooleanPreference.of("autoOrientation", true); static final Preference<Boolean> startOnBoot = BooleanPreference.of("onscreen_start_on_boot", false); static final Preference<Boolean> plotImag = BooleanPreference.of("graph_plot_imag", false); } }