Theming improvements (#502)
* Split theme definitions into day and night * Add support for Night Mode in code * Add theme chooser in preferences * Fix translations * Adjust IDs * Adjust preferences for custom themes * UI tweaks for custom theme support * Added code for custom theme support 🍅 * Fixed resource display in Kotlin 🍅 * Restored styles * Updated strings * Fixed getIdentifier() to fit into setTheme() * Removed redundant resources * Reset default theme to "Dusky" * Fixed night mode handler to maintain compatibility * Refactor functions to use helper methods * Added license block * Added preview to theme selector * Added color identifier getter helper method * Fixed reference in AccountMediaFragment * Cleanup * Fixed navbar foreground not changing color * Fix fallback theme switch(){} * Enable location-based daylight trigger * Cleanup * Modified theming strategy to reduce clutter in preferences * Updated translations for latest version * Removed "Default" theme flavor from settings * Updated Polish translations 🇵🇱 * Modified TwilightManager handling code to support Android M's UiModeManager features and moved it to its own function * Updated Polish translations 🇵🇱 * Cleanup; Fixed hardcoded string * Added missing escape in string * Removed permission request dialog. As we now use native UiModeManager APIs that don't need special permission for Android 6.0 and above, we no longer need to bother user with Android M+ specific location permission request dialog. * Increased readability of ThemeUtil class * Refactored ThemeUtils.setAppNightMode method * Cleanup
This commit is contained in:
parent
8fa1320afe
commit
11105f4aac
28 changed files with 341 additions and 142 deletions
|
@ -37,6 +37,8 @@ import com.keylesspalace.tusky.json.SpannedTypeAdapter;
|
|||
import com.keylesspalace.tusky.network.AuthInterceptor;
|
||||
import com.keylesspalace.tusky.network.MastodonApi;
|
||||
import com.keylesspalace.tusky.util.OkHttpUtils;
|
||||
import com.keylesspalace.tusky.util.ResourcesUtils;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
import okhttp3.Dispatcher;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
@ -61,9 +63,15 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||
/* There isn't presently a way to globally change the theme of a whole application at
|
||||
* runtime, just individual activities. So, each activity has to set its theme before any
|
||||
* views are created. */
|
||||
if (preferences.getBoolean("lightTheme", false)) {
|
||||
setTheme(R.style.AppTheme_Light);
|
||||
}
|
||||
String[] themeFlavorPair = preferences.getString("appTheme", TuskyApplication.APP_THEME_DEFAULT).split(":");
|
||||
String appTheme = themeFlavorPair[0], themeFlavorPreference = themeFlavorPair[2];
|
||||
|
||||
setTheme(ResourcesUtils.getResourceIdentifier(this, "style", appTheme));
|
||||
|
||||
String flavor = preferences.getString("appThemeFlavor", ThemeUtils.THEME_FLAVOR_DEFAULT);
|
||||
if (flavor.equals(ThemeUtils.THEME_FLAVOR_DEFAULT))
|
||||
flavor = themeFlavorPreference;
|
||||
ThemeUtils.setAppNightMode(flavor);
|
||||
|
||||
int style;
|
||||
switch(preferences.getString("statusTextSize", "medium")) {
|
||||
|
|
|
@ -26,7 +26,6 @@ import android.os.Bundle;
|
|||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
|
@ -42,6 +41,8 @@ import com.keylesspalace.tusky.network.MastodonApi;
|
|||
import com.keylesspalace.tusky.util.CustomTabsHelper;
|
||||
import com.keylesspalace.tusky.util.NotificationManager;
|
||||
import com.keylesspalace.tusky.util.OkHttpUtils;
|
||||
import com.keylesspalace.tusky.util.ResourcesUtils;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -68,9 +69,16 @@ public class LoginActivity extends AppCompatActivity {
|
|||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("lightTheme", false)) {
|
||||
setTheme(R.style.AppTheme_Light);
|
||||
}
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String[] themeFlavorPair = preferences.getString("appTheme", TuskyApplication.APP_THEME_DEFAULT).split(":");
|
||||
String appTheme = themeFlavorPair[0], themeFlavorPreference = themeFlavorPair[2];
|
||||
|
||||
setTheme(ResourcesUtils.getResourceIdentifier(this, "style", appTheme));
|
||||
|
||||
String flavor = preferences.getString("appThemeFlavor", ThemeUtils.THEME_FLAVOR_DEFAULT);
|
||||
if (flavor.equals(ThemeUtils.THEME_FLAVOR_DEFAULT))
|
||||
flavor = themeFlavorPreference;
|
||||
ThemeUtils.setAppNightMode(flavor);
|
||||
|
||||
setContentView(R.layout.activity_login);
|
||||
|
||||
|
@ -234,15 +242,8 @@ public class LoginActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
private static boolean openInCustomTab(Uri uri, Context context) {
|
||||
boolean lightTheme = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean("lightTheme", false);
|
||||
int toolbarColorRes;
|
||||
if (lightTheme) {
|
||||
toolbarColorRes = R.color.custom_tab_toolbar_light;
|
||||
} else {
|
||||
toolbarColorRes = R.color.custom_tab_toolbar_dark;
|
||||
}
|
||||
int toolbarColor = ContextCompat.getColor(context, toolbarColorRes);
|
||||
int toolbarColor = ThemeUtils.getColorById(context, "custom_tab_toolbar");
|
||||
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor(toolbarColor);
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
|
|
|
@ -27,6 +27,8 @@ import android.support.v7.widget.Toolbar;
|
|||
import android.view.MenuItem;
|
||||
|
||||
import com.keylesspalace.tusky.fragment.PreferencesFragment;
|
||||
import com.keylesspalace.tusky.util.ResourcesUtils;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
|
||||
public class PreferencesActivity extends BaseActivity
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
@ -46,9 +48,6 @@ public class PreferencesActivity extends BaseActivity
|
|||
}
|
||||
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
if (preferences.getBoolean("lightTheme", false)) {
|
||||
setTheme(R.style.AppTheme_Light);
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_preferences);
|
||||
|
||||
|
@ -72,6 +71,28 @@ public class PreferencesActivity extends BaseActivity
|
|||
}
|
||||
showFragment(currentPreferences, currentTitle);
|
||||
|
||||
PreferencesFragment preferencesFragment = (PreferencesFragment)getFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
String[] themeFlavorPair = preferences.getString("appTheme", TuskyApplication.APP_THEME_DEFAULT).split(":");
|
||||
String appTheme = themeFlavorPair[0], themeFlavorMode = themeFlavorPair[1], themeFlavorPreference = themeFlavorPair[2];
|
||||
|
||||
setTheme(ResourcesUtils.getResourceIdentifier(this, "style", appTheme));
|
||||
|
||||
if (preferencesFragment.findPreference("appThemeFlavor") != null) {
|
||||
boolean lockFlavor = themeFlavorMode.equals(ThemeUtils.THEME_MODE_ONLY);
|
||||
preferencesFragment.findPreference("appThemeFlavor").setEnabled(!lockFlavor);
|
||||
}
|
||||
|
||||
String flavor = preferences.getString("appThemeFlavor", ThemeUtils.THEME_FLAVOR_DEFAULT);
|
||||
if (flavor.equals(ThemeUtils.THEME_FLAVOR_DEFAULT)) {
|
||||
flavor = themeFlavorPreference;
|
||||
|
||||
preferences.edit()
|
||||
.putString("appThemeFlavor", flavor)
|
||||
.apply();
|
||||
}
|
||||
|
||||
// Set theme based on preference
|
||||
setTheme(ResourcesUtils.getResourceIdentifier(this, "style", appTheme));
|
||||
}
|
||||
|
||||
public void showFragment(@XmlRes int preferenceId, @StringRes int title) {
|
||||
|
@ -81,6 +102,8 @@ public class PreferencesActivity extends BaseActivity
|
|||
.replace(R.id.fragment_container, PreferencesFragment.newInstance(preferenceId))
|
||||
.commit();
|
||||
|
||||
getFragmentManager().executePendingTransactions();
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(title);
|
||||
|
@ -104,7 +127,32 @@ public class PreferencesActivity extends BaseActivity
|
|||
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
switch (key) {
|
||||
case "lightTheme": {
|
||||
case "appTheme": {
|
||||
String[] themeFlavorPair = sharedPreferences.getString("appTheme", TuskyApplication.APP_THEME_DEFAULT).split(":");
|
||||
String appTheme = themeFlavorPair[0];
|
||||
|
||||
setTheme(ResourcesUtils.getResourceIdentifier(this, "style", appTheme));
|
||||
|
||||
sharedPreferences.edit()
|
||||
.remove("appThemeFlavor")
|
||||
.apply();
|
||||
}
|
||||
case "appThemeFlavor": {
|
||||
String[] themeFlavorPair = sharedPreferences.getString("appTheme", TuskyApplication.APP_THEME_DEFAULT).split(":");
|
||||
String appTheme = themeFlavorPair[0], themeFlavorPreference = themeFlavorPair[2];
|
||||
|
||||
setTheme(ResourcesUtils.getResourceIdentifier(this, "style", appTheme));
|
||||
|
||||
String flavor = sharedPreferences.getString("appThemeFlavor", ThemeUtils.THEME_FLAVOR_DEFAULT);
|
||||
if (flavor.equals(ThemeUtils.THEME_FLAVOR_DEFAULT)) {
|
||||
flavor = themeFlavorPreference;
|
||||
|
||||
sharedPreferences.edit()
|
||||
.putString("appThemeFlavor", flavor)
|
||||
.apply();
|
||||
}
|
||||
ThemeUtils.setAppNightMode(flavor);
|
||||
|
||||
restartActivitiesOnExit = true;
|
||||
// recreate() could be used instead, but it doesn't have an animation B).
|
||||
Intent intent = getIntent();
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
package com.keylesspalace.tusky;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.UiModeManager;
|
||||
import android.arch.persistence.room.Room;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatDelegate;
|
||||
|
@ -28,12 +30,18 @@ import com.keylesspalace.tusky.util.OkHttpUtils;
|
|||
import com.squareup.picasso.Picasso;
|
||||
|
||||
public class TuskyApplication extends Application {
|
||||
public static final String APP_THEME_DEFAULT = "AppTheme:prefer:night";
|
||||
|
||||
private static AppDatabase db;
|
||||
|
||||
public static AppDatabase getDB() {
|
||||
return db;
|
||||
}
|
||||
|
||||
private static UiModeManager uiModeManager;
|
||||
|
||||
public static UiModeManager getUiModeManager() { return uiModeManager; }
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
@ -59,6 +67,8 @@ public class TuskyApplication extends Application {
|
|||
|
||||
JobManager.create(this).addJobCreator(new NotificationPullJobCreator(this));
|
||||
|
||||
uiModeManager = (UiModeManager)getSystemService(Context.UI_MODE_SERVICE);
|
||||
|
||||
//necessary for Android < APi 21
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package com.keylesspalace.tusky.fragment
|
|||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v4.app.ActivityOptionsCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v4.view.ViewCompat
|
||||
|
@ -26,6 +25,7 @@ import android.support.v4.widget.SwipeRefreshLayout
|
|||
import android.support.v7.widget.GridLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -37,6 +37,7 @@ import com.keylesspalace.tusky.ViewVideoActivity
|
|||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.ThemeUtils
|
||||
import com.keylesspalace.tusky.view.SquareImageView
|
||||
import com.squareup.picasso.Picasso
|
||||
import retrofit2.Call
|
||||
|
@ -133,10 +134,8 @@ class AccountMediaFragment : BaseFragment() {
|
|||
val columnCount = context?.resources?.getInteger(R.integer.profile_media_column_count) ?: 2
|
||||
val layoutManager = GridLayoutManager(context, columnCount)
|
||||
|
||||
val lightThemeEnabled = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getBoolean("lightTheme", false)
|
||||
val bgRes = if (lightThemeEnabled) R.color.window_background_light
|
||||
else R.color.window_background_dark
|
||||
val bgRes = ThemeUtils.getColorId(context, R.attr.window_background)
|
||||
|
||||
adapter.baseItemColor = ContextCompat.getColor(recyclerView.context, bgRes)
|
||||
|
||||
recyclerView.layoutManager = layoutManager
|
||||
|
|
|
@ -23,7 +23,6 @@ import android.preference.PreferenceManager;
|
|||
import android.provider.Browser;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextPaint;
|
||||
|
@ -34,7 +33,6 @@ import android.util.Log;
|
|||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||
|
||||
|
@ -172,8 +170,8 @@ public class LinkHelper {
|
|||
* @param context context
|
||||
*/
|
||||
public static void openLinkInCustomTab(Uri uri, Context context) {
|
||||
boolean lightTheme = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("lightTheme", false);
|
||||
int toolbarColor = ContextCompat.getColor(context, lightTheme ? R.color.custom_tab_toolbar_light : R.color.custom_tab_toolbar_dark);
|
||||
int toolbarColor = ThemeUtils.getColorById(context, "custom_tab_toolbar");
|
||||
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor(toolbarColor);
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/* Copyright 2017 Andrew Dawson
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Tusky 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 Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.AnyRes;
|
||||
|
||||
/**
|
||||
* Created by remi on 1/14/18.
|
||||
*/
|
||||
|
||||
public class ResourcesUtils {
|
||||
public static @AnyRes int getResourceIdentifier(Context context, String defType, String name) {
|
||||
return context.getResources().getIdentifier(name, defType, context.getPackageName());
|
||||
}
|
||||
}
|
|
@ -15,22 +15,35 @@
|
|||
|
||||
package com.keylesspalace.tusky.util;
|
||||
|
||||
import android.app.UiModeManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.AttrRes;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.ColorRes;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AppCompatDelegate;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.keylesspalace.tusky.TuskyApplication;
|
||||
|
||||
/**
|
||||
* Provides runtime compatibility to obtain theme information and re-theme views, especially where
|
||||
* the ability to do so is not supported in resource files.
|
||||
*/
|
||||
public class ThemeUtils {
|
||||
public static final String THEME_MODE_PREFER = "prefer";
|
||||
public static final String THEME_MODE_ONLY = "only";
|
||||
public static final String THEME_FLAVOR_NIGHT = "night";
|
||||
public static final String THEME_FLAVOR_DAY = "day";
|
||||
public static final String THEME_FLAVOR_AUTO = "auto";
|
||||
public static final String THEME_FLAVOR_DEFAULT = "preferred";
|
||||
|
||||
public static Drawable getDrawable(Context context, @AttrRes int attribute,
|
||||
@DrawableRes int fallbackDrawable) {
|
||||
TypedValue value = new TypedValue();
|
||||
|
@ -62,6 +75,17 @@ public class ThemeUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static @ColorRes int getColorId(Context context, @AttrRes int attribute) {
|
||||
TypedValue value = new TypedValue();
|
||||
context.getTheme().resolveAttribute(attribute, value, true);
|
||||
return value.resourceId;
|
||||
}
|
||||
|
||||
public static @ColorInt int getColorById(Context context, String name) {
|
||||
return getColor(context,
|
||||
ResourcesUtils.getResourceIdentifier(context, "attr", name));
|
||||
}
|
||||
|
||||
public static void setImageViewTint(ImageView view, @AttrRes int attribute) {
|
||||
view.setColorFilter(getColor(view.getContext(), attribute), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
@ -69,4 +93,29 @@ public class ThemeUtils {
|
|||
public static void setDrawableTint(Context context, Drawable drawable, @AttrRes int attribute) {
|
||||
drawable.setColorFilter(getColor(context, attribute), PorterDuff.Mode.SRC_IN);
|
||||
}
|
||||
|
||||
public static boolean setAppNightMode(String flavor) {
|
||||
int mode;
|
||||
switch (flavor) {
|
||||
case THEME_FLAVOR_AUTO:
|
||||
mode = UiModeManager.MODE_NIGHT_AUTO;
|
||||
break;
|
||||
case THEME_FLAVOR_NIGHT:
|
||||
mode = UiModeManager.MODE_NIGHT_YES;
|
||||
break;
|
||||
case THEME_FLAVOR_DAY:
|
||||
mode = UiModeManager.MODE_NIGHT_NO;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
TuskyApplication.getUiModeManager().setNightMode(mode);
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(mode);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue