diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATH.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATH.kt new file mode 100755 index 00000000..2df77a44 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATH.kt @@ -0,0 +1,119 @@ +package code.name.monkey.appthemehelper + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.ActivityManager +import android.content.Context +import android.os.Build +import android.view.View + +import androidx.annotation.ColorInt +import androidx.appcompat.widget.Toolbar +import code.name.monkey.appthemehelper.util.ColorUtil +import code.name.monkey.appthemehelper.util.TintHelper +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper + +import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +object ATH { + + @SuppressLint("CommitPrefEdits") + fun didThemeValuesChange(context: Context, since: Long): Boolean { + return ThemeStore.isConfigured(context) && ThemeStore.prefs(context).getLong(ThemeStore.VALUES_CHANGED, -1) > since + } + + fun setStatusbarColorAuto(activity: Activity) { + setStatusbarColor(activity, ThemeStore.statusBarColor(activity)) + } + + fun setStatusbarColor(activity: Activity, color: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.window.statusBarColor = color + setLightStatusbarAuto(activity, color) + } + } + + fun setLightStatusbarAuto(activity: Activity, bgColor: Int) { + setLightStatusbar(activity, ColorUtil.isColorLight(bgColor)) + } + + fun setLightStatusbar(activity: Activity, enabled: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val decorView = activity.window.decorView + val systemUiVisibility = decorView.systemUiVisibility + if (enabled) { + decorView.systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } else { + decorView.systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() + } + } + } + + fun setLightNavigationbar(activity: Activity, enabled: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val decorView = activity.window.decorView + var systemUiVisibility = decorView.systemUiVisibility + if (enabled) { + systemUiVisibility = systemUiVisibility or SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + } else { + systemUiVisibility = systemUiVisibility and SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv() + } + decorView.systemUiVisibility = systemUiVisibility + } + } + + fun setLightNavigationbarAuto(activity: Activity, bgColor: Int) { + setLightNavigationbar(activity, ColorUtil.isColorLight(bgColor)) + } + + fun setNavigationbarColorAuto(activity: Activity) { + setNavigationbarColor(activity, ThemeStore.navigationBarColor(activity)) + } + + fun setNavigationbarColor(activity: Activity, color: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + activity.window.navigationBarColor = color + } else { + activity.window.navigationBarColor = ColorUtil.darkenColor(color) + } + setLightNavigationbarAuto(activity, color) + } + + fun setActivityToolbarColorAuto(activity: Activity, toolbar: Toolbar?) { + setActivityToolbarColor(activity, toolbar, ThemeStore.primaryColor(activity)) + } + + fun setActivityToolbarColor(activity: Activity, toolbar: Toolbar?, + color: Int) { + if (toolbar == null) { + return + } + toolbar.setBackgroundColor(color) + ToolbarContentTintHelper.setToolbarContentColorBasedOnToolbarColor(activity, toolbar, color) + } + + fun setTaskDescriptionColorAuto(activity: Activity) { + setTaskDescriptionColor(activity, ThemeStore.primaryColor(activity)) + } + + fun setTaskDescriptionColor(activity: Activity, @ColorInt color: Int) { + var colorFinal = color + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // Task description requires fully opaque color + colorFinal = ColorUtil.stripAlpha(colorFinal) + // Sets color of entry in the system recents page + activity.setTaskDescription(ActivityManager.TaskDescription(activity.title as String, null, colorFinal)) + } + } + + fun setTint(view: View, @ColorInt color: Int) { + TintHelper.setTintAuto(view, color, false) + } + + fun setBackgroundTint(view: View, @ColorInt color: Int) { + TintHelper.setTintAuto(view, color, true) + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATHActivity.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATHActivity.kt new file mode 100755 index 00000000..5ca5cdbc --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ATHActivity.kt @@ -0,0 +1,43 @@ +package code.name.monkey.appthemehelper + +import android.content.Intent +import android.os.Bundle +import android.os.Handler + +import androidx.annotation.StyleRes +import androidx.appcompat.app.AppCompatActivity + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +open class ATHActivity : AppCompatActivity() { + + private var updateTime: Long = -1 + + private val themeRes: Int + @StyleRes + get() = ThemeStore.activityTheme(this) + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(themeRes) + super.onCreate(savedInstanceState) + updateTime = System.currentTimeMillis() + } + + override fun onResume() { + super.onResume() + if (ATH.didThemeValuesChange(this, updateTime)) { + onThemeChanged() + } + } + + fun onThemeChanged() { + postRecreate() + } + + fun postRecreate() { + // hack to prevent java.lang.RuntimeException: Performing pause of activity that is not resumed + // makes sure recreate() is called right after and not in onResume() + Handler().post { recreate() } + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStore.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStore.kt new file mode 100755 index 00000000..56557ea0 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStore.kt @@ -0,0 +1,288 @@ +package code.name.monkey.appthemehelper + +import android.annotation.SuppressLint +import android.content.Context +import android.content.SharedPreferences +import android.graphics.Color +import androidx.annotation.AttrRes +import androidx.annotation.CheckResult +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.IntRange +import androidx.annotation.StyleRes +import androidx.core.content.ContextCompat + +import code.name.monkey.appthemehelper.util.ATHUtil +import code.name.monkey.appthemehelper.util.ColorUtil + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +class ThemeStore @SuppressLint("CommitPrefEdits") +private constructor(private val mContext: Context) : ThemeStorePrefKeys, ThemeStoreInterface { + private val mEditor: SharedPreferences.Editor + + init { + mEditor = prefs(mContext).edit() + } + + override fun activityTheme(@StyleRes theme: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_ACTIVITY_THEME, theme) + return this + } + + override fun primaryColor(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_PRIMARY_COLOR, color) + if (autoGeneratePrimaryDark(mContext)) + primaryColorDark(ColorUtil.darkenColor(color)) + return this + } + + override fun primaryColorRes(@ColorRes colorRes: Int): ThemeStore { + return primaryColor(ContextCompat.getColor(mContext, colorRes)) + } + + override fun primaryColorAttr(@AttrRes colorAttr: Int): ThemeStore { + return primaryColor(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun primaryColorDark(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_PRIMARY_COLOR_DARK, color) + return this + } + + override fun primaryColorDarkRes(@ColorRes colorRes: Int): ThemeStore { + return primaryColorDark(ContextCompat.getColor(mContext, colorRes)) + } + + override fun primaryColorDarkAttr(@AttrRes colorAttr: Int): ThemeStore { + return primaryColorDark(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun accentColor(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_ACCENT_COLOR, color) + return this + } + + override fun accentColorRes(@ColorRes colorRes: Int): ThemeStore { + return accentColor(ContextCompat.getColor(mContext, colorRes)) + } + + override fun accentColorAttr(@AttrRes colorAttr: Int): ThemeStore { + return accentColor(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun statusBarColor(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR, color) + return this + } + + override fun statusBarColorRes(@ColorRes colorRes: Int): ThemeStore { + return statusBarColor(ContextCompat.getColor(mContext, colorRes)) + } + + override fun statusBarColorAttr(@AttrRes colorAttr: Int): ThemeStore { + return statusBarColor(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun navigationBarColor(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_NAVIGATION_BAR_COLOR, color) + return this + } + + // Commit method + + override fun navigationBarColorRes(@ColorRes colorRes: Int): ThemeStore { + return navigationBarColor(ContextCompat.getColor(mContext, colorRes)) + } + + // Static getters + + override fun navigationBarColorAttr(@AttrRes colorAttr: Int): ThemeStore { + return navigationBarColor(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun textColorPrimary(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY, color) + return this + } + + override fun textColorPrimaryRes(@ColorRes colorRes: Int): ThemeStore { + return textColorPrimary(ContextCompat.getColor(mContext, colorRes)) + } + + override fun textColorPrimaryAttr(@AttrRes colorAttr: Int): ThemeStore { + return textColorPrimary(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun textColorPrimaryInverse(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY_INVERSE, color) + return this + } + + override fun textColorPrimaryInverseRes(@ColorRes colorRes: Int): ThemeStore { + return textColorPrimaryInverse(ContextCompat.getColor(mContext, colorRes)) + } + + override fun textColorPrimaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore { + return textColorPrimaryInverse(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun textColorSecondary(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY, color) + return this + } + + override fun textColorSecondaryRes(@ColorRes colorRes: Int): ThemeStore { + return textColorSecondary(ContextCompat.getColor(mContext, colorRes)) + } + + override fun textColorSecondaryAttr(@AttrRes colorAttr: Int): ThemeStore { + return textColorSecondary(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun textColorSecondaryInverse(@ColorInt color: Int): ThemeStore { + mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY_INVERSE, color) + return this + } + + override fun textColorSecondaryInverseRes(@ColorRes colorRes: Int): ThemeStore { + return textColorSecondaryInverse(ContextCompat.getColor(mContext, colorRes)) + } + + override fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore { + return textColorSecondaryInverse(ATHUtil.resolveColor(mContext, colorAttr)) + } + + override fun coloredStatusBar(colored: Boolean): ThemeStore { + mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, colored) + return this + } + + override fun coloredNavigationBar(applyToNavBar: Boolean): ThemeStore { + mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARY_NAVBAR, applyToNavBar) + return this + } + + override fun autoGeneratePrimaryDark(autoGenerate: Boolean): ThemeStore { + mEditor.putBoolean(ThemeStorePrefKeys.KEY_AUTO_GENERATE_PRIMARYDARK, autoGenerate) + return this + } + + override fun commit() { + mEditor.putLong(ThemeStorePrefKeys.VALUES_CHANGED, System.currentTimeMillis()) + .putBoolean(ThemeStorePrefKeys.IS_CONFIGURED_KEY, true) + .commit() + } + + companion object { + + fun editTheme(context: Context): ThemeStore { + return ThemeStore(context) + } + + @CheckResult + protected fun prefs(context: Context): SharedPreferences { + return context.getSharedPreferences(ThemeStorePrefKeys.CONFIG_PREFS_KEY_DEFAULT, Context.MODE_PRIVATE) + } + + fun markChanged(context: Context) { + ThemeStore(context).commit() + } + + @CheckResult + @StyleRes + fun activityTheme(context: Context): Int { + return prefs(context).getInt(ThemeStorePrefKeys.KEY_ACTIVITY_THEME, 0) + } + + @CheckResult + @ColorInt + fun primaryColor(context: Context): Int { + return prefs(context).getInt(ThemeStorePrefKeys.KEY_PRIMARY_COLOR, ATHUtil.resolveColor(context, R.attr.colorPrimary, Color.parseColor("#455A64"))) + } + + @CheckResult + @ColorInt + fun primaryColorDark(context: Context): Int { + return prefs(context).getInt(ThemeStorePrefKeys.KEY_PRIMARY_COLOR_DARK, ATHUtil.resolveColor(context, R.attr.colorPrimaryDark, Color.parseColor("#37474F"))) + } + + @CheckResult + @ColorInt + fun accentColor(context: Context): Int { + return prefs(context).getInt(ThemeStorePrefKeys.KEY_ACCENT_COLOR, ATHUtil.resolveColor(context, R.attr.colorAccent, Color.parseColor("#263238"))) + } + + @CheckResult + @ColorInt + fun statusBarColor(context: Context): Int { + return if (!coloredStatusBar(context)) { + Color.BLACK + } else prefs(context).getInt(ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR, primaryColorDark(context)) + } + + @CheckResult + @ColorInt + fun navigationBarColor(context: Context): Int { + return if (!coloredNavigationBar(context)) { + Color.BLACK + } else prefs(context).getInt(ThemeStorePrefKeys.KEY_NAVIGATION_BAR_COLOR, primaryColor(context)) + } + + @CheckResult + @ColorInt + fun textColorPrimary(context: Context): Int { + return prefs(context).getInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY, ATHUtil.resolveColor(context, android.R.attr.textColorPrimary)) + } + + @CheckResult + @ColorInt + fun textColorPrimaryInverse(context: Context): Int { + return prefs(context).getInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY_INVERSE, ATHUtil.resolveColor(context, android.R.attr.textColorPrimaryInverse)) + } + + @CheckResult + @ColorInt + fun textColorSecondary(context: Context): Int { + return prefs(context).getInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY, ATHUtil.resolveColor(context, android.R.attr.textColorSecondary)) + } + + @CheckResult + @ColorInt + fun textColorSecondaryInverse(context: Context): Int { + return prefs(context).getInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY_INVERSE, ATHUtil.resolveColor(context, android.R.attr.textColorSecondaryInverse)) + } + + @CheckResult + fun coloredStatusBar(context: Context): Boolean { + return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true) + } + + @CheckResult + fun coloredNavigationBar(context: Context): Boolean { + return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARY_NAVBAR, false) + } + + @CheckResult + fun autoGeneratePrimaryDark(context: Context): Boolean { + return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_AUTO_GENERATE_PRIMARYDARK, true) + } + + @CheckResult + fun isConfigured(context: Context): Boolean { + return prefs(context).getBoolean(ThemeStorePrefKeys.IS_CONFIGURED_KEY, false) + } + + @SuppressLint("CommitPrefEdits") + fun isConfigured(context: Context, @IntRange(from = 0, to = Integer.MAX_VALUE.toLong()) version: Int): Boolean { + val prefs = prefs(context) + val lastVersion = prefs.getInt(ThemeStorePrefKeys.IS_CONFIGURED_VERSION_KEY, -1) + if (version > lastVersion) { + prefs.edit().putInt(ThemeStorePrefKeys.IS_CONFIGURED_VERSION_KEY, version).commit() + return false + } + return true + } + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStoreInterface.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStoreInterface.kt new file mode 100755 index 00000000..cbcb41f0 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStoreInterface.kt @@ -0,0 +1,94 @@ +package code.name.monkey.appthemehelper + +import androidx.annotation.AttrRes +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.annotation.StyleRes + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +internal interface ThemeStoreInterface { + + // Activity theme + + fun activityTheme(@StyleRes theme: Int): ThemeStore + + // Primary colors + + fun primaryColor(@ColorInt color: Int): ThemeStore + + fun primaryColorRes(@ColorRes colorRes: Int): ThemeStore + + fun primaryColorAttr(@AttrRes colorAttr: Int): ThemeStore + + fun autoGeneratePrimaryDark(autoGenerate: Boolean): ThemeStore + + fun primaryColorDark(@ColorInt color: Int): ThemeStore + + fun primaryColorDarkRes(@ColorRes colorRes: Int): ThemeStore + + fun primaryColorDarkAttr(@AttrRes colorAttr: Int): ThemeStore + + // Accent colors + + fun accentColor(@ColorInt color: Int): ThemeStore + + fun accentColorRes(@ColorRes colorRes: Int): ThemeStore + + fun accentColorAttr(@AttrRes colorAttr: Int): ThemeStore + + // Status bar color + + fun statusBarColor(@ColorInt color: Int): ThemeStore + + fun statusBarColorRes(@ColorRes colorRes: Int): ThemeStore + + fun statusBarColorAttr(@AttrRes colorAttr: Int): ThemeStore + + // Navigation bar color + + fun navigationBarColor(@ColorInt color: Int): ThemeStore + + fun navigationBarColorRes(@ColorRes colorRes: Int): ThemeStore + + fun navigationBarColorAttr(@AttrRes colorAttr: Int): ThemeStore + + // Primary text color + + fun textColorPrimary(@ColorInt color: Int): ThemeStore + + fun textColorPrimaryRes(@ColorRes colorRes: Int): ThemeStore + + fun textColorPrimaryAttr(@AttrRes colorAttr: Int): ThemeStore + + fun textColorPrimaryInverse(@ColorInt color: Int): ThemeStore + + fun textColorPrimaryInverseRes(@ColorRes colorRes: Int): ThemeStore + + fun textColorPrimaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore + + // Secondary text color + + fun textColorSecondary(@ColorInt color: Int): ThemeStore + + fun textColorSecondaryRes(@ColorRes colorRes: Int): ThemeStore + + fun textColorSecondaryAttr(@AttrRes colorAttr: Int): ThemeStore + + fun textColorSecondaryInverse(@ColorInt color: Int): ThemeStore + + fun textColorSecondaryInverseRes(@ColorRes colorRes: Int): ThemeStore + + fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore + + // Toggle configurations + + fun coloredStatusBar(colored: Boolean): ThemeStore + + fun coloredNavigationBar(applyToNavBar: Boolean): ThemeStore + + // Commit/apply + + fun commit() +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStorePrefKeys.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStorePrefKeys.kt new file mode 100755 index 00000000..6dee8266 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/ThemeStorePrefKeys.kt @@ -0,0 +1,31 @@ +package code.name.monkey.appthemehelper + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +internal interface ThemeStorePrefKeys { + companion object { + + const val CONFIG_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]" + const val IS_CONFIGURED_KEY = "is_configured" + const val IS_CONFIGURED_VERSION_KEY = "is_configured_version" + const val VALUES_CHANGED = "values_changed" + + const val KEY_ACTIVITY_THEME = "activity_theme" + + const val KEY_PRIMARY_COLOR = "primary_color" + const val KEY_PRIMARY_COLOR_DARK = "primary_color_dark" + const val KEY_ACCENT_COLOR = "accent_color" + const val KEY_STATUS_BAR_COLOR = "status_bar_color" + const val KEY_NAVIGATION_BAR_COLOR = "navigation_bar_color" + + const val KEY_TEXT_COLOR_PRIMARY = "text_color_primary" + const val KEY_TEXT_COLOR_PRIMARY_INVERSE = "text_color_primary_inverse" + const val KEY_TEXT_COLOR_SECONDARY = "text_color_secondary" + const val KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse" + + const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar" + const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar" + const val KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark" + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/ATHActionBarActivity.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/ATHActionBarActivity.kt new file mode 100755 index 00000000..225d28e3 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/ATHActionBarActivity.kt @@ -0,0 +1,12 @@ +package code.name.monkey.appthemehelper.common + +import androidx.appcompat.widget.Toolbar + +import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper + +class ATHActionBarActivity : ATHToolbarActivity() { + + override fun getATHToolbar(): Toolbar? { + return ToolbarContentTintHelper.getSupportActionBarView(supportActionBar) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATECheckBoxPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATECheckBoxPreference.kt new file mode 100755 index 00000000..eda460f2 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATECheckBoxPreference.kt @@ -0,0 +1,70 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.preference.CheckBoxPreference +import android.preference.Preference +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.R +import code.name.monkey.appthemehelper.ThemeStore + +/** + * @author Aidan Follestad (afollestad) + */ +class ATECheckBoxPreference @TargetApi(Build.VERSION_CODES.LOLLIPOP) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : CheckBoxPreference(context, attrs, defStyleAttr, defStyleRes) { + + init { + init() + } + + private fun init() { + layoutResource = R.layout.ate_preference_custom + + try { + val canRecycleLayoutField = Preference::class.java.getDeclaredField("mCanRecycleLayout") + canRecycleLayoutField.isAccessible = true + canRecycleLayoutField.setBoolean(this, true) + } catch (ignored: Exception) { + } + + try { + val hasSpecifiedLayout = Preference::class.java.getDeclaredField("mHasSpecifiedLayout") + hasSpecifiedLayout.isAccessible = true + hasSpecifiedLayout.setBoolean(this, true) + } catch (ignored: Exception) { + } + + } + + override fun onBindView(view: View) { + super.onBindView(view) + + val parentCheckbox = findCheckboxView(view) + if (parentCheckbox != null) { + ATH.setTint(parentCheckbox, ThemeStore.accentColor(view.context)) + } + } + + private fun findCheckboxView(view: View): View? { + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + val child = view.getChildAt(i) + if (child is CheckBox) { + return child + } else if (child is ViewGroup) { + val potentialCheckbox = findCheckboxView(child) + if (potentialCheckbox != null) return potentialCheckbox + } + } + } else if (view is CheckBox) { + return view + } + return null + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEColorPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEColorPreference.kt new file mode 100755 index 00000000..48f30f21 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEColorPreference.kt @@ -0,0 +1,54 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.content.Context +import android.preference.Preference +import android.util.AttributeSet +import android.view.View + +import code.name.monkey.appthemehelper.R + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEColorPreference(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : Preference(context, attrs, defStyleAttr) { + + private var mView: View? = null + private var color: Int = 0 + private var border: Int = 0 + + init { + init() + + } + + private fun init() { + layoutResource = R.layout.ate_preference_custom + widgetLayoutResource = R.layout.ate_preference_color + isPersistent = false + } + + override fun onBindView(view: View) { + super.onBindView(view) + mView = view + invalidateColor() + } + + fun setColor(color: Int, border: Int) { + this.color = color + this.border = border + invalidateColor() + } + + private fun invalidateColor() { + if (mView != null) { + val circle = mView!!.findViewById(R.id.circle) as BorderCircleView + if (this.color != 0) { + circle.visibility = View.VISIBLE + circle.setBackgroundColor(color) + circle.setBorderColor(border) + } else { + circle.visibility = View.GONE + } + } + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEDialogPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEDialogPreference.kt new file mode 100755 index 00000000..bbfd887c --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEDialogPreference.kt @@ -0,0 +1,20 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.content.Context +import code.name.monkey.appthemehelper.R +import com.afollestad.materialdialogs.prefs.MaterialDialogPreference + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEDialogPreference(context: Context) : MaterialDialogPreference(context) { + + init { + init() + } + + private fun init() { + layoutResource = R.layout.ate_preference_custom + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEEditTextPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEEditTextPreference.kt new file mode 100755 index 00000000..4e395c4e --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEEditTextPreference.kt @@ -0,0 +1,20 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.content.Context +import android.util.AttributeSet + +import com.afollestad.materialdialogs.prefs.MaterialEditTextPreference + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEEditTextPreference(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : MaterialEditTextPreference(context, attrs, defStyleAttr, defStyleRes) { + + init { + init() + } + + private fun init() { + layoutResource = code.name.monkey.appthemehelper.R.layout.ate_preference_custom + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEListPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEListPreference.kt new file mode 100755 index 00000000..f88aa282 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEListPreference.kt @@ -0,0 +1,35 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.content.Context +import android.util.AttributeSet + +import com.afollestad.materialdialogs.prefs.MaterialListPreference +import code.name.monkey.appthemehelper.R + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEListPreference : MaterialListPreference { + + constructor(context: Context) : super(context) { + init() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init() + } + + private fun init() { + layoutResource = R.layout.ate_preference_custom + if (summary == null || summary.toString().trim { it <= ' ' }.isEmpty()) + summary = "%s" + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEMultiSelectPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEMultiSelectPreference.kt new file mode 100755 index 00000000..406e0a56 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEMultiSelectPreference.kt @@ -0,0 +1,21 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.content.Context +import android.util.AttributeSet + +import com.afollestad.materialdialogs.prefs.MaterialListPreference +import code.name.monkey.appthemehelper.R + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEMultiSelectPreference(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : MaterialListPreference(context, attrs, defStyleAttr, defStyleRes) { + + init { + init() + } + + private fun init() { + layoutResource = R.layout.ate_preference_custom + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEPreference.kt new file mode 100755 index 00000000..533f629c --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEPreference.kt @@ -0,0 +1,23 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.preference.Preference +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.R + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEPreference @TargetApi(Build.VERSION_CODES.LOLLIPOP) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : Preference(context, attrs, defStyleAttr, defStyleRes) { + + init { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEPreferenceCategory.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEPreferenceCategory.kt new file mode 100755 index 00000000..9d58beff --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATEPreferenceCategory.kt @@ -0,0 +1,20 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.preference.PreferenceCategory +import android.util.AttributeSet +import android.view.View +import android.widget.TextView + +import code.name.monkey.appthemehelper.ThemeStore + +class ATEPreferenceCategory @TargetApi(Build.VERSION_CODES.LOLLIPOP) constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : PreferenceCategory(context, attrs, defStyleAttr, defStyleRes) { + + override fun onBindView(view: View) { + super.onBindView(view) + val mTitle = view.findViewById(android.R.id.title) as TextView + mTitle.setTextColor(ThemeStore.accentColor(view.context)) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATESwitchPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATESwitchPreference.kt new file mode 100755 index 00000000..997e9d78 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/ATESwitchPreference.kt @@ -0,0 +1,110 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.preference.Preference +import android.preference.SwitchPreference +import androidx.appcompat.widget.SwitchCompat +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.Switch + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.R +import code.name.monkey.appthemehelper.ThemeStore +import code.name.monkey.appthemehelper.common.views.ATESwitch + +import java.lang.reflect.Field + +/** + * @author Aidan Follestad (afollestad) + */ +class ATESwitchPreference : SwitchPreference { + + private var mSwitch: ATESwitch? = null + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom + if (COMPAT_MODE) { + widgetLayoutResource = R.layout.ate_preference_switch + } else { + try { + val canRecycleLayoutField = Preference::class.java.getDeclaredField("mCanRecycleLayout") + canRecycleLayoutField.isAccessible = true + canRecycleLayoutField.setBoolean(this, true) + } catch (ignored: Exception) { + } + + try { + val hasSpecifiedLayout = Preference::class.java.getDeclaredField("mHasSpecifiedLayout") + hasSpecifiedLayout.isAccessible = true + hasSpecifiedLayout.setBoolean(this, true) + } catch (ignored: Exception) { + } + + } + } + + override fun onBindView(view: View) { + super.onBindView(view) + if (COMPAT_MODE) { + mSwitch = view.findViewById(R.id.switchWidget) as ATESwitch + mSwitch!!.isChecked = isChecked + } else { + val parentSwitch = findSwitchView(view) + if (parentSwitch != null) { + ATH.setTint(parentSwitch, ThemeStore.accentColor(view.context)) + } + } + } + + private fun findSwitchView(view: View): View? { + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + val child = view.getChildAt(i) + if (child is Switch || child is SwitchCompat) { + return child + } else if (child is ViewGroup) { + val potentialSwitch = findSwitchView(child) + if (potentialSwitch != null) return potentialSwitch + } + } + } else if (view is Switch || view is SwitchCompat) { + return view + } + return null + } + + override fun setChecked(checked: Boolean) { + super.setChecked(checked) + if (COMPAT_MODE) { + if (mSwitch != null) { + mSwitch!!.isChecked = checked + } + } + } + + companion object { + + internal val COMPAT_MODE = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/BorderCircleView.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/BorderCircleView.kt new file mode 100755 index 00000000..9a86a12c --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/BorderCircleView.kt @@ -0,0 +1,98 @@ +package code.name.monkey.appthemehelper.common.prefs + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.drawable.Drawable +import androidx.core.content.ContextCompat +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout + +import code.name.monkey.appthemehelper.R + +class BorderCircleView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) { + + private val mCheck: Drawable? = ContextCompat.getDrawable(context, R.drawable.ate_check) + private val paint: Paint = Paint() + private val paintBorder: Paint + private val borderWidth: Int = resources.getDimension(R.dimen.ate_circleview_border).toInt() + + private var paintCheck: Paint? = null + private var blackFilter: PorterDuffColorFilter? = null + private var whiteFilter: PorterDuffColorFilter? = null + + init { + + paint.isAntiAlias = true + + paintBorder = Paint() + paintBorder.isAntiAlias = true + paintBorder.color = Color.BLACK + + setWillNotDraw(false) + } + + override fun setBackgroundColor(color: Int) { + paint.color = color + requestLayout() + invalidate() + } + + fun setBorderColor(color: Int) { + paintBorder.color = color + requestLayout() + invalidate() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val widthMode = View.MeasureSpec.getMode(widthMeasureSpec) + val heightMode = View.MeasureSpec.getMode(heightMeasureSpec) + if (widthMode == View.MeasureSpec.EXACTLY && heightMode != View.MeasureSpec.EXACTLY) { + val width = View.MeasureSpec.getSize(widthMeasureSpec) + + var height = width + if (heightMode == View.MeasureSpec.AT_MOST) { + height = Math.min(height, View.MeasureSpec.getSize(heightMeasureSpec)) + } + setMeasuredDimension(width, height) + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + var canvasSize = canvas.width + if (canvas.height < canvasSize) { + canvasSize = canvas.height + } + + val circleCenter = (canvasSize - borderWidth * 2) / 2 + canvas.drawCircle((circleCenter + borderWidth).toFloat(), (circleCenter + borderWidth).toFloat(), (canvasSize - borderWidth * 2) / 2 + borderWidth - 4.0f, paintBorder) + canvas.drawCircle((circleCenter + borderWidth).toFloat(), (circleCenter + borderWidth).toFloat(), (canvasSize - borderWidth * 2) / 2 - 4.0f, paint) + + if (isActivated) { + val offset = canvasSize / 2 - mCheck!!.intrinsicWidth / 2 + if (paintCheck == null) { + paintCheck = Paint() + paintCheck!!.isAntiAlias = true + } + if (whiteFilter == null || blackFilter == null) { + blackFilter = PorterDuffColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN) + whiteFilter = PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN) + } + if (paint.color == Color.WHITE) { + paintCheck!!.colorFilter = blackFilter + } else { + paintCheck!!.colorFilter = whiteFilter + } + mCheck.setBounds(offset, offset, mCheck.intrinsicWidth - offset, mCheck.intrinsicHeight - offset) + mCheck.draw(canvas) + } + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATECheckBoxPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATECheckBoxPreference.kt new file mode 100755 index 00000000..1fce8858 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATECheckBoxPreference.kt @@ -0,0 +1,38 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import androidx.preference.CheckBoxPreference +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.R + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATECheckBoxPreference : CheckBoxPreference { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom_support + widgetLayoutResource = R.layout.ate_preference_checkbox + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEColorPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEColorPreference.kt new file mode 100755 index 00000000..2790d652 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEColorPreference.kt @@ -0,0 +1,64 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.content.Context +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import android.util.AttributeSet +import android.view.View + +import code.name.monkey.appthemehelper.R +import code.name.monkey.appthemehelper.common.prefs.BorderCircleView + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEColorPreference(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : Preference(context, attrs, defStyleAttr) { + + private var mView: View? = null + private var color: Int = 0 + private var border: Int = 0 + + constructor(context: Context) : this(context, null, 0) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) { + init(context, attrs) + } + + init { + init(context, attrs) + + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom_support + widgetLayoutResource = R.layout.ate_preference_color + isPersistent = false + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + mView = holder.itemView + invalidateColor() + } + + fun setColor(color: Int, border: Int) { + this.color = color + this.border = border + invalidateColor() + } + + private fun invalidateColor() { + if (mView != null) { + val circle = mView!!.findViewById(R.id.circle) as BorderCircleView + if (this.color != 0) { + circle.visibility = View.VISIBLE + circle.setBackgroundColor(color) + circle.setBorderColor(border) + } else { + circle.visibility = View.GONE + } + } + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEDialogPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEDialogPreference.kt new file mode 100755 index 00000000..b37ef98a --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEDialogPreference.kt @@ -0,0 +1,34 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.content.Context +import androidx.preference.DialogPreference +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.R + + +/** + * @author Aidan Follestad (afollestad) + */ +open class ATEDialogPreference : DialogPreference { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom_support + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEEditTextPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEEditTextPreference.kt new file mode 100755 index 00000000..d73fe005 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEEditTextPreference.kt @@ -0,0 +1,34 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.content.Context +import androidx.preference.EditTextPreference +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.R + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEEditTextPreference : EditTextPreference { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom_support + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEListPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEListPreference.kt new file mode 100755 index 00000000..011d9fa8 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEListPreference.kt @@ -0,0 +1,35 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.content.Context +import androidx.preference.ListPreference +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.R + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEListPreference : ListPreference { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom_support + if (summary == null || summary.toString().trim { it <= ' ' }.isEmpty()) + summary = "%s" + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreference.kt new file mode 100755 index 00000000..f9d63998 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreference.kt @@ -0,0 +1,36 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import androidx.preference.Preference +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.R + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEPreference : Preference { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom_support + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreferenceCategory.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreferenceCategory.kt new file mode 100755 index 00000000..d86956db --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreferenceCategory.kt @@ -0,0 +1,42 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceViewHolder +import android.util.AttributeSet +import android.widget.TextView + +import code.name.monkey.appthemehelper.R +import code.name.monkey.appthemehelper.ThemeStore + +class ATEPreferenceCategory : PreferenceCategory { + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context) : super(context) { + init(context, null) + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val mTitle = holder.itemView as TextView + mTitle.setTextColor(ThemeStore.accentColor(context)) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_category + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreferenceFragmentCompat.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreferenceFragmentCompat.kt new file mode 100755 index 00000000..dd606835 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATEPreferenceFragmentCompat.kt @@ -0,0 +1,52 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.annotation.SuppressLint +import androidx.fragment.app.DialogFragment +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat + +import code.name.monkey.appthemehelper.common.prefs.supportv7.dialogs.ATEEditTextPreferenceDialogFragmentCompat +import code.name.monkey.appthemehelper.common.prefs.supportv7.dialogs.ATEListPreferenceDialogFragmentCompat +import code.name.monkey.appthemehelper.common.prefs.supportv7.dialogs.ATEPreferenceDialogFragment + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +@SuppressLint("RestrictedApi") +abstract class ATEPreferenceFragmentCompat : PreferenceFragmentCompat() { + + override fun onDisplayPreferenceDialog(preference: Preference) { + if (this.callbackFragment is PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback) { + (this.callbackFragment as PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback).onPreferenceDisplayDialog(this, preference) + return + } + + if (this.activity is PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback) { + (this.activity as PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback).onPreferenceDisplayDialog(this, preference) + return + } + + if (this.fragmentManager!!.findFragmentByTag("android.support.v7.preference.PreferenceFragment.DIALOG") == null) { + val dialogFragment = onCreatePreferenceDialog(preference) + + if (dialogFragment != null) { + dialogFragment.setTargetFragment(this, 0) + dialogFragment.show(this.fragmentManager!!, "android.support.v7.preference.PreferenceFragment.DIALOG") + return + } + } + + super.onDisplayPreferenceDialog(preference) + } + + open fun onCreatePreferenceDialog(preference: Preference): DialogFragment? { + if (preference is ATEEditTextPreference) { + return ATEEditTextPreferenceDialogFragmentCompat.newInstance(preference.getKey()) + } else if (preference is ATEListPreference) { + return ATEListPreferenceDialogFragmentCompat.newInstance(preference.getKey()) + } else if (preference is ATEDialogPreference) { + return ATEPreferenceDialogFragment.newInstance(preference.getKey()) + } + return null + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESeekBarPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESeekBarPreference.kt new file mode 100644 index 00000000..ff0a1fea --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESeekBarPreference.kt @@ -0,0 +1,8 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.content.Context +import android.util.AttributeSet + +import androidx.preference.SeekBarPreference + +class ATESeekBarPreference(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : SeekBarPreference(context, attrs, defStyleAttr, defStyleRes) diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESwitchPreference.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESwitchPreference.kt new file mode 100755 index 00000000..2ee1a45e --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/ATESwitchPreference.kt @@ -0,0 +1,37 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7 + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import androidx.preference.CheckBoxPreference +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.R + +/** + * @author Aidan Follestad (afollestad) + */ +class ATESwitchPreference : CheckBoxPreference { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + layoutResource = R.layout.ate_preference_custom_support + widgetLayoutResource = R.layout.ate_preference_switch_support + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEEditTextPreferenceDialogFragmentCompat.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEEditTextPreferenceDialogFragmentCompat.kt new file mode 100755 index 00000000..52fdf75c --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEEditTextPreferenceDialogFragmentCompat.kt @@ -0,0 +1,50 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7.dialogs + +import android.os.Bundle +import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEEditTextPreference +import com.afollestad.materialdialogs.MaterialDialog + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +class ATEEditTextPreferenceDialogFragmentCompat : ATEPreferenceDialogFragment(), MaterialDialog.InputCallback { + + private var input: CharSequence? = null + + private val editTextPreference: ATEEditTextPreference + get() = preference as ATEEditTextPreference + + override fun onPrepareDialogBuilder(builder: MaterialDialog.Builder) { + super.onPrepareDialogBuilder(builder) + builder.input("", editTextPreference.text, this) + } + + override fun needInputMethod(): Boolean { + return true + } + + override fun onDialogClosed(positiveResult: Boolean) { + if (positiveResult) { + val value = input!!.toString() + if (this.editTextPreference.callChangeListener(value)) { + this.editTextPreference.text = value + } + } + + } + + override fun onInput(dialog: MaterialDialog, input: CharSequence) { + this.input = input + } + + companion object { + + fun newInstance(key: String): ATEEditTextPreferenceDialogFragmentCompat { + val fragment = ATEEditTextPreferenceDialogFragmentCompat() + val b = Bundle(1) + b.putString(ATEPreferenceDialogFragment.ARG_KEY, key) + fragment.arguments = b + return fragment + } + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEListPreferenceDialogFragmentCompat.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEListPreferenceDialogFragmentCompat.kt new file mode 100755 index 00000000..6e9bc059 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEListPreferenceDialogFragmentCompat.kt @@ -0,0 +1,73 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7.dialogs + +import android.os.Bundle +import androidx.preference.ListPreference +import android.view.View + +import com.afollestad.materialdialogs.DialogAction +import com.afollestad.materialdialogs.MaterialDialog +import code.name.monkey.appthemehelper.common.prefs.supportv7.ATEListPreference + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +class ATEListPreferenceDialogFragmentCompat : ATEPreferenceDialogFragment(), MaterialDialog.ListCallbackSingleChoice { + private var mClickedDialogEntryIndex: Int = 0 + + private val listPreference: ATEListPreference + get() = preference as ATEListPreference + + override fun onPrepareDialogBuilder(builder: MaterialDialog.Builder) { + super.onPrepareDialogBuilder(builder) + + val preference = listPreference + + if (preference.entries == null || preference.entryValues == null) { + throw IllegalStateException( + "ListPreference requires an entries array and an entryValues array.") + } + + mClickedDialogEntryIndex = preference.findIndexOfValue(preference.value) + builder.items(*preference.entries) + .alwaysCallSingleChoiceCallback() + .itemsCallbackSingleChoice(mClickedDialogEntryIndex, this) + + /* + * The typical interaction for list-based dialogs is to have + * click-on-an-item dismiss the dialog instead of the user having to + * press 'Ok'. + */ + builder.positiveText("") + builder.negativeText("") + builder.neutralText("") + } + + override fun onDialogClosed(positiveResult: Boolean) { + val preference = listPreference + if (positiveResult && mClickedDialogEntryIndex >= 0 && + preference.entryValues != null) { + val value = preference.entryValues[mClickedDialogEntryIndex].toString() + if (preference.callChangeListener(value)) { + preference.value = value + } + } + } + + override fun onSelection(dialog: MaterialDialog, itemView: View, which: Int, text: CharSequence): Boolean { + mClickedDialogEntryIndex = which + onClick(dialog, DialogAction.POSITIVE) + dismiss() + return true + } + + companion object { + + fun newInstance(key: String): ATEListPreferenceDialogFragmentCompat { + val fragment = ATEListPreferenceDialogFragmentCompat() + val b = Bundle(1) + b.putString(ATEPreferenceDialogFragment.ARG_KEY, key) + fragment.arguments = b + return fragment + } + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEPreferenceDialogFragment.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEPreferenceDialogFragment.kt new file mode 100755 index 00000000..d3d3b9fb --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/prefs/supportv7/dialogs/ATEPreferenceDialogFragment.kt @@ -0,0 +1,85 @@ +package code.name.monkey.appthemehelper.common.prefs.supportv7.dialogs + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import androidx.preference.DialogPreference +import com.afollestad.materialdialogs.DialogAction +import com.afollestad.materialdialogs.MaterialDialog + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +open class ATEPreferenceDialogFragment : DialogFragment(), MaterialDialog.SingleButtonCallback { + private var mWhichButtonClicked: DialogAction? = null + var preference: DialogPreference? = null + private set + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val rawFragment = this.targetFragment + if (rawFragment !is DialogPreference.TargetFragment) { + throw IllegalStateException("Target fragment must implement TargetFragment interface") + } else { + val fragment = rawFragment as DialogPreference.TargetFragment? + val key = this.arguments!!.getString(ARG_KEY) + this.preference = fragment!!.findPreference(key) as DialogPreference + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val context = this.activity + val builder = MaterialDialog.Builder(context!!) + .title(this.preference!!.dialogTitle) + .icon(this.preference!!.dialogIcon) + .onAny(this) + .positiveText(this.preference!!.positiveButtonText) + .negativeText(this.preference!!.negativeButtonText) + + builder.content(this.preference!!.dialogMessage) + this.onPrepareDialogBuilder(builder) + val dialog = builder.build() + if (this.needInputMethod()) { + this.requestInputMethod(dialog) + } + + return dialog + } + + protected open fun onPrepareDialogBuilder(builder: MaterialDialog.Builder) {} + + protected open fun needInputMethod(): Boolean { + return false + } + + private fun requestInputMethod(dialog: Dialog) { + val window = dialog.window + window!!.setSoftInputMode(5) + } + + override fun onClick(dialog: MaterialDialog, which: DialogAction) { + mWhichButtonClicked = which + } + + override fun onDismiss(dialog: DialogInterface?) { + super.onDismiss(dialog) + onDialogClosed(mWhichButtonClicked == DialogAction.POSITIVE) + } + + open fun onDialogClosed(positiveResult: Boolean) { + + } + + companion object { + const val ARG_KEY = "key" + + fun newInstance(key: String): ATEPreferenceDialogFragment { + val fragment = ATEPreferenceDialogFragment() + val b = Bundle(1) + b.putString(ARG_KEY, key) + fragment.arguments = b + return fragment + } + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATECheckBox.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATECheckBox.kt new file mode 100755 index 00000000..33b07cb4 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATECheckBox.kt @@ -0,0 +1,31 @@ +package code.name.monkey.appthemehelper.common.views + +import android.content.Context +import androidx.appcompat.widget.AppCompatCheckBox +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.ThemeStore + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATECheckBox : AppCompatCheckBox { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + ATH.setTint(this, ThemeStore.accentColor(context)) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEEditText.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEEditText.kt new file mode 100755 index 00000000..106ca768 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEEditText.kt @@ -0,0 +1,32 @@ +package code.name.monkey.appthemehelper.common.views + +import android.content.Context +import androidx.appcompat.widget.AppCompatEditText +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.ThemeStore + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEEditText : AppCompatEditText { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + ATH.setTint(this, ThemeStore.accentColor(context)) + setTextColor(ThemeStore.textColorPrimary(context)) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEPrimaryTextView.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEPrimaryTextView.kt new file mode 100755 index 00000000..5508d2ff --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEPrimaryTextView.kt @@ -0,0 +1,32 @@ +package code.name.monkey.appthemehelper.common.views + +import android.content.Context +import androidx.appcompat.widget.AppCompatTextView + +import android.text.Layout +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.ThemeStore + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEPrimaryTextView : AppCompatTextView { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + setTextColor(ThemeStore.textColorPrimary(context)) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEProgressBar.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEProgressBar.kt new file mode 100755 index 00000000..76f9fa9d --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEProgressBar.kt @@ -0,0 +1,38 @@ +package code.name.monkey.appthemehelper.common.views + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.widget.ProgressBar + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.ThemeStore + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEProgressBar : ProgressBar { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + ATH.setTint(this, ThemeStore.accentColor(context)) + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATERadioButton.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATERadioButton.kt new file mode 100755 index 00000000..787247f6 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATERadioButton.kt @@ -0,0 +1,37 @@ +package code.name.monkey.appthemehelper.common.views + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.widget.RadioButton + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.ThemeStore + +/** + * @author Aidan Follestad (afollestad) + */ +class ATERadioButton : RadioButton { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + ATH.setTint(this, ThemeStore.accentColor(context)) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESecondaryTextView.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESecondaryTextView.kt new file mode 100755 index 00000000..e2f08e55 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESecondaryTextView.kt @@ -0,0 +1,29 @@ +package code.name.monkey.appthemehelper.common.views + +import android.content.Context +import androidx.appcompat.widget.AppCompatTextView +import android.util.AttributeSet + +import code.name.monkey.appthemehelper.ThemeStore + +/** + * @author Aidan Follestad (afollestad) + */ +class ATESecondaryTextView : AppCompatTextView { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + setTextColor(ThemeStore.textColorSecondary(context)) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESeekBar.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESeekBar.kt new file mode 100755 index 00000000..d0ef3030 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESeekBar.kt @@ -0,0 +1,38 @@ +package code.name.monkey.appthemehelper.common.views + +import android.annotation.TargetApi +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.widget.SeekBar + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.ThemeStore + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATESeekBar : SeekBar { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + ATH.setTint(this, ThemeStore.accentColor(context)) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEStockSwitch.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEStockSwitch.kt new file mode 100755 index 00000000..15758822 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATEStockSwitch.kt @@ -0,0 +1,36 @@ +package code.name.monkey.appthemehelper.common.views + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.Switch + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.ThemeStore + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATEStockSwitch : Switch { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + ATH.setTint(this, ThemeStore.accentColor(context)) + } + + override fun isShown(): Boolean { + return parent != null && visibility == View.VISIBLE + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESwitch.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESwitch.kt new file mode 100755 index 00000000..64ba8d0a --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/common/views/ATESwitch.kt @@ -0,0 +1,36 @@ +package code.name.monkey.appthemehelper.common.views + +import android.content.Context +import androidx.appcompat.widget.SwitchCompat +import android.util.AttributeSet +import android.view.View + +import code.name.monkey.appthemehelper.ATH +import code.name.monkey.appthemehelper.ThemeStore + + +/** + * @author Aidan Follestad (afollestad) + */ +class ATESwitch : SwitchCompat { + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + ATH.setTint(this, ThemeStore.accentColor(context)) + } + + override fun isShown(): Boolean { + return parent != null && visibility == View.VISIBLE + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ATHUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ATHUtil.kt new file mode 100755 index 00000000..66d174c6 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ATHUtil.kt @@ -0,0 +1,43 @@ +package code.name.monkey.appthemehelper.util + +import android.content.Context +import android.content.res.TypedArray +import androidx.annotation.AttrRes + +/** + * @author Aidan Follestad (afollestad) + */ +object ATHUtil { + + fun isWindowBackgroundDark(context: Context): Boolean { + return !ColorUtil.isColorLight(ATHUtil.resolveColor(context, android.R.attr.windowBackground)) + } + + @JvmOverloads + fun resolveColor(context: Context, @AttrRes attr: Int, fallback: Int = 0): Int { + val a = context.theme.obtainStyledAttributes(intArrayOf(attr)) + try { + return a.getColor(0, fallback) + } finally { + a.recycle() + } + } + + fun isInClassPath(clsName: String): Boolean { + try { + return inClassPath(clsName) != null + } catch (t: Throwable) { + return false + } + + } + + fun inClassPath(clsName: String): Class<*> { + try { + return Class.forName(clsName) + } catch (t: Throwable) { + throw IllegalStateException(String.format("%s is not in your class path! You must include the associated library.", clsName)) + } + + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ColorUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ColorUtil.kt new file mode 100755 index 00000000..94a63162 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ColorUtil.kt @@ -0,0 +1,74 @@ +package code.name.monkey.appthemehelper.util + +import android.graphics.Color +import androidx.annotation.ColorInt +import androidx.annotation.FloatRange + + +object ColorUtil { + + fun stripAlpha(@ColorInt color: Int): Int { + return -0x1000000 or color + } + + @ColorInt + fun shiftColor(@ColorInt color: Int, @FloatRange(from = 0.0, to = 2.0) by: Float): Int { + if (by == 1f) return color + val alpha = Color.alpha(color) + val hsv = FloatArray(3) + Color.colorToHSV(color, hsv) + hsv[2] *= by // value component + return (alpha shl 24) + (0x00ffffff and Color.HSVToColor(hsv)) + } + + @ColorInt + fun darkenColor(@ColorInt color: Int): Int { + return shiftColor(color, 0.9f) + } + + @ColorInt + fun lightenColor(@ColorInt color: Int): Int { + return shiftColor(color, 1.1f) + } + + fun isColorLight(@ColorInt color: Int): Boolean { + val darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255 + return darkness < 0.4 + } + + @ColorInt + fun invertColor(@ColorInt color: Int): Int { + val r = 255 - Color.red(color) + val g = 255 - Color.green(color) + val b = 255 - Color.blue(color) + return Color.argb(Color.alpha(color), r, g, b) + } + + @ColorInt + fun adjustAlpha(@ColorInt color: Int, @FloatRange(from = 0.0, to = 1.0) factor: Float): Int { + val alpha = Math.round(Color.alpha(color) * factor) + val red = Color.red(color) + val green = Color.green(color) + val blue = Color.blue(color) + return Color.argb(alpha, red, green, blue) + } + + @ColorInt + fun withAlpha(@ColorInt baseColor: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float): Int { + val a = Math.min(255, Math.max(0, (alpha * 255).toInt())) shl 24 + val rgb = 0x00ffffff and baseColor + return a + rgb + } + + /** + * Taken from CollapsingToolbarLayout's CollapsingTextHelper class. + */ + fun blendColors(color1: Int, color2: Int, @FloatRange(from = 0.0, to = 1.0) ratio: Float): Int { + val inverseRatio = 1f - ratio + val a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio + val r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio + val g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio + val b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio + return Color.argb(a.toInt(), r.toInt(), g.toInt(), b.toInt()) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/DrawableUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/DrawableUtil.kt new file mode 100755 index 00000000..1997533e --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/DrawableUtil.kt @@ -0,0 +1,23 @@ +package code.name.monkey.appthemehelper.util + +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.TransitionDrawable +import androidx.annotation.ColorInt + + +object DrawableUtil { + + fun createTransitionDrawable(@ColorInt startColor: Int, @ColorInt endColor: Int): TransitionDrawable { + return createTransitionDrawable(ColorDrawable(startColor), ColorDrawable(endColor)) + } + + fun createTransitionDrawable(start: Drawable, end: Drawable): TransitionDrawable { + val drawables = arrayOfNulls(2) + + drawables[0] = start + drawables[1] = end + + return TransitionDrawable(drawables) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/EdgeGlowUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/EdgeGlowUtil.kt new file mode 100755 index 00000000..b5092ec5 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/EdgeGlowUtil.kt @@ -0,0 +1,342 @@ +package code.name.monkey.appthemehelper.util + +import android.annotation.TargetApi +import android.graphics.PorterDuff +import android.graphics.drawable.Drawable +import android.os.Build +import androidx.annotation.ColorInt +import androidx.viewpager.widget.ViewPager +import androidx.core.widget.EdgeEffectCompat +import androidx.core.widget.NestedScrollView +import androidx.recyclerview.widget.RecyclerView +import android.widget.AbsListView +import android.widget.EdgeEffect +import android.widget.ScrollView + +import code.name.monkey.appthemehelper.BuildConfig + +import java.lang.reflect.Field + +class EdgeGlowUtil protected constructor() { + companion object { + + // Invalidation methods + + private var EDGE_GLOW_FIELD_EDGE: Field? = null + private var EDGE_GLOW_FIELD_GLOW: Field? = null + private var EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT: Field? = null + + private fun invalidateEdgeEffectFields() { + if (EDGE_GLOW_FIELD_EDGE != null && EDGE_GLOW_FIELD_GLOW != null && + EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT != null) { + EDGE_GLOW_FIELD_EDGE!!.isAccessible = true + EDGE_GLOW_FIELD_GLOW!!.isAccessible = true + EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT!!.isAccessible = true + return + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + var edge: Field? = null + var glow: Field? = null + var cls: Class<*>? = null + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + try { + cls = Class.forName("android.widget.EdgeGlow") + } catch (e: ClassNotFoundException) { + if (BuildConfig.DEBUG) e.printStackTrace() + } + + } else { + cls = EdgeEffect::class.java + } + if (cls != null) { + for (f in cls.declaredFields) { + when (f.name) { + "mEdge" -> { + f.isAccessible = true + edge = f + } + "mGlow" -> { + f.isAccessible = true + glow = f + } + } + } + } + EDGE_GLOW_FIELD_EDGE = edge + EDGE_GLOW_FIELD_GLOW = glow + } else { + EDGE_GLOW_FIELD_EDGE = null + EDGE_GLOW_FIELD_GLOW = null + } + + var efc: Field? = null + try { + efc = EdgeEffectCompat::class.java.getDeclaredField("mEdgeEffect") + } catch (e: NoSuchFieldException) { + if (BuildConfig.DEBUG) e.printStackTrace() + } + + EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT = efc + } + + private var SCROLL_VIEW_FIELD_EDGE_GLOW_TOP: Field? = null + private var SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM: Field? = null + + private fun invalidateScrollViewFields() { + if (SCROLL_VIEW_FIELD_EDGE_GLOW_TOP != null && SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM != null) { + SCROLL_VIEW_FIELD_EDGE_GLOW_TOP!!.isAccessible = true + SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM!!.isAccessible = true + return + } + val cls = ScrollView::class.java + for (f in cls.declaredFields) { + when (f.name) { + "mEdgeGlowTop" -> { + f.isAccessible = true + SCROLL_VIEW_FIELD_EDGE_GLOW_TOP = f + } + "mEdgeGlowBottom" -> { + f.isAccessible = true + SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM = f + } + } + } + } + + private var NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP: Field? = null + private var NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM: Field? = null + + private fun invalidateNestedScrollViewFields() { + if (NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP != null && NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM != null) { + NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP!!.isAccessible = true + NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM!!.isAccessible = true + return + } + val cls = ATHUtil.inClassPath("android.support.v4.widget.NestedScrollView") + for (f in cls.declaredFields) { + when (f.name) { + "mEdgeGlowTop" -> { + f.isAccessible = true + NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP = f + } + "mEdgeGlowBottom" -> { + f.isAccessible = true + NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM = f + } + } + } + } + + private var LIST_VIEW_FIELD_EDGE_GLOW_TOP: Field? = null + private var LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM: Field? = null + + private fun invalidateListViewFields() { + if (LIST_VIEW_FIELD_EDGE_GLOW_TOP != null && LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM != null) { + LIST_VIEW_FIELD_EDGE_GLOW_TOP!!.isAccessible = true + LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM!!.isAccessible = true + return + } + val cls = AbsListView::class.java + for (f in cls.declaredFields) { + when (f.name) { + "mEdgeGlowTop" -> { + f.isAccessible = true + LIST_VIEW_FIELD_EDGE_GLOW_TOP = f + } + "mEdgeGlowBottom" -> { + f.isAccessible = true + LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM = f + } + } + } + } + + private var RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP: Field? = null + private var RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT: Field? = null + private var RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT: Field? = null + private var RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM: Field? = null + + private fun invalidateRecyclerViewFields() { + if (RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP != null && RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT != null && + RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT != null && RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM != null) { + RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP!!.isAccessible = true + RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT!!.isAccessible = true + RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT!!.isAccessible = true + RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM!!.isAccessible = true + return + } + val cls = ATHUtil.inClassPath("android.support.v7.widget.RecyclerView") + for (f in cls.declaredFields) { + when (f.name) { + "mTopGlow" -> { + f.isAccessible = true + RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP = f + } + "mBottomGlow" -> { + f.isAccessible = true + RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM = f + } + "mLeftGlow" -> { + f.isAccessible = true + RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT = f + } + "mRightGlow" -> { + f.isAccessible = true + RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT = f + } + } + } + } + + private var VIEW_PAGER_FIELD_EDGE_GLOW_LEFT: Field? = null + private var VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT: Field? = null + + private fun invalidateViewPagerFields() { + if (VIEW_PAGER_FIELD_EDGE_GLOW_LEFT != null && VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT != null) { + VIEW_PAGER_FIELD_EDGE_GLOW_LEFT!!.isAccessible = true + VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT!!.isAccessible = true + return + } + val cls = ATHUtil.inClassPath("android.support.v4.view.ViewPager") + for (f in cls.declaredFields) { + when (f.name) { + "mLeftEdge" -> { + f.isAccessible = true + VIEW_PAGER_FIELD_EDGE_GLOW_LEFT = f + } + "mRightEdge" -> { + f.isAccessible = true + VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT = f + } + } + } + } + + // Setter methods + + fun setEdgeGlowColor(scrollView: ScrollView, @ColorInt color: Int) { + invalidateScrollViewFields() + try { + var ee: Any + ee = SCROLL_VIEW_FIELD_EDGE_GLOW_TOP!!.get(scrollView) + setEffectColor(ee, color) + ee = SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM!!.get(scrollView) + setEffectColor(ee, color) + } catch (ex: Exception) { + if (BuildConfig.DEBUG) ex.printStackTrace() + } + + } + + fun setEdgeGlowColor(scrollView: NestedScrollView, @ColorInt color: Int) { + invalidateNestedScrollViewFields() + try { + var ee: Any + ee = NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_TOP!!.get(scrollView) + setEffectColor(ee, color) + ee = NESTED_SCROLL_VIEW_FIELD_EDGE_GLOW_BOTTOM!!.get(scrollView) + setEffectColor(ee, color) + } catch (ex: Exception) { + if (BuildConfig.DEBUG) ex.printStackTrace() + } + + } + + fun setEdgeGlowColor(listView: AbsListView, @ColorInt color: Int) { + invalidateListViewFields() + try { + var ee: Any + ee = LIST_VIEW_FIELD_EDGE_GLOW_TOP!!.get(listView) + setEffectColor(ee, color) + ee = LIST_VIEW_FIELD_EDGE_GLOW_BOTTOM!!.get(listView) + setEffectColor(ee, color) + } catch (ex: Exception) { + if (BuildConfig.DEBUG) ex.printStackTrace() + } + + } + + fun setEdgeGlowColor(scrollView: RecyclerView, @ColorInt color: Int, scrollListener: RecyclerView.OnScrollListener?) { + var scrollListener = scrollListener + invalidateRecyclerViewFields() + invalidateRecyclerViewFields() + if (scrollListener == null) { + scrollListener = object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + EdgeGlowUtil.setEdgeGlowColor(recyclerView, color, this) + } + } + scrollView.addOnScrollListener(scrollListener) + } + try { + var ee: Any + ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_TOP!!.get(scrollView) + setEffectColor(ee, color) + ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_BOTTOM!!.get(scrollView) + setEffectColor(ee, color) + ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_LEFT!!.get(scrollView) + setEffectColor(ee, color) + ee = RECYCLER_VIEW_FIELD_EDGE_GLOW_RIGHT!!.get(scrollView) + setEffectColor(ee, color) + } catch (ex: Exception) { + if (BuildConfig.DEBUG) ex.printStackTrace() + } + + } + + fun setEdgeGlowColor(pager: ViewPager, @ColorInt color: Int) { + invalidateViewPagerFields() + try { + var ee: Any + ee = VIEW_PAGER_FIELD_EDGE_GLOW_LEFT!!.get(pager) + setEffectColor(ee, color) + ee = VIEW_PAGER_FIELD_EDGE_GLOW_RIGHT!!.get(pager) + setEffectColor(ee, color) + } catch (ex: Exception) { + if (BuildConfig.DEBUG) ex.printStackTrace() + } + + } + + // Utilities + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private fun setEffectColor(edgeEffect: Any?, @ColorInt color: Int) { + var edgeEffect = edgeEffect + invalidateEdgeEffectFields() + if (edgeEffect is EdgeEffectCompat) { + // EdgeEffectCompat + try { + EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT!!.isAccessible = true + edgeEffect = EDGE_EFFECT_COMPAT_FIELD_EDGE_EFFECT!!.get(edgeEffect) + } catch (e: IllegalAccessException) { + e.printStackTrace() + return + } + + } + if (edgeEffect == null) + return + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // EdgeGlow + try { + EDGE_GLOW_FIELD_EDGE!!.isAccessible = true + val mEdge = EDGE_GLOW_FIELD_EDGE!!.get(edgeEffect) as Drawable + EDGE_GLOW_FIELD_GLOW!!.isAccessible = true + val mGlow = EDGE_GLOW_FIELD_GLOW!!.get(edgeEffect) as Drawable + mEdge.setColorFilter(color, PorterDuff.Mode.SRC_IN) + mGlow.setColorFilter(color, PorterDuff.Mode.SRC_IN) + mEdge.callback = null // free up any references + mGlow.callback = null // free up any references + } catch (ex: Exception) { + ex.printStackTrace() + } + + } else { + // EdgeEffect + (edgeEffect as EdgeEffect).color = color + } + } + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialDialogsUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialDialogsUtil.kt new file mode 100755 index 00000000..4235b896 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialDialogsUtil.kt @@ -0,0 +1,24 @@ +package code.name.monkey.appthemehelper.util + +import android.content.Context +import android.content.res.ColorStateList + +import com.afollestad.materialdialogs.internal.ThemeSingleton +import code.name.monkey.appthemehelper.ThemeStore + + +object MaterialDialogsUtil { + + fun updateMaterialDialogsThemeSingleton(context: Context) { + val md = ThemeSingleton.get() + md.titleColor = ThemeStore.textColorPrimary(context) + md.contentColor = ThemeStore.textColorSecondary(context) + md.itemColor = md.titleColor + md.widgetColor = ThemeStore.accentColor(context) + md.linkColor = ColorStateList.valueOf(md.widgetColor) + md.positiveColor = ColorStateList.valueOf(md.widgetColor) + md.neutralColor = ColorStateList.valueOf(md.widgetColor) + md.negativeColor = ColorStateList.valueOf(md.widgetColor) + md.darkTheme = ATHUtil.isWindowBackgroundDark(context) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialUtil.kt new file mode 100644 index 00000000..14815d95 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialUtil.kt @@ -0,0 +1,57 @@ +package code.name.monkey.appthemehelper.util + +import android.content.Context +import android.content.res.ColorStateList + +import com.google.android.material.button.MaterialButton +import com.google.android.material.textfield.TextInputLayout + +import code.name.monkey.appthemehelper.ThemeStore + +object MaterialUtil { + + fun setTint(button: MaterialButton) { + setTint(button, ThemeStore.accentColor(button.context)) + } + + private fun setTint(button: MaterialButton, accentColor: Int) { + val context = button.context + val textColor = ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(accentColor))) + button.setTextColor(textColor) + } + + @JvmOverloads + fun setTint(button: MaterialButton, background: Boolean, color: Int = ThemeStore.accentColor(button.context)) { + //button.setPadding(48, 48, 48, 48); + button.isAllCaps = false + val context = button.context + val colorState = ColorStateList.valueOf(color) + val textColor = ColorStateList.valueOf(MaterialValueHelper.getPrimaryTextColor(context, ColorUtil.isColorLight(color))) + + + if (background) { + button.backgroundTintList = colorState + button.setTextColor(textColor) + button.iconTint = textColor + } else { + button.strokeColor = colorState + button.setTextColor(colorState) + button.iconTint = colorState + } + + } + + fun setTint(textInputLayout: TextInputLayout, background: Boolean) { + val context = textInputLayout.context + val accentColor = ThemeStore.accentColor(context) + val colorState = ColorStateList.valueOf(accentColor) + + if (background) { + textInputLayout.backgroundTintList = colorState + textInputLayout.defaultHintTextColor = colorState + } else { + textInputLayout.boxStrokeColor = accentColor + textInputLayout.defaultHintTextColor = colorState + } + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialValueHelper.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialValueHelper.kt new file mode 100755 index 00000000..3e09cb5e --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/MaterialValueHelper.kt @@ -0,0 +1,44 @@ +package code.name.monkey.appthemehelper.util + +import android.annotation.SuppressLint +import android.content.Context +import androidx.annotation.ColorInt +import androidx.core.content.ContextCompat + +import code.name.monkey.appthemehelper.R + + +object MaterialValueHelper { + + @SuppressLint("PrivateResource") + @ColorInt + fun getPrimaryTextColor(context: Context, dark: Boolean): Int { + return if (dark) { + ContextCompat.getColor(context, R.color.primary_text_default_material_light) + } else ContextCompat.getColor(context, R.color.primary_text_default_material_dark) + } + + @SuppressLint("PrivateResource") + @ColorInt + fun getSecondaryTextColor(context: Context, dark: Boolean): Int { + return if (dark) { + ContextCompat.getColor(context, R.color.secondary_text_default_material_light) + } else ContextCompat.getColor(context, R.color.secondary_text_default_material_dark) + } + + @SuppressLint("PrivateResource") + @ColorInt + fun getPrimaryDisabledTextColor(context: Context, dark: Boolean): Int { + return if (dark) { + ContextCompat.getColor(context, R.color.primary_text_disabled_material_light) + } else ContextCompat.getColor(context, R.color.primary_text_disabled_material_dark) + } + + @SuppressLint("PrivateResource") + @ColorInt + fun getSecondaryDisabledTextColor(context: Context, dark: Boolean): Int { + return if (dark) { + ContextCompat.getColor(context, R.color.secondary_text_disabled_material_light) + } else ContextCompat.getColor(context, R.color.secondary_text_disabled_material_dark) + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/NavigationViewUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/NavigationViewUtil.kt new file mode 100644 index 00000000..05782028 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/NavigationViewUtil.kt @@ -0,0 +1,41 @@ +package code.name.monkey.appthemehelper.util + +import android.content.res.ColorStateList +import androidx.annotation.ColorInt +import code.name.monkey.appthemehelper.ThemeStore +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.google.android.material.navigation.NavigationView + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +object NavigationViewUtil { + + fun setItemIconColors(navigationView: NavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) { + val iconSl = ColorStateList(arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)), intArrayOf(normalColor, selectedColor)) + navigationView.itemIconTintList = iconSl + val drawable = navigationView.itemBackground + navigationView.itemBackground = TintHelper.createTintedDrawable(drawable, ColorUtil.withAlpha(ThemeStore.accentColor(navigationView.context), 0.2f)) + } + + fun setItemTextColors(navigationView: NavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) { + val textSl = ColorStateList( + arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)), + intArrayOf(normalColor, selectedColor)) + navigationView.itemTextColor = textSl + } + + fun setItemIconColors(bottomNavigationView: BottomNavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) { + val iconSl = ColorStateList( + arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)), + intArrayOf(normalColor, selectedColor)) + bottomNavigationView.itemIconTintList = iconSl + } + + fun setItemTextColors(bottomNavigationView: BottomNavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) { + val textSl = ColorStateList( + arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)), + intArrayOf(normalColor, selectedColor)) + bottomNavigationView.itemTextColor = textSl + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TabLayoutUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TabLayoutUtil.kt new file mode 100755 index 00000000..06cbaba8 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TabLayoutUtil.kt @@ -0,0 +1,23 @@ +package code.name.monkey.appthemehelper.util + +import android.content.res.ColorStateList +import androidx.annotation.ColorInt +import com.google.android.material.tabs.TabLayout + + +object TabLayoutUtil { + + fun setTabIconColors(tabLayout: TabLayout?, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) { + if (tabLayout == null) + return + + val sl = ColorStateList(arrayOf(intArrayOf(-android.R.attr.state_selected), intArrayOf(android.R.attr.state_selected)), + intArrayOf(normalColor, selectedColor)) + for (i in 0 until tabLayout.tabCount) { + val tab = tabLayout.getTabAt(i) + if (tab != null && tab.icon != null) { + tab.icon = TintHelper.createTintedDrawable(tab.icon, sl) + } + } + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TextInputLayoutUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TextInputLayoutUtil.kt new file mode 100755 index 00000000..09d79070 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TextInputLayoutUtil.kt @@ -0,0 +1,35 @@ +package code.name.monkey.appthemehelper.util + +import android.content.res.ColorStateList +import androidx.annotation.ColorInt +import com.google.android.material.textfield.TextInputLayout + +import java.lang.reflect.Field + +/** + * @author Aidan Follestad (afollestad) + */ +object TextInputLayoutUtil { + + fun setHint(view: TextInputLayout, @ColorInt hintColor: Int) { + try { + val mDefaultTextColorField = TextInputLayout::class.java.getDeclaredField("mDefaultTextColor") + mDefaultTextColorField.isAccessible = true + mDefaultTextColorField.set(view, ColorStateList.valueOf(hintColor)) + } catch (t: Throwable) { + throw RuntimeException("Failed to set TextInputLayout hint (collapsed) color: " + t.localizedMessage, t) + } + + } + + fun setAccent(view: TextInputLayout, @ColorInt accentColor: Int) { + try { + val mFocusedTextColorField = TextInputLayout::class.java.getDeclaredField("mFocusedTextColor") + mFocusedTextColorField.isAccessible = true + mFocusedTextColorField.set(view, ColorStateList.valueOf(accentColor)) + } catch (t: Throwable) { + throw RuntimeException("Failed to set TextInputLayout accent (expanded) color: " + t.localizedMessage, t) + } + + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TypefaceHelper.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TypefaceHelper.kt new file mode 100755 index 00000000..d3e4f0e7 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/TypefaceHelper.kt @@ -0,0 +1,50 @@ +package code.name.monkey.appthemehelper.util + +/** + * @author Aidan Follestad (afollestad) + */ + +import android.content.Context +import android.graphics.Typeface +import androidx.collection.SimpleArrayMap + +/* + Each call to Typeface.createFromAsset will load a new instance of the typeface into memory, + and this memory is not consistently get garbage collected + http://code.google.com/p/android/issues/detail?id=9904 + (It states released but even on Lollipop you can see the typefaces accumulate even after + multiple GC passes) + + You can detect this by running: + adb shell dumpsys meminfo com.your.packagenage + + You will see output like: + + Asset Allocations + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Regular.ttf: 123K + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K + +*/ +object TypefaceHelper { + + private val cache = SimpleArrayMap() + + operator fun get(c: Context, name: String): Typeface? { + synchronized(cache) { + if (!cache.containsKey(name)) { + try { + val t = Typeface.createFromAsset(c.assets, name) + cache.put(name, t) + return t + } catch (e: RuntimeException) { + return null + } + + } + return cache.get(name) + } + } +} \ No newline at end of file diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt new file mode 100644 index 00000000..bc79d20c --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/VersionUtils.kt @@ -0,0 +1,59 @@ +package code.name.monkey.appthemehelper.util + +import android.os.Build + +/** + * @author Hemanth S (h4h13). + */ + +object VersionUtils { + + /** + * @return true if device is running API >= 19 + */ + fun hasKitKat(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT + } + + /** + * @return true if device is running API >= 20 + */ + fun hasAndroidLPreview(): Boolean { + return Build.VERSION.SDK_INT >= 20 + } + + /** + * @return true if device is running API >= 21 + */ + fun hasLollipop(): Boolean { + return Build.VERSION.SDK_INT >= 21 + } + + /** + * @return true if device is running API >= 23 + */ + fun hasMarshmallow(): Boolean { + return Build.VERSION.SDK_INT >= 23 + } + + /** + * @return true if device is running API >= 24 + */ + fun hasNougat(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + } + + /** + * @return true if device is running API >= 24 + */ + fun hasNougatMR(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 + } + + /** + * @return true if device is running API >= 26 + */ + fun hasOreo(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + } +} diff --git a/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ViewUtil.kt b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ViewUtil.kt new file mode 100755 index 00000000..d4f8c902 --- /dev/null +++ b/appthemehelper/src/main/java/code/name/monkey/appthemehelper/util/ViewUtil.kt @@ -0,0 +1,47 @@ +package code.name.monkey.appthemehelper.util + +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.TransitionDrawable +import android.os.Build +import androidx.annotation.ColorInt +import android.view.View +import android.view.ViewTreeObserver + + +object ViewUtil { + + fun removeOnGlobalLayoutListener(v: View, listener: ViewTreeObserver.OnGlobalLayoutListener) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + v.viewTreeObserver.removeGlobalOnLayoutListener(listener) + } else { + v.viewTreeObserver.removeOnGlobalLayoutListener(listener) + } + } + + fun setBackgroundCompat(view: View, drawable: Drawable?) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) + view.background = drawable + else + view.setBackgroundDrawable(drawable) + } + + fun setBackgroundTransition(view: View, newDrawable: Drawable): TransitionDrawable { + val transition = DrawableUtil.createTransitionDrawable(view.background, newDrawable) + setBackgroundCompat(view, transition) + return transition + } + + fun setBackgroundColorTransition(view: View, @ColorInt newColor: Int): TransitionDrawable { + val oldColor = view.background + + val start = oldColor ?: ColorDrawable(view.solidColor) + val end = ColorDrawable(newColor) + + val transition = DrawableUtil.createTransitionDrawable(start, end) + + setBackgroundCompat(view, transition) + + return transition + } +}