From b9f96f23d1faa6753be285b4b41877022d8f77d1 Mon Sep 17 00:00:00 2001 From: atbest Date: Sat, 25 May 2019 19:08:58 -0400 Subject: [PATCH 1/5] Updated replace rule activity --- .../app/ui/replacerule/ReplaceRuleActivity.kt | 27 +++++++++ .../app/ui/replacerule/ReplaceRuleAdapter.kt | 16 ++--- app/src/main/res/layout/item_relace_rule.xml | 59 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 4 files changed, 82 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt index 464c5746b..8f6077c96 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -13,6 +13,8 @@ import io.legado.app.data.entities.ReplaceRule import kotlinx.android.synthetic.main.activity_replace_rule.* import org.jetbrains.anko.doAsync import org.jetbrains.anko.toast +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.ItemTouchHelper class ReplaceRuleActivity : AppCompatActivity() { @@ -26,6 +28,7 @@ class ReplaceRuleActivity : AppCompatActivity() { rv_replace_rule.layoutManager = LinearLayoutManager(this) initRecyclerView() initDataObservers() + initSwipeToDelete() } private fun initRecyclerView() { @@ -69,4 +72,28 @@ class ReplaceRuleActivity : AppCompatActivity() { } } } + + private fun initSwipeToDelete() { + ItemTouchHelper(object : ItemTouchHelper.Callback() { + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + return ItemTouchHelper.Callback.makeMovementFlags(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return false + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + toast("You swiped the item!") + TODO() + // remove((viewHolder as TodoViewHolder).todo) + } + }).attachToRecyclerView(rv_replace_rule) + + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt index 717e2bfff..a767cee52 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt @@ -50,13 +50,15 @@ class ReplaceRuleAdapter(context: Context) : class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { fun bind(rule: ReplaceRule, listener: OnClickListener?, hideDivider: Boolean) = with(itemView) { - cb_enable.text = rule.name - cb_enable.isChecked = rule.isEnabled - divider.isGone = hideDivider - iv_delete.onClick { listener?.delete(rule) } - iv_edit.onClick { listener?.edit(rule) } - cb_enable.onClick { - rule.isEnabled = cb_enable.isChecked + tv_name.text = rule.name + swt_enabled.isChecked = rule.isEnabled + divider.isGone = hideDivider + iv_delete.isGone = true + iv_edit.isGone = true + // iv_delete.onClick { listener?.delete(rule) } + // iv_edit.onClick { listener?.edit(rule) } + swt_enabled.onClick { + rule.isEnabled = swt_enabled.isChecked listener?.update(rule) } } diff --git a/app/src/main/res/layout/item_relace_rule.xml b/app/src/main/res/layout/item_relace_rule.xml index 80295a529..c739121b9 100644 --- a/app/src/main/res/layout/item_relace_rule.xml +++ b/app/src/main/res/layout/item_relace_rule.xml @@ -2,25 +2,12 @@ - - + + + + + + + + + 删除 净化替换 暂无 + 启用 From dab348e601b6e546d5543984f04fc45c6ee82bea Mon Sep 17 00:00:00 2001 From: atbest Date: Sat, 25 May 2019 19:10:14 -0400 Subject: [PATCH 2/5] Extracted search and explore urls to separate table --- .../java/io/legado/app/base/BaseActivity.kt | 3 +- .../app/data/dao/ExploreSearchUrlDao.kt | 71 +++++++++++++++++++ .../io/legado/app/data/dao/ReplaceRuleDao.kt | 1 + .../legado/app/data/dao/SearchKeywordDao.kt | 32 +++++++++ .../app/data/entities/ExploreSearchUrl.kt | 29 ++++++++ .../legado/app/data/entities/SearchKeyword.kt | 17 +++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/io/legado/app/data/dao/ExploreSearchUrlDao.kt create mode 100644 app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt create mode 100644 app/src/main/java/io/legado/app/data/entities/ExploreSearchUrl.kt create mode 100644 app/src/main/java/io/legado/app/data/entities/SearchKeyword.kt diff --git a/app/src/main/java/io/legado/app/base/BaseActivity.kt b/app/src/main/java/io/legado/app/base/BaseActivity.kt index 8e23928f4..c85db5b5c 100644 --- a/app/src/main/java/io/legado/app/base/BaseActivity.kt +++ b/app/src/main/java/io/legado/app/base/BaseActivity.kt @@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.lifecycle.ViewModel +import com.google.android.material.bottomnavigation.BottomNavigationView abstract class BaseActivity : AppCompatActivity() { @@ -33,7 +34,7 @@ abstract class BaseActivity : AppCompatAct return true } } - return if (item == null) false else onCompatOptionsItemSelected(item) + return item != null && onCompatOptionsItemSelected(item) } open fun onCompatOptionsItemSelected(item: MenuItem): Boolean { diff --git a/app/src/main/java/io/legado/app/data/dao/ExploreSearchUrlDao.kt b/app/src/main/java/io/legado/app/data/dao/ExploreSearchUrlDao.kt new file mode 100644 index 000000000..eef3d93ed --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/ExploreSearchUrlDao.kt @@ -0,0 +1,71 @@ +package io.legado.app.data.dao + +import androidx.paging.DataSource +import androidx.room.* +import io.legado.app.data.entities.ExploreSearchUrl + + +@Dao +interface ExploreSearchUrlDao { + + companion object { + private const val ORDER_DEFAULT = "ORDER BY sourceId ASC, defOrder ASC" + private const val ORDER_USAGE = "ORDER BY usage DESC, lastUseTime DESC" + private const val ORDER_TIME = "ORDER BY lastUseTime DESC" + private const val QUERY_NAME = "name LIKE '%' || :name || '%'" + private const val QUERY_ENABLED_EXPLORE = "WHERE type = 0 AND isEnabled = 1" + } + + // 用于发现列表,默认排序 + @Query("SELECT * FROM explore_search_urls $QUERY_ENABLED_EXPLORE $ORDER_DEFAULT") + fun observeExploreUrls(): DataSource.Factory + + // 用于发现列表,按使用次数排序 + @Query("SELECT * FROM explore_search_urls $QUERY_ENABLED_EXPLORE $ORDER_USAGE") + fun observeExploreUrlsByUsage(): DataSource.Factory + + // 用于发现列表,按使用时间排序 + @Query("SELECT * FROM explore_search_urls $QUERY_ENABLED_EXPLORE $ORDER_TIME") + fun observeExploreUrlsByTime(): DataSource.Factory + + // 用于搜索时的发现列表,默认排序 + @Query("SELECT * FROM explore_search_urls $QUERY_ENABLED_EXPLORE AND $QUERY_NAME $ORDER_DEFAULT") + fun observeFilteredExploreUrls(name: String): DataSource.Factory + + // 用于搜索时的发现列表,按使用次数排序 + @Query("SELECT * FROM explore_search_urls $QUERY_ENABLED_EXPLORE AND $QUERY_NAME $ORDER_USAGE") + fun observeFilteredExploreUrlsByUsage(): DataSource.Factory + + // 用于搜索时的发现列表,按使用时间排序 + @Query("SELECT * FROM explore_search_urls $QUERY_ENABLED_EXPLORE AND $QUERY_NAME $ORDER_TIME") + fun observeFilteredExploreUrlsByTime(): DataSource.Factory + + // 获取特定书源的发现 + @Query("SELECT * FROM explore_search_urls $QUERY_ENABLED_EXPLORE AND sourceId = :sourceId") + fun findExploreUrlsBySourceId(sourceId: Int): List + + // 获取特定书源的搜索链接 + @Query("SELECT * FROM explore_search_urls WHERE type = 1 AND sourceId = :sourceId") + fun findSearchUrlsBySourceId(sourceId: Int): List + + // 所有的搜索链接 + @get:Query("SELECT * FROM explore_search_urls WHERE type = 1") + val allSearchUrls: List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg keywords: ExploreSearchUrl) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(keyword: ExploreSearchUrl): Long + + @Update + fun update(vararg keywords: ExploreSearchUrl) + + @Delete + fun delete(vararg keywords: ExploreSearchUrl) + + // 批量删除特定书源的发现和搜索链接,一般用于更新书源时 + @Query("DELETE FROM explore_search_urls WHERE sourceId = :sourceId") + fun deleteBySourceId(sourceId: Int) + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt index 919855143..d4a2073c0 100644 --- a/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt @@ -44,6 +44,7 @@ interface ReplaceRuleDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(replaceRule: ReplaceRule): Long + @Update fun update(vararg replaceRules: ReplaceRule) diff --git a/app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt new file mode 100644 index 000000000..b2370ceb6 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt @@ -0,0 +1,32 @@ +package io.legado.app.data.dao + +import androidx.paging.DataSource +import androidx.room.* +import io.legado.app.data.entities.SearchKeyword + + +@Dao +interface SearchKeywordDao { + + @Query("SELECT * FROM search_keywords ORDER BY usage DESC") + fun observeByUsage(): DataSource.Factory + + @Query("SELECT * FROM search_keywords ORDER BY lastUseTime DESC") + fun observeByTime(): DataSource.Factory + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg keywords: SearchKeyword) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(keyword: SearchKeyword): Long + + @Update + fun update(vararg keywords: SearchKeyword) + + @Delete + fun delete(vararg keywords: SearchKeyword) + + @Query("DELETE FROM search_keywords") + fun deleteAll() + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/ExploreSearchUrl.kt b/app/src/main/java/io/legado/app/data/entities/ExploreSearchUrl.kt new file mode 100644 index 000000000..2df63b4bb --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/ExploreSearchUrl.kt @@ -0,0 +1,29 @@ +package io.legado.app.data.entities + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey +import kotlinx.android.parcel.Parcelize + +@Parcelize +@Entity(tableName = "explore_search_urls", + indices = [(Index(value = ["sourceId", "url"], unique = true))], + foreignKeys = [(ForeignKey(entity = Source::class, + parentColumns = ["sourceId"], + childColumns = ["sourceId"], + onDelete = ForeignKey.CASCADE))]) // 删除书源时自动删除章节 +data class ExploreSearchUrl ( + @PrimaryKey(autoGenerate = true) + var esId: Int = 0, // 编号 + var sourceId: Int = 0, // 书源Id + var name: String = "", // 发现名称,搜索可以没有 + var url: String = "", // 地址 + var type: Int = 0, // 类型,0 为发现,1 为搜索 + var isEnabled: Boolean = true, // 是否启用 + var defOrder: Int = 0, // 默认排序,是在编辑书源的时候的顺序 + var usage: Int = 0, // 使用次数,用于按使用次数排序 + var lastUseTime: Long = 0L // 最后一次使用的时间 + ) : Parcelable + diff --git a/app/src/main/java/io/legado/app/data/entities/SearchKeyword.kt b/app/src/main/java/io/legado/app/data/entities/SearchKeyword.kt new file mode 100644 index 000000000..e8dce4fd9 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/SearchKeyword.kt @@ -0,0 +1,17 @@ +package io.legado.app.data.entities + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import kotlinx.android.parcel.Parcelize + + +@Parcelize +@Entity(tableName = "search_keywords", indices = [(Index(value = ["word"], unique = true))]) +data class SearchKeyword ( + @PrimaryKey + var word: String = "", // 搜索关键词 + var usage: Int = 1, // 使用次数 + var lastUseTime: Long = 0 // 最后一次使用时间 +): Parcelable From 05629e386064999f4d3ffc86932ad59cb0aaeb36 Mon Sep 17 00:00:00 2001 From: atbest Date: Sat, 25 May 2019 19:29:03 -0400 Subject: [PATCH 3/5] Update --- app/src/main/java/io/legado/app/utils/MiscExtensions.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/legado/app/utils/MiscExtensions.kt b/app/src/main/java/io/legado/app/utils/MiscExtensions.kt index 24bf95c9a..1f47d2af9 100644 --- a/app/src/main/java/io/legado/app/utils/MiscExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/MiscExtensions.kt @@ -2,8 +2,8 @@ package io.legado.app.utils import com.jayway.jsonpath.ReadContext -fun ReadContext.readString(path: String) = this.read(path, String::class.java) +fun ReadContext.readString(path: String): String? = this.read(path, String::class.java) -fun ReadContext.readBool(path: String) = this.read(path, Boolean::class.java) +fun ReadContext.readBool(path: String): Boolean? = this.read(path, Boolean::class.java) -fun ReadContext.readInt(path: String) = this.read(path, Int::class.java) \ No newline at end of file +fun ReadContext.readInt(path: String): Int? = this.read(path, Int::class.java) \ No newline at end of file From 43cae29b51445602abe77174a13fe636aa33da25 Mon Sep 17 00:00:00 2001 From: gedoor Date: Sun, 26 May 2019 10:17:37 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E4=B8=BB=E9=A2=98=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/lib/theme/ATH.java | 117 +++++ .../java/io/legado/app/lib/theme/ATHUtil.java | 31 ++ .../io/legado/app/lib/theme/ColorUtil.java | 80 ++++ .../io/legado/app/lib/theme/DrawableUtil.java | 28 ++ .../app/lib/theme/MaterialValueHelper.java | 52 +++ .../app/lib/theme/NavigationViewUtil.java | 55 +++ .../io/legado/app/lib/theme/Selector.java | 430 ++++++++++++++++++ .../io/legado/app/lib/theme/ThemeStore.java | 318 +++++++++++++ .../app/lib/theme/ThemeStoreInterface.java | 92 ++++ .../app/lib/theme/ThemeStorePrefKeys.java | 29 ++ .../io/legado/app/lib/theme/TintHelper.java | 384 ++++++++++++++++ .../io/legado/app/lib/theme/ViewUtil.java | 48 ++ app/src/main/res/values-night/colors.xml | 34 ++ app/src/main/res/values-night/styles.xml | 19 + app/src/main/res/values/colors.xml | 92 ++++ .../res/values/colors_material_design.xml | 338 ++++++++++++++ 16 files changed, 2147 insertions(+) create mode 100644 app/src/main/java/io/legado/app/lib/theme/ATH.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/ATHUtil.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/ColorUtil.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/DrawableUtil.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/NavigationViewUtil.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/Selector.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/ThemeStore.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/TintHelper.java create mode 100644 app/src/main/java/io/legado/app/lib/theme/ViewUtil.java create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/values-night/styles.xml create mode 100644 app/src/main/res/values/colors_material_design.xml diff --git a/app/src/main/java/io/legado/app/lib/theme/ATH.java b/app/src/main/java/io/legado/app/lib/theme/ATH.java new file mode 100644 index 000000000..d7ee194d1 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ATH.java @@ -0,0 +1,117 @@ +package io.legado.app.lib.theme; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.ColorStateList; +import android.os.Build; +import android.view.View; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class ATH { + + @SuppressLint("CommitPrefEdits") + public static boolean didThemeValuesChange(@NonNull Context context, long since) { + return ThemeStore.isConfigured(context) && ThemeStore.prefs(context).getLong(ThemeStore.VALUES_CHANGED, -1) > since; + } + + public static void setStatusbarColorAuto(Activity activity) { + setStatusbarColor(activity, ThemeStore.statusBarColor(activity)); + } + + public static void setStatusbarColor(Activity activity, int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setStatusBarColor(color); + setLightStatusbarAuto(activity, color); + } + } + + public static void setLightStatusbarAuto(Activity activity, int bgColor) { + setLightStatusbar(activity, ColorUtil.isColorLight(bgColor)); + } + + public static void setLightStatusbar(Activity activity, boolean enabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final View decorView = activity.getWindow().getDecorView(); + final int systemUiVisibility = decorView.getSystemUiVisibility(); + if (enabled) { + decorView.setSystemUiVisibility(systemUiVisibility | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + decorView.setSystemUiVisibility(systemUiVisibility & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + } + } + + public static void setLightNavigationbar(Activity activity, boolean enabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final View decorView = activity.getWindow().getDecorView(); + int systemUiVisibility = decorView.getSystemUiVisibility(); + if (enabled) { + systemUiVisibility |= SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } else { + systemUiVisibility &= ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } + decorView.setSystemUiVisibility(systemUiVisibility); + } + } + + public static void setLightNavigationbarAuto(Activity activity, int bgColor) { + setLightNavigationbar(activity, ColorUtil.isColorLight(bgColor)); + } + + public static void setNavigationbarColorAuto(Activity activity) { + setNavigationbarColor(activity, ThemeStore.navigationBarColor(activity)); + } + + public static void setNavigationbarColor(Activity activity, int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity.getWindow().setNavigationBarColor(color); + setLightNavigationbarAuto(activity, color); + } + } + + public static void setTaskDescriptionColorAuto(@NonNull Activity activity) { + setTaskDescriptionColor(activity, ThemeStore.primaryColor(activity)); + } + + public static void setTaskDescriptionColor(@NonNull Activity activity, @ColorInt int color) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // Task description requires fully opaque color + color = ColorUtil.stripAlpha(color); + // Sets color of entry in the system recents page + activity.setTaskDescription(new ActivityManager.TaskDescription((String) activity.getTitle(), null, color)); + } + } + + public static void setTint(@NonNull View view, @ColorInt int color) { + TintHelper.setTintAuto(view, color, false); + } + + public static void setBackgroundTint(@NonNull View view, @ColorInt int color) { + TintHelper.setTintAuto(view, color, true); + } + + public static void setAlertDialogTint(@NonNull AlertDialog dialog) { + ColorStateList colorStateList = Selector.colorBuild() + .setDefaultColor(ThemeStore.accentColor(dialog.getContext())) + .setPressedColor(ColorUtil.darkenColor(ThemeStore.accentColor(dialog.getContext()))) + .create(); + if (dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE) != null) { + dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE).setTextColor(colorStateList); + } + if (dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) != null) { + dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setTextColor(colorStateList); + } + } + + private ATH() { + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ATHUtil.java b/app/src/main/java/io/legado/app/lib/theme/ATHUtil.java new file mode 100644 index 000000000..eb84da3e5 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ATHUtil.java @@ -0,0 +1,31 @@ +package io.legado.app.lib.theme; + +import android.content.Context; +import android.content.res.TypedArray; +import androidx.annotation.AttrRes; + +/** + * @author Aidan Follestad (afollestad) + */ +public final class ATHUtil { + + public static boolean isWindowBackgroundDark(Context context) { + return !ColorUtil.isColorLight(ATHUtil.resolveColor(context, android.R.attr.windowBackground)); + } + + public static int resolveColor(Context context, @AttrRes int attr) { + return resolveColor(context, attr, 0); + } + + public static int resolveColor(Context context, @AttrRes int attr, int fallback) { + TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); + try { + return a.getColor(0, fallback); + } finally { + a.recycle(); + } + } + + private ATHUtil() { + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ColorUtil.java b/app/src/main/java/io/legado/app/lib/theme/ColorUtil.java new file mode 100644 index 000000000..4fc255d07 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ColorUtil.java @@ -0,0 +1,80 @@ +package io.legado.app.lib.theme; + +import android.graphics.Color; +import androidx.annotation.ColorInt; +import androidx.annotation.FloatRange; + +@SuppressWarnings({"unused", "WeakerAccess"}) +public class ColorUtil { + + public static String intToString(int intColor) { + return String.format("#%06X", 0xFFFFFF & intColor); + } + + + public static int stripAlpha(@ColorInt int color) { + return 0xff000000 | color; + } + + @ColorInt + public static int shiftColor(@ColorInt int color, @FloatRange(from = 0.0f, to = 2.0f) float by) { + if (by == 1f) return color; + int alpha = Color.alpha(color); + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + hsv[2] *= by; // value component + return (alpha << 24) + (0x00ffffff & Color.HSVToColor(hsv)); + } + + @ColorInt + public static int darkenColor(@ColorInt int color) { + return shiftColor(color, 0.9f); + } + + @ColorInt + public static int lightenColor(@ColorInt int color) { + return shiftColor(color, 1.1f); + } + + public static boolean isColorLight(@ColorInt int color) { + final double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255; + return darkness < 0.4; + } + + @ColorInt + public static int invertColor(@ColorInt int color) { + final int r = 255 - Color.red(color); + final int g = 255 - Color.green(color); + final int b = 255 - Color.blue(color); + return Color.argb(Color.alpha(color), r, g, b); + } + + @ColorInt + public static int adjustAlpha(@ColorInt int color, @FloatRange(from = 0.0, to = 1.0) float factor) { + int alpha = Math.round(Color.alpha(color) * factor); + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + return Color.argb(alpha, red, green, blue); + } + + @ColorInt + public static int withAlpha(@ColorInt int baseColor, @FloatRange(from = 0.0, to = 1.0) float alpha) { + int a = Math.min(255, Math.max(0, (int) (alpha * 255))) << 24; + int rgb = 0x00ffffff & baseColor; + return a + rgb; + } + + /** + * Taken from CollapsingToolbarLayout's CollapsingTextHelper class. + */ + public static int blendColors(int color1, int color2, @FloatRange(from = 0.0, to = 1.0) float ratio) { + final float inverseRatio = 1f - ratio; + float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio); + float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio); + float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio); + float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio); + return Color.argb((int) a, (int) r, (int) g, (int) b); + } + +} diff --git a/app/src/main/java/io/legado/app/lib/theme/DrawableUtil.java b/app/src/main/java/io/legado/app/lib/theme/DrawableUtil.java new file mode 100644 index 000000000..2254798f5 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/DrawableUtil.java @@ -0,0 +1,28 @@ +package io.legado.app.lib.theme; + +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import androidx.annotation.ColorInt; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class DrawableUtil { + + public static TransitionDrawable createTransitionDrawable(@ColorInt int startColor, @ColorInt int endColor) { + return createTransitionDrawable(new ColorDrawable(startColor), new ColorDrawable(endColor)); + } + + public static TransitionDrawable createTransitionDrawable(Drawable start, Drawable end) { + final Drawable[] drawables = new Drawable[2]; + + drawables[0] = start; + drawables[1] = end; + + return new TransitionDrawable(drawables); + } + + private DrawableUtil() { + } +} diff --git a/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.java b/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.java new file mode 100644 index 000000000..51f9031b4 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.java @@ -0,0 +1,52 @@ +package io.legado.app.lib.theme; + +import android.annotation.SuppressLint; +import android.content.Context; +import androidx.annotation.ColorInt; +import androidx.core.content.ContextCompat; +import io.legado.app.R; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class MaterialValueHelper { + + @SuppressLint("PrivateResource") + @ColorInt + public static int getPrimaryTextColor(final Context context, boolean dark) { + if (dark) { + return ContextCompat.getColor(context, R.color.primary_text_default_material_light); + } + return ContextCompat.getColor(context, R.color.primary_text_default_material_dark); + } + + @SuppressLint("PrivateResource") + @ColorInt + public static int getSecondaryTextColor(final Context context, boolean dark) { + if (dark) { + return ContextCompat.getColor(context, R.color.secondary_text_default_material_light); + } + return ContextCompat.getColor(context, R.color.secondary_text_default_material_dark); + } + + @SuppressLint("PrivateResource") + @ColorInt + public static int getPrimaryDisabledTextColor(final Context context, boolean dark) { + if (dark) { + return ContextCompat.getColor(context, R.color.primary_text_disabled_material_light); + } + return ContextCompat.getColor(context, R.color.primary_text_disabled_material_dark); + } + + @SuppressLint("PrivateResource") + @ColorInt + public static int getSecondaryDisabledTextColor(final Context context, boolean dark) { + if (dark) { + return ContextCompat.getColor(context, R.color.secondary_text_disabled_material_light); + } + return ContextCompat.getColor(context, R.color.secondary_text_disabled_material_dark); + } + + private MaterialValueHelper() { + } +} diff --git a/app/src/main/java/io/legado/app/lib/theme/NavigationViewUtil.java b/app/src/main/java/io/legado/app/lib/theme/NavigationViewUtil.java new file mode 100644 index 000000000..0707cf132 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/NavigationViewUtil.java @@ -0,0 +1,55 @@ +package io.legado.app.lib.theme; + +import android.content.res.ColorStateList; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import com.google.android.material.internal.NavigationMenuView; +import com.google.android.material.navigation.NavigationView; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class NavigationViewUtil { + + public static void setItemIconColors(@NonNull NavigationView navigationView, @ColorInt int normalColor, @ColorInt int selectedColor) { + final ColorStateList iconSl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_checked}, + new int[]{android.R.attr.state_checked} + }, + new int[]{ + normalColor, + selectedColor + }); + navigationView.setItemIconTintList(iconSl); + } + + public static void setItemTextColors(@NonNull NavigationView navigationView, @ColorInt int normalColor, @ColorInt int selectedColor) { + final ColorStateList textSl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_checked}, + new int[]{android.R.attr.state_checked} + }, + new int[]{ + normalColor, + selectedColor + }); + navigationView.setItemTextColor(textSl); + } + + /** + * 去掉navigationView的滚动条 + * @param navigationView NavigationView + */ + public static void disableScrollbar(NavigationView navigationView) { + if (navigationView != null) { + NavigationMenuView navigationMenuView = (NavigationMenuView) navigationView.getChildAt(0); + if (navigationMenuView != null) { + navigationMenuView.setVerticalScrollBarEnabled(false); + } + } + } + + private NavigationViewUtil() { + } +} diff --git a/app/src/main/java/io/legado/app/lib/theme/Selector.java b/app/src/main/java/io/legado/app/lib/theme/Selector.java new file mode 100644 index 000000000..df4e44b59 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/Selector.java @@ -0,0 +1,430 @@ +package io.legado.app.lib.theme; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.StateListDrawable; +import androidx.annotation.ColorInt; +import androidx.annotation.Dimension; +import androidx.annotation.DrawableRes; +import androidx.annotation.IntDef; +import androidx.core.content.ContextCompat; + +public class Selector { + public static ShapeSelector shapeBuild() { + return new ShapeSelector(); + } + + public static ColorSelector colorBuild() { + return new ColorSelector(); + } + + public static DrawableSelector drawableBuild() { + return new DrawableSelector(); + } + + /** + * 形状ShapeSelector + * + * @author hjy + * created at 2017/12/11 22:26 + */ + public static final class ShapeSelector { + @IntDef({GradientDrawable.RECTANGLE, GradientDrawable.OVAL, + GradientDrawable.LINE, GradientDrawable.RING}) + private @interface Shape { + } + + private int mShape; //the shape of background + private int mDefaultBgColor; //default background color + private int mDisabledBgColor; //state_enabled = false + private int mPressedBgColor; //state_pressed = true + private int mSelectedBgColor; //state_selected = true + private int mFocusedBgColor; //state_focused = true + private int mCheckedBgColor; //state_checked = true + private int mStrokeWidth; //stroke width in pixel + private int mDefaultStrokeColor; //default stroke color + private int mDisabledStrokeColor; //state_enabled = false + private int mPressedStrokeColor; //state_pressed = true + private int mSelectedStrokeColor; //state_selected = true + private int mFocusedStrokeColor; //state_focused = true + private int mCheckedStrokeColor; //state_checked = true + private int mCornerRadius; //corner radius + + private boolean hasSetDisabledBgColor = false; + private boolean hasSetPressedBgColor = false; + private boolean hasSetSelectedBgColor = false; + private boolean hasSetFocusedBgColor = false; + private boolean hasSetCheckedBgColor = false; + + private boolean hasSetDisabledStrokeColor = false; + private boolean hasSetPressedStrokeColor = false; + private boolean hasSetSelectedStrokeColor = false; + private boolean hasSetFocusedStrokeColor = false; + private boolean hasSetCheckedStrokeColor = false; + + public ShapeSelector() { + //initialize default values + mShape = GradientDrawable.RECTANGLE; + mDefaultBgColor = Color.TRANSPARENT; + mDisabledBgColor = Color.TRANSPARENT; + mPressedBgColor = Color.TRANSPARENT; + mSelectedBgColor = Color.TRANSPARENT; + mFocusedBgColor = Color.TRANSPARENT; + mStrokeWidth = 0; + mDefaultStrokeColor = Color.TRANSPARENT; + mDisabledStrokeColor = Color.TRANSPARENT; + mPressedStrokeColor = Color.TRANSPARENT; + mSelectedStrokeColor = Color.TRANSPARENT; + mFocusedStrokeColor = Color.TRANSPARENT; + mCornerRadius = 0; + } + + public ShapeSelector setShape(@Shape int shape) { + mShape = shape; + return this; + } + + public ShapeSelector setDefaultBgColor(@ColorInt int color) { + mDefaultBgColor = color; + if (!hasSetDisabledBgColor) + mDisabledBgColor = color; + if (!hasSetPressedBgColor) + mPressedBgColor = color; + if (!hasSetSelectedBgColor) + mSelectedBgColor = color; + if (!hasSetFocusedBgColor) + mFocusedBgColor = color; + return this; + } + + public ShapeSelector setDisabledBgColor(@ColorInt int color) { + mDisabledBgColor = color; + hasSetDisabledBgColor = true; + return this; + } + + public ShapeSelector setPressedBgColor(@ColorInt int color) { + mPressedBgColor = color; + hasSetPressedBgColor = true; + return this; + } + + public ShapeSelector setSelectedBgColor(@ColorInt int color) { + mSelectedBgColor = color; + hasSetSelectedBgColor = true; + return this; + } + + public ShapeSelector setFocusedBgColor(@ColorInt int color) { + mFocusedBgColor = color; + hasSetPressedBgColor = true; + return this; + } + + public ShapeSelector setCheckedBgColor(@ColorInt int color) { + mCheckedBgColor = color; + hasSetCheckedBgColor = true; + return this; + } + + public ShapeSelector setStrokeWidth(@Dimension int width) { + mStrokeWidth = width; + return this; + } + + public ShapeSelector setDefaultStrokeColor(@ColorInt int color) { + mDefaultStrokeColor = color; + if (!hasSetDisabledStrokeColor) + mDisabledStrokeColor = color; + if (!hasSetPressedStrokeColor) + mPressedStrokeColor = color; + if (!hasSetSelectedStrokeColor) + mSelectedStrokeColor = color; + if (!hasSetFocusedStrokeColor) + mFocusedStrokeColor = color; + return this; + } + + public ShapeSelector setDisabledStrokeColor(@ColorInt int color) { + mDisabledStrokeColor = color; + hasSetDisabledStrokeColor = true; + return this; + } + + public ShapeSelector setPressedStrokeColor(@ColorInt int color) { + mPressedStrokeColor = color; + hasSetPressedStrokeColor = true; + return this; + } + + public ShapeSelector setSelectedStrokeColor(@ColorInt int color) { + mSelectedStrokeColor = color; + hasSetSelectedStrokeColor = true; + return this; + } + + public ShapeSelector setCheckedStrokeColor(@ColorInt int color) { + mCheckedStrokeColor = color; + hasSetCheckedStrokeColor = true; + return this; + } + + public ShapeSelector setFocusedStrokeColor(@ColorInt int color) { + mFocusedStrokeColor = color; + hasSetFocusedStrokeColor = true; + return this; + } + + public ShapeSelector setCornerRadius(@Dimension int radius) { + mCornerRadius = radius; + return this; + } + + public StateListDrawable create() { + StateListDrawable selector = new StateListDrawable(); + + //enabled = false + if (hasSetDisabledBgColor || hasSetDisabledStrokeColor) { + GradientDrawable disabledShape = getItemShape(mShape, mCornerRadius, + mDisabledBgColor, mStrokeWidth, mDisabledStrokeColor); + selector.addState(new int[]{-android.R.attr.state_enabled}, disabledShape); + } + + //pressed = true + if (hasSetPressedBgColor || hasSetPressedStrokeColor) { + GradientDrawable pressedShape = getItemShape(mShape, mCornerRadius, + mPressedBgColor, mStrokeWidth, mPressedStrokeColor); + selector.addState(new int[]{android.R.attr.state_pressed}, pressedShape); + } + + //selected = true + if (hasSetSelectedBgColor || hasSetSelectedStrokeColor) { + GradientDrawable selectedShape = getItemShape(mShape, mCornerRadius, + mSelectedBgColor, mStrokeWidth, mSelectedStrokeColor); + selector.addState(new int[]{android.R.attr.state_selected}, selectedShape); + } + + //focused = true + if (hasSetFocusedBgColor || hasSetFocusedStrokeColor) { + GradientDrawable focusedShape = getItemShape(mShape, mCornerRadius, + mFocusedBgColor, mStrokeWidth, mFocusedStrokeColor); + selector.addState(new int[]{android.R.attr.state_focused}, focusedShape); + } + + //checked = true + if (hasSetCheckedBgColor || hasSetCheckedStrokeColor) { + GradientDrawable checkedShape = getItemShape(mShape, mCornerRadius, + mCheckedBgColor, mStrokeWidth, mCheckedStrokeColor); + selector.addState(new int[]{android.R.attr.state_checked}, checkedShape); + } + + //default + GradientDrawable defaultShape = getItemShape(mShape, mCornerRadius, + mDefaultBgColor, mStrokeWidth, mDefaultStrokeColor); + selector.addState(new int[]{}, defaultShape); + + return selector; + } + + private GradientDrawable getItemShape(int shape, int cornerRadius, + int solidColor, int strokeWidth, int strokeColor) { + GradientDrawable drawable = new GradientDrawable(); + drawable.setShape(shape); + drawable.setStroke(strokeWidth, strokeColor); + drawable.setCornerRadius(cornerRadius); + drawable.setColor(solidColor); + return drawable; + } + } + + /** + * 资源DrawableSelector + * + * @author hjy + * created at 2017/12/11 22:34 + */ + public static final class DrawableSelector { + + private Drawable mDefaultDrawable; + private Drawable mDisabledDrawable; + private Drawable mPressedDrawable; + private Drawable mSelectedDrawable; + private Drawable mFocusedDrawable; + + private boolean hasSetDisabledDrawable = false; + private boolean hasSetPressedDrawable = false; + private boolean hasSetSelectedDrawable = false; + private boolean hasSetFocusedDrawable = false; + + private DrawableSelector() { + mDefaultDrawable = new ColorDrawable(Color.TRANSPARENT); + } + + public DrawableSelector setDefaultDrawable(Drawable drawable) { + mDefaultDrawable = drawable; + if (!hasSetDisabledDrawable) + mDisabledDrawable = drawable; + if (!hasSetPressedDrawable) + mPressedDrawable = drawable; + if (!hasSetSelectedDrawable) + mSelectedDrawable = drawable; + if (!hasSetFocusedDrawable) + mFocusedDrawable = drawable; + return this; + } + + public DrawableSelector setDisabledDrawable(Drawable drawable) { + mDisabledDrawable = drawable; + hasSetDisabledDrawable = true; + return this; + } + + public DrawableSelector setPressedDrawable(Drawable drawable) { + mPressedDrawable = drawable; + hasSetPressedDrawable = true; + return this; + } + + public DrawableSelector setSelectedDrawable(Drawable drawable) { + mSelectedDrawable = drawable; + hasSetSelectedDrawable = true; + return this; + } + + public DrawableSelector setFocusedDrawable(Drawable drawable) { + mFocusedDrawable = drawable; + hasSetFocusedDrawable = true; + return this; + } + + public StateListDrawable create() { + StateListDrawable selector = new StateListDrawable(); + if (hasSetDisabledDrawable) + selector.addState(new int[]{-android.R.attr.state_enabled}, mDisabledDrawable); + if (hasSetPressedDrawable) + selector.addState(new int[]{android.R.attr.state_pressed}, mPressedDrawable); + if (hasSetSelectedDrawable) + selector.addState(new int[]{android.R.attr.state_selected}, mSelectedDrawable); + if (hasSetFocusedDrawable) + selector.addState(new int[]{android.R.attr.state_focused}, mFocusedDrawable); + selector.addState(new int[]{}, mDefaultDrawable); + return selector; + } + + public DrawableSelector setDefaultDrawable(Context context, @DrawableRes int drawableRes) { + return setDefaultDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + + public DrawableSelector setDisabledDrawable(Context context, @DrawableRes int drawableRes) { + return setDisabledDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + + public DrawableSelector setPressedDrawable(Context context, @DrawableRes int drawableRes) { + return setPressedDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + + public DrawableSelector setSelectedDrawable(Context context, @DrawableRes int drawableRes) { + return setSelectedDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + + public DrawableSelector setFocusedDrawable(Context context, @DrawableRes int drawableRes) { + return setFocusedDrawable(ContextCompat.getDrawable(context, drawableRes)); + } + } + + /** + * 颜色ColorSelector + * + * @author hjy + * created at 2017/12/11 22:26 + */ + public static final class ColorSelector { + + private int mDefaultColor; + private int mDisabledColor; + private int mPressedColor; + private int mSelectedColor; + private int mFocusedColor; + private int mCheckedColor; + + private boolean hasSetDisabledColor = false; + private boolean hasSetPressedColor = false; + private boolean hasSetSelectedColor = false; + private boolean hasSetFocusedColor = false; + private boolean hasSetCheckedColor = false; + + private ColorSelector() { + mDefaultColor = Color.BLACK; + mDisabledColor = Color.GRAY; + mPressedColor = Color.BLACK; + mSelectedColor = Color.BLACK; + mFocusedColor = Color.BLACK; + } + + public ColorSelector setDefaultColor(@ColorInt int color) { + mDefaultColor = color; + if (!hasSetDisabledColor) + mDisabledColor = color; + if (!hasSetPressedColor) + mPressedColor = color; + if (!hasSetSelectedColor) + mSelectedColor = color; + if (!hasSetFocusedColor) + mFocusedColor = color; + return this; + } + + public ColorSelector setDisabledColor(@ColorInt int color) { + mDisabledColor = color; + hasSetDisabledColor = true; + return this; + } + + public ColorSelector setPressedColor(@ColorInt int color) { + mPressedColor = color; + hasSetPressedColor = true; + return this; + } + + public ColorSelector setSelectedColor(@ColorInt int color) { + mSelectedColor = color; + hasSetSelectedColor = true; + return this; + } + + public ColorSelector setFocusedColor(@ColorInt int color) { + mFocusedColor = color; + hasSetFocusedColor = true; + return this; + } + + public ColorSelector setCheckedColor(@ColorInt int color) { + mCheckedColor = color; + hasSetCheckedColor = true; + return this; + } + + public ColorStateList create() { + int[] colors = new int[]{ + hasSetDisabledColor ? mDisabledColor : mDefaultColor, + hasSetPressedColor ? mPressedColor : mDefaultColor, + hasSetSelectedColor ? mSelectedColor : mDefaultColor, + hasSetFocusedColor ? mFocusedColor : mDefaultColor, + hasSetCheckedColor ? mCheckedColor : mDefaultColor, + mDefaultColor + }; + int[][] states = new int[6][]; + states[0] = new int[]{-android.R.attr.state_enabled}; + states[1] = new int[]{android.R.attr.state_pressed}; + states[2] = new int[]{android.R.attr.state_selected}; + states[3] = new int[]{android.R.attr.state_focused}; + states[4] = new int[]{android.R.attr.state_checked}; + states[5] = new int[]{}; + return new ColorStateList(states, colors); + } + } +} diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.java b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.java new file mode 100644 index 000000000..52e4544ea --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.java @@ -0,0 +1,318 @@ +package io.legado.app.lib.theme; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; +import androidx.annotation.*; +import androidx.core.content.ContextCompat; +import io.legado.app.R; + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +public final class ThemeStore implements ThemeStorePrefKeys, ThemeStoreInterface { + + private final Context mContext; + private final SharedPreferences.Editor mEditor; + + public static ThemeStore editTheme(@NonNull Context context) { + return new ThemeStore(context); + } + + @SuppressLint("CommitPrefEdits") + private ThemeStore(@NonNull Context context) { + mContext = context; + mEditor = prefs(context).edit(); + } + + + @Override + public ThemeStore primaryColor(@ColorInt int color) { + mEditor.putInt(KEY_PRIMARY_COLOR, color); + if (autoGeneratePrimaryDark(mContext)) + primaryColorDark(ColorUtil.darkenColor(color)); + return this; + } + + @Override + public ThemeStore primaryColorRes(@ColorRes int colorRes) { + return primaryColor(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore primaryColorAttr(@AttrRes int colorAttr) { + return primaryColor(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore primaryColorDark(@ColorInt int color) { + mEditor.putInt(KEY_PRIMARY_COLOR_DARK, color); + return this; + } + + @Override + public ThemeStore primaryColorDarkRes(@ColorRes int colorRes) { + return primaryColorDark(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore primaryColorDarkAttr(@AttrRes int colorAttr) { + return primaryColorDark(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore accentColor(@ColorInt int color) { + mEditor.putInt(KEY_ACCENT_COLOR, color); + return this; + } + + @Override + public ThemeStore accentColorRes(@ColorRes int colorRes) { + return accentColor(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore accentColorAttr(@AttrRes int colorAttr) { + return accentColor(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore statusBarColor(@ColorInt int color) { + mEditor.putInt(KEY_STATUS_BAR_COLOR, color); + return this; + } + + @Override + public ThemeStore statusBarColorRes(@ColorRes int colorRes) { + return statusBarColor(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore statusBarColorAttr(@AttrRes int colorAttr) { + return statusBarColor(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore navigationBarColor(@ColorInt int color) { + mEditor.putInt(KEY_NAVIGATION_BAR_COLOR, color); + return this; + } + + @Override + public ThemeStore navigationBarColorRes(@ColorRes int colorRes) { + return navigationBarColor(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore navigationBarColorAttr(@AttrRes int colorAttr) { + return navigationBarColor(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore textColorPrimary(@ColorInt int color) { + mEditor.putInt(KEY_TEXT_COLOR_PRIMARY, color); + return this; + } + + @Override + public ThemeStore textColorPrimaryRes(@ColorRes int colorRes) { + return textColorPrimary(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore textColorPrimaryAttr(@AttrRes int colorAttr) { + return textColorPrimary(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore textColorPrimaryInverse(@ColorInt int color) { + mEditor.putInt(KEY_TEXT_COLOR_PRIMARY_INVERSE, color); + return this; + } + + @Override + public ThemeStore textColorPrimaryInverseRes(@ColorRes int colorRes) { + return textColorPrimaryInverse(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore textColorPrimaryInverseAttr(@AttrRes int colorAttr) { + return textColorPrimaryInverse(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore textColorSecondary(@ColorInt int color) { + mEditor.putInt(KEY_TEXT_COLOR_SECONDARY, color); + return this; + } + + @Override + public ThemeStore textColorSecondaryRes(@ColorRes int colorRes) { + return textColorSecondary(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore textColorSecondaryAttr(@AttrRes int colorAttr) { + return textColorSecondary(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore textColorSecondaryInverse(@ColorInt int color) { + mEditor.putInt(KEY_TEXT_COLOR_SECONDARY_INVERSE, color); + return this; + } + + @Override + public ThemeStore textColorSecondaryInverseRes(@ColorRes int colorRes) { + return textColorSecondaryInverse(ContextCompat.getColor(mContext, colorRes)); + } + + @Override + public ThemeStore textColorSecondaryInverseAttr(@AttrRes int colorAttr) { + return textColorSecondaryInverse(ATHUtil.resolveColor(mContext, colorAttr)); + } + + @Override + public ThemeStore backgroundColor(int color) { + mEditor.putInt(KEY_BACKGROUND_COLOR, color); + return this; + } + + @Override + public ThemeStore coloredStatusBar(boolean colored) { + mEditor.putBoolean(KEY_APPLY_PRIMARYDARK_STATUSBAR, colored); + return this; + } + + @Override + public ThemeStore coloredNavigationBar(boolean applyToNavBar) { + mEditor.putBoolean(KEY_APPLY_PRIMARY_NAVBAR, applyToNavBar); + return this; + } + + @Override + public ThemeStore autoGeneratePrimaryDark(boolean autoGenerate) { + mEditor.putBoolean(KEY_AUTO_GENERATE_PRIMARYDARK, autoGenerate); + return this; + } + + // Commit method + + @SuppressWarnings("unchecked") + @Override + public void apply() { + mEditor.putLong(VALUES_CHANGED, System.currentTimeMillis()) + .putBoolean(IS_CONFIGURED_KEY, true) + .apply(); + } + + // Static getters + + @CheckResult + @NonNull + protected static SharedPreferences prefs(@NonNull Context context) { + return context.getSharedPreferences(CONFIG_PREFS_KEY_DEFAULT, Context.MODE_PRIVATE); + } + + public static void markChanged(@NonNull Context context) { + new ThemeStore(context).apply(); + } + + @CheckResult + @ColorInt + public static int primaryColor(@NonNull Context context) { + return prefs(context).getInt(KEY_PRIMARY_COLOR, ATHUtil.resolveColor(context, R.attr.colorPrimary, Color.parseColor("#455A64"))); + } + + @CheckResult + @ColorInt + public static int primaryColorDark(@NonNull Context context) { + return prefs(context).getInt(KEY_PRIMARY_COLOR_DARK, ATHUtil.resolveColor(context, R.attr.colorPrimaryDark, Color.parseColor("#37474F"))); + } + + @CheckResult + @ColorInt + public static int accentColor(@NonNull Context context) { + return prefs(context).getInt(KEY_ACCENT_COLOR, ATHUtil.resolveColor(context, R.attr.colorAccent, Color.parseColor("#263238"))); + } + + @CheckResult + @ColorInt + public static int statusBarColor(@NonNull Context context) { + if (!coloredStatusBar(context)) { + return Color.BLACK; + } + return prefs(context).getInt(KEY_STATUS_BAR_COLOR, primaryColorDark(context)); + } + + @CheckResult + @ColorInt + public static int navigationBarColor(@NonNull Context context) { + if (!coloredNavigationBar(context)) { + return Color.BLACK; + } + return prefs(context).getInt(KEY_NAVIGATION_BAR_COLOR, primaryColor(context)); + } + + @CheckResult + @ColorInt + public static int textColorPrimary(@NonNull Context context) { + return prefs(context).getInt(KEY_TEXT_COLOR_PRIMARY, ATHUtil.resolveColor(context, android.R.attr.textColorPrimary)); + } + + @CheckResult + @ColorInt + public static int textColorPrimaryInverse(@NonNull Context context) { + return prefs(context).getInt(KEY_TEXT_COLOR_PRIMARY_INVERSE, ATHUtil.resolveColor(context, android.R.attr.textColorPrimaryInverse)); + } + + @CheckResult + @ColorInt + public static int textColorSecondary(@NonNull Context context) { + return prefs(context).getInt(KEY_TEXT_COLOR_SECONDARY, ATHUtil.resolveColor(context, android.R.attr.textColorSecondary)); + } + + @CheckResult + @ColorInt + public static int textColorSecondaryInverse(@NonNull Context context) { + return prefs(context).getInt(KEY_TEXT_COLOR_SECONDARY_INVERSE, ATHUtil.resolveColor(context, android.R.attr.textColorSecondaryInverse)); + } + + @CheckResult + @ColorInt + public static int backgroundColor(@NonNull Context context) { + return prefs(context).getInt(KEY_BACKGROUND_COLOR, ATHUtil.resolveColor(context, android.R.attr.colorBackground)); + } + + @CheckResult + public static boolean coloredStatusBar(@NonNull Context context) { + return prefs(context).getBoolean(KEY_APPLY_PRIMARYDARK_STATUSBAR, true); + } + + @CheckResult + public static boolean coloredNavigationBar(@NonNull Context context) { + return prefs(context).getBoolean(KEY_APPLY_PRIMARY_NAVBAR, false); + } + + @CheckResult + public static boolean autoGeneratePrimaryDark(@NonNull Context context) { + return prefs(context).getBoolean(KEY_AUTO_GENERATE_PRIMARYDARK, true); + } + + @CheckResult + public static boolean isConfigured(Context context) { + return prefs(context).getBoolean(IS_CONFIGURED_KEY, false); + } + + @SuppressLint("CommitPrefEdits") + public static boolean isConfigured(Context context, @IntRange(from = 0, to = Integer.MAX_VALUE) int version) { + final SharedPreferences prefs = prefs(context); + final int lastVersion = prefs.getInt(IS_CONFIGURED_VERSION_KEY, -1); + if (version > lastVersion) { + prefs.edit().putInt(IS_CONFIGURED_VERSION_KEY, version).apply(); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.java b/app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.java new file mode 100644 index 000000000..4c3214396 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.java @@ -0,0 +1,92 @@ +package io.legado.app.lib.theme; + + +import androidx.annotation.AttrRes; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +interface ThemeStoreInterface { + + // Primary colors + + ThemeStore primaryColor(@ColorInt int color); + + ThemeStore primaryColorRes(@ColorRes int colorRes); + + ThemeStore primaryColorAttr(@AttrRes int colorAttr); + + ThemeStore autoGeneratePrimaryDark(boolean autoGenerate); + + ThemeStore primaryColorDark(@ColorInt int color); + + ThemeStore primaryColorDarkRes(@ColorRes int colorRes); + + ThemeStore primaryColorDarkAttr(@AttrRes int colorAttr); + + // Accent colors + + ThemeStore accentColor(@ColorInt int color); + + ThemeStore accentColorRes(@ColorRes int colorRes); + + ThemeStore accentColorAttr(@AttrRes int colorAttr); + + // Status bar color + + ThemeStore statusBarColor(@ColorInt int color); + + ThemeStore statusBarColorRes(@ColorRes int colorRes); + + ThemeStore statusBarColorAttr(@AttrRes int colorAttr); + + // Navigation bar color + + ThemeStore navigationBarColor(@ColorInt int color); + + ThemeStore navigationBarColorRes(@ColorRes int colorRes); + + ThemeStore navigationBarColorAttr(@AttrRes int colorAttr); + + // Primary text color + + ThemeStore textColorPrimary(@ColorInt int color); + + ThemeStore textColorPrimaryRes(@ColorRes int colorRes); + + ThemeStore textColorPrimaryAttr(@AttrRes int colorAttr); + + ThemeStore textColorPrimaryInverse(@ColorInt int color); + + ThemeStore textColorPrimaryInverseRes(@ColorRes int colorRes); + + ThemeStore textColorPrimaryInverseAttr(@AttrRes int colorAttr); + + // Secondary text color + + ThemeStore textColorSecondary(@ColorInt int color); + + ThemeStore textColorSecondaryRes(@ColorRes int colorRes); + + ThemeStore textColorSecondaryAttr(@AttrRes int colorAttr); + + ThemeStore textColorSecondaryInverse(@ColorInt int color); + + ThemeStore textColorSecondaryInverseRes(@ColorRes int colorRes); + + ThemeStore textColorSecondaryInverseAttr(@AttrRes int colorAttr); + + ThemeStore backgroundColor(@ColorInt int color); + + // Toggle configurations + + ThemeStore coloredStatusBar(boolean colored); + + ThemeStore coloredNavigationBar(boolean applyToNavBar); + + // Commit/apply + + void apply(); +} diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.java b/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.java new file mode 100644 index 000000000..9a8286f9e --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.java @@ -0,0 +1,29 @@ +package io.legado.app.lib.theme; + +/** + * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) + */ +interface ThemeStorePrefKeys { + + String CONFIG_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]"; + String IS_CONFIGURED_KEY = "is_configured"; + String IS_CONFIGURED_VERSION_KEY = "is_configured_version"; + String VALUES_CHANGED = "values_changed"; + + String KEY_PRIMARY_COLOR = "primary_color"; + String KEY_PRIMARY_COLOR_DARK = "primary_color_dark"; + String KEY_ACCENT_COLOR = "accent_color"; + String KEY_STATUS_BAR_COLOR = "status_bar_color"; + String KEY_NAVIGATION_BAR_COLOR = "navigation_bar_color"; + + String KEY_TEXT_COLOR_PRIMARY = "text_color_primary"; + String KEY_TEXT_COLOR_PRIMARY_INVERSE = "text_color_primary_inverse"; + String KEY_TEXT_COLOR_SECONDARY = "text_color_secondary"; + String KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse"; + + String KEY_BACKGROUND_COLOR = "backgroundColor"; + + String KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar"; + String KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar"; + String KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark"; +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/TintHelper.java b/app/src/main/java/io/legado/app/lib/theme/TintHelper.java new file mode 100644 index 000000000..439190147 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/TintHelper.java @@ -0,0 +1,384 @@ +package io.legado.app.lib.theme; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.RippleDrawable; +import android.os.Build; +import android.view.View; +import android.widget.*; +import androidx.annotation.CheckResult; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatEditText; +import androidx.appcompat.widget.SearchView; +import androidx.appcompat.widget.SwitchCompat; +import androidx.core.content.ContextCompat; +import androidx.core.graphics.drawable.DrawableCompat; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import io.legado.app.R; + +import java.lang.reflect.Field; + +/** + * @author afollestad, plusCubed + */ +public final class TintHelper { + + @SuppressLint("PrivateResource") + @ColorInt + private static int getDefaultRippleColor(@NonNull Context context, boolean useDarkRipple) { + // Light ripple is actually translucent black, and vice versa + return ContextCompat.getColor(context, useDarkRipple ? + R.color.ripple_material_light : R.color.ripple_material_dark); + } + + @NonNull + private static ColorStateList getDisabledColorStateList(@ColorInt int normal, @ColorInt int disabled) { + return new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled} + }, new int[]{ + disabled, + normal + }); + } + + @SuppressWarnings("deprecation") + public static void setTintSelector(@NonNull View view, @ColorInt final int color, final boolean darker, final boolean useDarkTheme) { + final boolean isColorLight = ColorUtil.isColorLight(color); + final int disabled = ContextCompat.getColor(view.getContext(), useDarkTheme ? R.color.ate_button_disabled_dark : R.color.ate_button_disabled_light); + final int pressed = ColorUtil.shiftColor(color, darker ? 0.9f : 1.1f); + final int activated = ColorUtil.shiftColor(color, darker ? 1.1f : 0.9f); + final int rippleColor = getDefaultRippleColor(view.getContext(), isColorLight); + final int textColor = ContextCompat.getColor(view.getContext(), isColorLight ? R.color.ate_primary_text_light : R.color.ate_primary_text_dark); + + final ColorStateList sl; + if (view instanceof Button) { + sl = getDisabledColorStateList(color, disabled); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + view.getBackground() instanceof RippleDrawable) { + RippleDrawable rd = (RippleDrawable) view.getBackground(); + rd.setColor(ColorStateList.valueOf(rippleColor)); + } + + // Disabled text color state for buttons, may get overridden later by ATE tags + final Button button = (Button) view; + button.setTextColor(getDisabledColorStateList(textColor, ContextCompat.getColor(view.getContext(), useDarkTheme ? R.color.ate_button_text_disabled_dark : R.color.ate_button_text_disabled_light))); + } else if (view instanceof FloatingActionButton) { + // FloatingActionButton doesn't support disabled state? + sl = new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_pressed}, + new int[]{android.R.attr.state_pressed} + }, new int[]{ + color, + pressed + }); + + final FloatingActionButton fab = (FloatingActionButton) view; + fab.setRippleColor(rippleColor); + fab.setBackgroundTintList(sl); + if (fab.getDrawable() != null) + fab.setImageDrawable(createTintedDrawable(fab.getDrawable(), textColor)); + return; + } else { + sl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_activated}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_checked} + }, + new int[]{ + disabled, + color, + pressed, + activated, + activated + } + ); + } + + Drawable drawable = view.getBackground(); + if (drawable != null) { + drawable = createTintedDrawable(drawable, sl); + ViewUtil.setBackgroundCompat(view, drawable); + } + + if (view instanceof TextView && !(view instanceof Button)) { + final TextView tv = (TextView) view; + tv.setTextColor(getDisabledColorStateList(textColor, ContextCompat.getColor(view.getContext(), isColorLight ? R.color.ate_text_disabled_light : R.color.ate_text_disabled_dark))); + } + } + + public static void setTintAuto(final @NonNull View view, final @ColorInt int color, + boolean background) { + setTintAuto(view, color, background, ATHUtil.isWindowBackgroundDark(view.getContext())); + } + + @SuppressWarnings("deprecation") + public static void setTintAuto(final @NonNull View view, final @ColorInt int color, + boolean background, final boolean isDark) { + if (!background) { + if (view instanceof RadioButton) + setTint((RadioButton) view, color, isDark); + else if (view instanceof SeekBar) + setTint((SeekBar) view, color, isDark); + else if (view instanceof ProgressBar) + setTint((ProgressBar) view, color); + else if (view instanceof AppCompatEditText) + setTint((AppCompatEditText) view, color, isDark); + else if (view instanceof CheckBox) + setTint((CheckBox) view, color, isDark); + else if (view instanceof ImageView) + setTint((ImageView) view, color); + else if (view instanceof Switch) + setTint((Switch) view, color, isDark); + else if (view instanceof SwitchCompat) + setTint((SwitchCompat) view, color, isDark); + else if (view instanceof SearchView) { + int iconIdS[] = new int[]{androidx.appcompat.R.id.search_button, androidx.appcompat.R.id.search_close_btn,}; + for (int iconId : iconIdS) { + ImageView icon = view.findViewById(iconId); + if (icon != null) { + setTint(icon, color); + } + } + + } else { + background = true; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + !background && view.getBackground() instanceof RippleDrawable) { + // Ripples for the above views (e.g. when you tap and hold a switch or checkbox) + RippleDrawable rd = (RippleDrawable) view.getBackground(); + @SuppressLint("PrivateResource") final int unchecked = ContextCompat.getColor(view.getContext(), + isDark ? R.color.ripple_material_dark : R.color.ripple_material_light); + final int checked = ColorUtil.adjustAlpha(color, 0.4f); + final ColorStateList sl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_activated, -android.R.attr.state_checked}, + new int[]{android.R.attr.state_activated}, + new int[]{android.R.attr.state_checked} + }, + new int[]{ + unchecked, + checked, + checked + } + ); + rd.setColor(sl); + } + } + if (background) { + // Need to tint the background of a view + if (view instanceof FloatingActionButton || view instanceof Button) { + setTintSelector(view, color, false, isDark); + } else if (view.getBackground() != null) { + Drawable drawable = view.getBackground(); + if (drawable != null) { + drawable = createTintedDrawable(drawable, color); + ViewUtil.setBackgroundCompat(view, drawable); + } + } + } + } + + public static void setTint(@NonNull RadioButton radioButton, @ColorInt int color, boolean useDarker) { + ColorStateList sl = new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, -android.R.attr.state_checked}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_checked} + }, new int[]{ + // Rdio button includes own alpha for disabled state + ColorUtil.stripAlpha(ContextCompat.getColor(radioButton.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light)), + ContextCompat.getColor(radioButton.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light), + color + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + radioButton.setButtonTintList(sl); + } else { + Drawable d = createTintedDrawable(ContextCompat.getDrawable(radioButton.getContext(), R.drawable.abc_btn_radio_material), sl); + radioButton.setButtonDrawable(d); + } + } + + public static void setTint(@NonNull SeekBar seekBar, @ColorInt int color, boolean useDarker) { + final ColorStateList s1 = getDisabledColorStateList(color, + ContextCompat.getColor(seekBar.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + seekBar.setThumbTintList(s1); + seekBar.setProgressTintList(s1); + } else { + Drawable progressDrawable = createTintedDrawable(seekBar.getProgressDrawable(), s1); + seekBar.setProgressDrawable(progressDrawable); + Drawable thumbDrawable = createTintedDrawable(seekBar.getThumb(), s1); + seekBar.setThumb(thumbDrawable); + } + } + + public static void setTint(@NonNull ProgressBar progressBar, @ColorInt int color) { + setTint(progressBar, color, false); + } + + public static void setTint(@NonNull ProgressBar progressBar, @ColorInt int color, boolean skipIndeterminate) { + ColorStateList sl = ColorStateList.valueOf(color); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + progressBar.setProgressTintList(sl); + progressBar.setSecondaryProgressTintList(sl); + if (!skipIndeterminate) + progressBar.setIndeterminateTintList(sl); + } else { + PorterDuff.Mode mode = PorterDuff.Mode.SRC_IN; + if (!skipIndeterminate && progressBar.getIndeterminateDrawable() != null) + progressBar.getIndeterminateDrawable().setColorFilter(color, mode); + if (progressBar.getProgressDrawable() != null) + progressBar.getProgressDrawable().setColorFilter(color, mode); + } + } + + + @SuppressLint("RestrictedApi") + public static void setTint(@NonNull AppCompatEditText editText, @ColorInt int color, boolean useDarker) { + final ColorStateList editTextColorStateList = new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, -android.R.attr.state_pressed, -android.R.attr.state_focused}, + new int[]{} + }, new int[]{ + ContextCompat.getColor(editText.getContext(), useDarker ? R.color.ate_text_disabled_dark : R.color.ate_text_disabled_light), + ContextCompat.getColor(editText.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light), + color + }); + editText.setSupportBackgroundTintList(editTextColorStateList); + setCursorTint(editText, color); + } + + public static void setTint(@NonNull CheckBox box, @ColorInt int color, boolean useDarker) { + ColorStateList sl = new ColorStateList(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, -android.R.attr.state_checked}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_checked} + }, new int[]{ + ContextCompat.getColor(box.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light), + ContextCompat.getColor(box.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light), + color + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + box.setButtonTintList(sl); + } else { + Drawable drawable = createTintedDrawable(ContextCompat.getDrawable(box.getContext(), R.drawable.abc_btn_check_material), sl); + box.setButtonDrawable(drawable); + } + } + + public static void setTint(@NonNull ImageView image, @ColorInt int color) { + image.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + } + + private static Drawable modifySwitchDrawable(@NonNull Context context, @NonNull Drawable from, @ColorInt int tint, boolean thumb, boolean compatSwitch, boolean useDarker) { + if (useDarker) { + tint = ColorUtil.shiftColor(tint, 1.1f); + } + tint = ColorUtil.adjustAlpha(tint, (compatSwitch && !thumb) ? 0.5f : 1.0f); + int disabled; + int normal; + if (thumb) { + disabled = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_thumb_disabled_dark : R.color.ate_switch_thumb_disabled_light); + normal = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_thumb_normal_dark : R.color.ate_switch_thumb_normal_light); + } else { + disabled = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_track_disabled_dark : R.color.ate_switch_track_disabled_light); + normal = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_track_normal_dark : R.color.ate_switch_track_normal_light); + } + + // Stock switch includes its own alpha + if (!compatSwitch) { + normal = ColorUtil.stripAlpha(normal); + } + + final ColorStateList sl = new ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, -android.R.attr.state_activated, -android.R.attr.state_checked}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_activated}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_checked} + }, + new int[]{ + disabled, + normal, + tint, + tint + } + ); + return createTintedDrawable(from, sl); + } + + public static void setTint(@NonNull Switch switchView, @ColorInt int color, boolean useDarker) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return; + if (switchView.getTrackDrawable() != null) { + switchView.setTrackDrawable(modifySwitchDrawable(switchView.getContext(), + switchView.getTrackDrawable(), color, false, false, useDarker)); + } + if (switchView.getThumbDrawable() != null) { + switchView.setThumbDrawable(modifySwitchDrawable(switchView.getContext(), + switchView.getThumbDrawable(), color, true, false, useDarker)); + } + } + + public static void setTint(@NonNull SwitchCompat switchView, @ColorInt int color, boolean useDarker) { + if (switchView.getTrackDrawable() != null) { + switchView.setTrackDrawable(modifySwitchDrawable(switchView.getContext(), + switchView.getTrackDrawable(), color, false, true, useDarker)); + } + if (switchView.getThumbDrawable() != null) { + switchView.setThumbDrawable(modifySwitchDrawable(switchView.getContext(), + switchView.getThumbDrawable(), color, true, true, useDarker)); + } + } + + // This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise. + @CheckResult + @Nullable + public static Drawable createTintedDrawable(@Nullable Drawable drawable, @ColorInt int color) { + if (drawable == null) return null; + drawable = DrawableCompat.wrap(drawable.mutate()); + DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_IN); + DrawableCompat.setTint(drawable, color); + return drawable; + } + + // This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise. + @CheckResult + @Nullable + public static Drawable createTintedDrawable(@Nullable Drawable drawable, @NonNull ColorStateList sl) { + if (drawable == null) return null; + drawable = DrawableCompat.wrap(drawable.mutate()); + DrawableCompat.setTintList(drawable, sl); + return drawable; + } + + public static void setCursorTint(@NonNull EditText editText, @ColorInt int color) { + try { + Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes"); + fCursorDrawableRes.setAccessible(true); + int mCursorDrawableRes = fCursorDrawableRes.getInt(editText); + Field fEditor = TextView.class.getDeclaredField("mEditor"); + fEditor.setAccessible(true); + Object editor = fEditor.get(editText); + Class clazz = editor.getClass(); + Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable"); + fCursorDrawable.setAccessible(true); + Drawable[] drawables = new Drawable[2]; + drawables[0] = ContextCompat.getDrawable(editText.getContext(), mCursorDrawableRes); + drawables[0] = createTintedDrawable(drawables[0], color); + drawables[1] = ContextCompat.getDrawable(editText.getContext(), mCursorDrawableRes); + drawables[1] = createTintedDrawable(drawables[1], color); + fCursorDrawable.set(editor, drawables); + } catch (Exception ignored) { + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/lib/theme/ViewUtil.java b/app/src/main/java/io/legado/app/lib/theme/ViewUtil.java new file mode 100644 index 000000000..7fd0f65e3 --- /dev/null +++ b/app/src/main/java/io/legado/app/lib/theme/ViewUtil.java @@ -0,0 +1,48 @@ +package io.legado.app.lib.theme; + +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.view.View; +import android.view.ViewTreeObserver; +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public final class ViewUtil { + + @SuppressWarnings("deprecation") + public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) { + v.getViewTreeObserver().removeOnGlobalLayoutListener(listener); + } + + @SuppressWarnings("deprecation") + public static void setBackgroundCompat(@NonNull View view, @Nullable Drawable drawable) { + view.setBackground(drawable); + } + + public static TransitionDrawable setBackgroundTransition(@NonNull View view, @NonNull Drawable newDrawable) { + TransitionDrawable transition = DrawableUtil.createTransitionDrawable(view.getBackground(), newDrawable); + setBackgroundCompat(view, transition); + return transition; + } + + public static TransitionDrawable setBackgroundColorTransition(@NonNull View view, @ColorInt int newColor) { + final Drawable oldColor = view.getBackground(); + + Drawable start = oldColor != null ? oldColor : new ColorDrawable(view.getSolidColor()); + Drawable end = new ColorDrawable(newColor); + + TransitionDrawable transition = DrawableUtil.createTransitionDrawable(start, end); + + setBackgroundCompat(view, transition); + + return transition; + } + + private ViewUtil() { + } +} diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 000000000..d53b8cac2 --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,34 @@ + + + @color/md_grey_800 + #353535 + #282828 + + #69000000 + + @color/md_grey_800 + @color/md_grey_900 + @color/md_grey_100 + + #30ffffff + + #363636 + #804D4D4D + #80686868 + #88111111 + #66666666 + + #737373 + #565656 + + #ffffffff + #e5ffffff + #b3ffffff + #b7b7b7 + + + #303030 + + + #222222 + diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..c6d88fbc5 --- /dev/null +++ b/app/src/main/res/values-night/styles.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8194c2ece..43af45038 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,96 @@ #66666666 + #eb4333 + #439b53 + + #00000000 + + @color/md_grey_100 + #dedede + #fcfcfc + #00000000 + + #30000000 + + #d3321b + + #80ACACAC + #80858585 + #88000000 + + #737373 + #adadad + + #de000000 + #b2000000 + #8a000000 + #dfdfdf + #383838 + + + #efefef + + #23000000 + #EEEEEE + #aaaaaaaa + + + #FFD4D4D4 + + #64000000 + #f4f4f4 + + + #99343434 + #000000 + #ffffff + + + + + + #1F000000 + #1F000000 + + #43000000 + #43000000 + #43000000 + + #61000000 + + #8A000000 + #8A000000 + #8A000000 + + #DE000000 + + #FFFAFAFA + + #FFBDBDBD + + #E8E8E8 + + + + #1AFFFFFF + + #1F000000 + + #4DFFFFFF + #4DFFFFFF + #4DFFFFFF + #4DFFFFFF + + #B3FFFFFF + #B3FFFFFF + #B3FFFFFF + + #FFFFFFFF + + #FFBDBDBD + + #FF424242 + + #202020 diff --git a/app/src/main/res/values/colors_material_design.xml b/app/src/main/res/values/colors_material_design.xml new file mode 100644 index 000000000..a00ddac9e --- /dev/null +++ b/app/src/main/res/values/colors_material_design.xml @@ -0,0 +1,338 @@ + + + + + + + @color/md_grey_50 + + #1F000000 + + #61000000 + + #8A000000 + #8A000000 + + #DE000000 + + @color/md_grey_300 + @color/md_grey_100 + @color/md_white_1000 + @color/md_white_1000 + + + @color/md_grey_850 + + #1FFFFFFF + + #4DFFFFFF + + #B3FFFFFF + #B3FFFFFF + + #FFFFFFFF + + @color/md_black_1000 + @color/md_grey_900 + @color/md_grey_800 + @color/md_grey_800 + + + #FFEBEE + #FFCDD2 + #EF9A9A + #E57373 + #EF5350 + #F44336 + #E53935 + #D32F2F + #C62828 + #B71C1C + #FF8A80 + #FF5252 + #FF1744 + #D50000 + + + #FCE4EC + #F8BBD0 + #F48FB1 + #F06292 + #EC407A + #E91E63 + #D81B60 + #C2185B + #AD1457 + #880E4F + #FF80AB + #FF4081 + #F50057 + #C51162 + + + #F3E5F5 + #E1BEE7 + #CE93D8 + #BA68C8 + #AB47BC + #9C27B0 + #8E24AA + #7B1FA2 + #6A1B9A + #4A148C + #EA80FC + #E040FB + #D500F9 + #AA00FF + + + #EDE7F6 + #D1C4E9 + #B39DDB + #9575CD + #7E57C2 + #673AB7 + #5E35B1 + #512DA8 + #4527A0 + #311B92 + #B388FF + #7C4DFF + #651FFF + #6200EA + + + #E8EAF6 + #C5CAE9 + #9FA8DA + #7986CB + #5C6BC0 + #3F51B5 + #3949AB + #303F9F + #283593 + #1A237E + #8C9EFF + #536DFE + #3D5AFE + #304FFE + + + #E3F2FD + #BBDEFB + #90CAF9 + #64B5F6 + #42A5F5 + #2196F3 + #1E88E5 + #1976D2 + #1565C0 + #0D47A1 + #82B1FF + #448AFF + #2979FF + #2962FF + + + #E1F5FE + #B3E5FC + #81D4FA + #4FC3F7 + #29B6F6 + #03A9F4 + #039BE5 + #0288D1 + #0277BD + #01579B + #80D8FF + #40C4FF + #00B0FF + #0091EA + + + #E0F7FA + #B2EBF2 + #80DEEA + #4DD0E1 + #26C6DA + #00BCD4 + #00ACC1 + #0097A7 + #00838F + #006064 + #84FFFF + #18FFFF + #00E5FF + #00B8D4 + + + #E0F2F1 + #B2DFDB + #80CBC4 + #4DB6AC + #26A69A + #009688 + #00897B + #00796B + #00695C + #004D40 + #A7FFEB + #64FFDA + #1DE9B6 + #00BFA5 + + + #E8F5E9 + #C8E6C9 + #A5D6A7 + #81C784 + #66BB6A + #4CAF50 + #43A047 + #388E3C + #2E7D32 + #1B5E20 + #B9F6CA + #69F0AE + #00E676 + #00C853 + + + #F1F8E9 + #DCEDC8 + #C5E1A5 + #AED581 + #9CCC65 + #8BC34A + #7CB342 + #689F38 + #558B2F + #33691E + #CCFF90 + #B2FF59 + #76FF03 + #64DD17 + + + #F9FBE7 + #F0F4C3 + #E6EE9C + #DCE775 + #D4E157 + #CDDC39 + #C0CA33 + #AFB42B + #9E9D24 + #827717 + #F4FF81 + #EEFF41 + #C6FF00 + #AEEA00 + + + #FFFDE7 + #FFF9C4 + #FFF59D + #FFF176 + #FFEE58 + #FFEB3B + #FDD835 + #FBC02D + #F9A825 + #F57F17 + #FFFF8D + #FFFF00 + #FFEA00 + #FFD600 + + + #FFF8E1 + #FFECB3 + #FFE082 + #FFD54F + #FFCA28 + #FFC107 + #FFB300 + #FFA000 + #FF8F00 + #FF6F00 + #FFE57F + #FFD740 + #FFC400 + #FFAB00 + + + #FFF3E0 + #FFE0B2 + #FFCC80 + #FFB74D + #FFA726 + #FF9800 + #FB8C00 + #F57C00 + #EF6C00 + #E65100 + #FFD180 + #FFAB40 + #FF9100 + #FF6D00 + + + #FBE9E7 + #FFCCBC + #FFAB91 + #FF8A65 + #FF7043 + #FF5722 + #F4511E + #E64A19 + #D84315 + #BF360C + #FF9E80 + #FF6E40 + #FF3D00 + #DD2C00 + + + #EFEBE9 + #D7CCC8 + #BCAAA4 + #A1887F + #8D6E63 + #795548 + #6D4C41 + #5D4037 + #4E342E + #3E2723 + + + #FAFAFA + #F5F5F5 + #EEEEEE + #E0E0E0 + #BDBDBD + #9E9E9E + #757575 + #616161 + #424242 + #303030 + #212121 + + + #ECEFF1 + #CFD8DC + #B0BEC5 + #90A4AE + #78909C + #607D8B + #546E7A + #455A64 + #37474F + #263238 + + + #000000 + #FFFFFF + + From 5f2991373b0ef21cabd3d15a53c11700d93b9b86 Mon Sep 17 00:00:00 2001 From: atbest Date: Sun, 26 May 2019 00:06:45 -0400 Subject: [PATCH 5/5] Added importing book shelf from YueDu --- .../jsonpath/spi/json/GsonJsonProvider.java | 4 +- .../java/io/legado/app/data/dao/BookDao.kt | 11 +++ .../java/io/legado/app/data/entities/Book.kt | 4 +- .../io/legado/app/ui/main/MainActivity.kt | 92 +++++++++++++++---- .../app/ui/replacerule/ReplaceRuleActivity.kt | 14 ++- .../app/ui/replacerule/ReplaceRuleAdapter.kt | 14 +-- .../io/legado/app/utils/MiscExtensions.kt | 5 +- app/src/main/res/drawable/ic_divider.xml | 6 ++ .../main/res/layout/activity_replace_rule.xml | 4 +- app/src/main/res/layout/item_relace_rule.xml | 12 +-- 10 files changed, 128 insertions(+), 38 deletions(-) create mode 100644 app/src/main/res/drawable/ic_divider.xml diff --git a/app/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java b/app/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java index 0828b2951..ab0ffc9ae 100644 --- a/app/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java +++ b/app/src/main/java/com/jayway/jsonpath/spi/json/GsonJsonProvider.java @@ -247,8 +247,8 @@ public class GsonJsonProvider extends AbstractJsonProvider { } } - throw new JsonPathException("length operation can not applied to " + obj != null ? obj.getClass().getName() - : "null"); + throw new JsonPathException("length operation can not applied to " + (obj != null ? obj.getClass().getName() + : "null")); } @Override diff --git a/app/src/main/java/io/legado/app/data/dao/BookDao.kt b/app/src/main/java/io/legado/app/data/dao/BookDao.kt index aae85f47f..0ea2baac1 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookDao.kt @@ -3,6 +3,8 @@ package io.legado.app.data.dao import androidx.lifecycle.LiveData import androidx.paging.DataSource import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query import io.legado.app.data.entities.Book @@ -19,4 +21,13 @@ interface BookDao { @Query("SELECT * FROM books WHERE `name` in (:names)") fun findByName(vararg names: String): List + @get:Query("SELECT descUrl FROM books") + val allBookUrls: List + + @get:Query("SELECT COUNT(*) FROM books") + val allBookCount: Int + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg books: Book) + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index 1efd170c3..c883ff956 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -30,11 +30,13 @@ data class Book(@PrimaryKey var lastCheckTime: Long = 0, // 最近一次更新书籍信息的时间 var lastCheckCount: Int = 0, // 最近一次发现新章节的数量 var totalChapterNum: Int = 0, // 书籍目录总数 - var durChapterTitle: String? = null, // 当前章节名称 + var durChapterTitle: String? = null, // 当前章节名称 var durChapterIndex: Int = 0, // 当前章节索引 var durChapterPos: Int = 0, // 当前阅读的进度(首行字符的索引位置) var durChapterTime: Long = 0, // 最近一次阅读书籍的时间(打开正文的时间) var canUpdate: Boolean = true, // 刷新书架时更新书籍信息 + var order: Int = 0, // 手动排序 + var useReplaceRule: Boolean = true, // 正文使用净化替换规则 var variable: String? = null // 自定义书籍变量信息(用于书源规则检索书籍信息) ) : Parcelable { diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index f6fb83294..46cb0096b 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -16,6 +16,7 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseActivity import io.legado.app.constant.AppConst.APP_TAG +import io.legado.app.data.entities.Book import io.legado.app.data.entities.ReplaceRule import io.legado.app.help.permission.Permissions import io.legado.app.help.permission.PermissionsCompat @@ -107,30 +108,85 @@ class MainActivity : BaseActivity(), NavigationV .build() ) - // Replace rules - val rFile = File(yuedu, "myBookReplaceRule.json") - val replaceRules = mutableListOf() - if (rFile.exists()) try { - val items: List> = jsonPath.parse(rFile.readText()).read("$.*") - for (item in items) { - val jsonItem = jsonPath.parse(item) - val rRule = ReplaceRule() - rRule.name = jsonItem.readString("$.replaceSummary") - rRule.pattern = jsonItem.readString("$.regex") - rRule.replacement = jsonItem.readString("$.replacement") - rRule.isRegex = jsonItem.readBool("$.isRegex") - rRule.scope = jsonItem.readString("$.useTo") - rRule.isEnabled = jsonItem.readBool("$.enable") - rRule.order = jsonItem.readInt("$.serialNumber") - replaceRules.add(rRule) + // 导入书架 + val shelfFile = File(yuedu, "myBookShelf.json") + val books = mutableListOf() + if (shelfFile.exists()) try { + doAsync { + val items: List> = jsonPath.parse(shelfFile.readText()).read("$") + val existingBooks = App.db.bookDao().allBookUrls.toSet() + for (item in items) { + val jsonItem = jsonPath.parse(item) + val book = Book() + book.descUrl = jsonItem.readString("$.noteUrl") ?: "" + if (book.descUrl.isBlank()) continue + book.name = jsonItem.readString("$.bookInfoBean.name") + if (book.descUrl in existingBooks) { + Log.d(APP_TAG, "Found existing book: ${book.name}") + continue + } + book.author = jsonItem.readString("$.bookInfoBean.author") + book.type = if (jsonItem.readString("$.bookInfoBean.bookSourceType") == "AUDIO") 1 else 0 + book.tocUrl = jsonItem.readString("$.bookInfoBean.chapterUrl") ?: book.descUrl + book.coverUrl = jsonItem.readString("$.bookInfoBean.coverUrl") + book.customCoverUrl = jsonItem.readString("$.customCoverPath") + book.lastCheckTime = jsonItem.readLong("$.bookInfoBean.finalRefreshData") ?: 0 + book.canUpdate = jsonItem.readBool("$.allowUpdate") == true + book.totalChapterNum = jsonItem.readInt("$.chapterListSize") ?: 0 + book.durChapterIndex = jsonItem.readInt("$.durChapter") ?: 0 + book.durChapterTitle = jsonItem.readString("$.durChapterName") + book.durChapterPos = jsonItem.readInt("$.durChapterPage") ?: 0 + book.durChapterTime = jsonItem.readLong("$.finalDate") ?: 0 + book.group = jsonItem.readInt("$.group") ?: 0 + // book. = jsonItem.readString("$.hasUpdate") + // book. = jsonItem.readString("$.isLoading") + book.latestChapterTitle = jsonItem.readString("$.lastChapterName") + book.lastCheckCount = jsonItem.readInt("$.newChapters") ?: 0 + book.order = jsonItem.readInt("$.serialNumber") ?: 0 + book.useReplaceRule = jsonItem.readBool("$.useReplaceRule") == true + book.variable = jsonItem.readString("$.variable") + books.add(book) + Log.d(APP_TAG, "Added ${book.name}") + } + App.db.bookDao().insert(*books.toTypedArray()) + val count = books.size + + uiThread { + toast(if (count > 0) "成功地导入 $count 本新书和音频" else "没有发现新书或音频") + } + } + } catch (e: Exception) { + Log.e(APP_TAG, "Failed to import book shelf.", e) + toast("Unable to import books:\n${e.localizedMessage}") + } + + // Replace rules + val ruleFile = File(yuedu, "myBookReplaceRule.json") + val replaceRules = mutableListOf() + if (ruleFile.exists()) try { doAsync { + val items: List> = jsonPath.parse(ruleFile.readText()).read("$") + val existingRules = App.db.replaceRuleDao().all.map { it.pattern }.toSet() + for (item in items) { + val jsonItem = jsonPath.parse(item) + val rRule = ReplaceRule() + rRule.pattern = jsonItem.readString("$.regex") + if (rRule.pattern.isNullOrEmpty() || rRule.pattern in existingRules) continue + rRule.name = jsonItem.readString("$.replaceSummary") + rRule.replacement = jsonItem.readString("$.replacement") + rRule.isRegex = jsonItem.readBool("$.isRegex") == true + rRule.scope = jsonItem.readString("$.useTo") + rRule.isEnabled = jsonItem.readBool("$.enable") == true + rRule.order = jsonItem.readInt("$.serialNumber") ?: 0 + replaceRules.add(rRule) + } App.db.replaceRuleDao().insert(*replaceRules.toTypedArray()) - val count = items.size + val count = replaceRules.size val maxId = App.db.replaceRuleDao().maxOrder uiThread { - toast("成功地导入 $count 条净化替换规则") + toast(if (count > 0) "成功地导入 $count 条净化替换规则" else "没有发现新的净化替换规则") } } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt index 8f6077c96..5d0ebadc6 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -1,11 +1,14 @@ package io.legado.app.ui.replacerule import android.os.Bundle +import android.util.Log import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList +import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R @@ -15,6 +18,8 @@ import org.jetbrains.anko.doAsync import org.jetbrains.anko.toast import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.ItemTouchHelper +import io.legado.app.constant.AppConst.APP_TAG +import kotlinx.android.synthetic.main.item_relace_rule.* class ReplaceRuleActivity : AppCompatActivity() { @@ -25,7 +30,6 @@ class ReplaceRuleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_replace_rule) - rv_replace_rule.layoutManager = LinearLayoutManager(this) initRecyclerView() initDataObservers() initSwipeToDelete() @@ -57,6 +61,12 @@ class ReplaceRuleActivity : AppCompatActivity() { } } rv_replace_rule.adapter = adapter + rv_replace_rule.addItemDecoration( + DividerItemDecoration(this, DividerItemDecoration.VERTICAL).apply { + ContextCompat.getDrawable(baseContext, R.drawable.ic_divider)?.let { + Log.e(APP_TAG, it.toString()) + this.setDrawable(it) } + }) } private fun initDataObservers() { @@ -90,7 +100,7 @@ class ReplaceRuleActivity : AppCompatActivity() { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { toast("You swiped the item!") - TODO() + // TODO() // remove((viewHolder as TodoViewHolder).todo) } }).attachToRecyclerView(rv_replace_rule) diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt index a767cee52..d9befdd97 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt @@ -1,6 +1,7 @@ package io.legado.app.ui.replacerule import android.content.Context +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -9,6 +10,7 @@ import androidx.paging.PagedListAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import io.legado.app.R +import io.legado.app.constant.AppConst.APP_TAG import io.legado.app.data.entities.ReplaceRule import kotlinx.android.synthetic.main.item_relace_rule.view.* import org.jetbrains.anko.sdk27.listeners.onClick @@ -45,16 +47,16 @@ class ReplaceRuleAdapter(context: Context) : } override fun onBindViewHolder(holder: MyViewHolder, pos: Int) { - getItem(pos)?.let { holder.bind(it, onClickListener, pos == itemCount - 1) } + getItem(pos)?.let { holder.bind(it, onClickListener) } } class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { - fun bind(rule: ReplaceRule, listener: OnClickListener?, hideDivider: Boolean) = with(itemView) { + fun bind(rule: ReplaceRule, listener: OnClickListener?) = with(itemView) { tv_name.text = rule.name - swt_enabled.isChecked = rule.isEnabled - divider.isGone = hideDivider - iv_delete.isGone = true - iv_edit.isGone = true + swt_enabled.isChecked = rule.isEnabled + // divider.isGone = hideDivider + iv_delete.isGone = true + iv_edit.isGone = true // iv_delete.onClick { listener?.delete(rule) } // iv_edit.onClick { listener?.edit(rule) } swt_enabled.onClick { diff --git a/app/src/main/java/io/legado/app/utils/MiscExtensions.kt b/app/src/main/java/io/legado/app/utils/MiscExtensions.kt index 1f47d2af9..84665cd8c 100644 --- a/app/src/main/java/io/legado/app/utils/MiscExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/MiscExtensions.kt @@ -6,4 +6,7 @@ fun ReadContext.readString(path: String): String? = this.read(path, String::clas fun ReadContext.readBool(path: String): Boolean? = this.read(path, Boolean::class.java) -fun ReadContext.readInt(path: String): Int? = this.read(path, Int::class.java) \ No newline at end of file +fun ReadContext.readInt(path: String): Int? = this.read(path, Int::class.java) + +fun ReadContext.readLong(path: String): Long? = this.read(path, Long::class.java) + diff --git a/app/src/main/res/drawable/ic_divider.xml b/app/src/main/res/drawable/ic_divider.xml new file mode 100644 index 000000000..7515c3960 --- /dev/null +++ b/app/src/main/res/drawable/ic_divider.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/layout/activity_replace_rule.xml b/app/src/main/res/layout/activity_replace_rule.xml index d2d3d92e8..c16af5b11 100644 --- a/app/src/main/res/layout/activity_replace_rule.xml +++ b/app/src/main/res/layout/activity_replace_rule.xml @@ -18,8 +18,8 @@ android:id="@+id/rv_replace_rule" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingStart="8dp" - android:paddingEnd="8dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" android:layout_marginBottom="8dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/item_relace_rule.xml b/app/src/main/res/layout/item_relace_rule.xml index c739121b9..34e28b5c9 100644 --- a/app/src/main/res/layout/item_relace_rule.xml +++ b/app/src/main/res/layout/item_relace_rule.xml @@ -4,9 +4,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="8dp" - android:paddingEnd="8dp"> + android:layout_height="wrap_content"> - +