From 5cc7063136861ffe7d6ed59b365cf36823a0aba9 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 08:20:32 +0800 Subject: [PATCH 01/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 5410750cb..60e8e6217 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -1,9 +1,12 @@ ## 更新日志 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 +* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的开源阅读软件迁移过来 + +**2020/03/18** +* 尝试修复搜索时崩溃 **2020/03/16** -* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的开源阅读软件迁移过来 * 修复滚动模式切换章节位置不归0的bug * 修复文字选择更多菜单在部分手机上报错的bug * 修复文字选择菜单问题 From 0014f97e4898598268dce66b5cb451fcc5d4f77b Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 08:24:52 +0800 Subject: [PATCH 02/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 60e8e6217..28a759ced 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -5,6 +5,8 @@ **2020/03/18** * 尝试修复搜索时崩溃 +* 解决看过书籍的移到顶部需要向上滚动才能看到的bug +* 只有再书源被删除找不到书源时才会自动换源 **2020/03/16** * 修复滚动模式切换章节位置不归0的bug From 7961e10a9eaeabbc517cd009e463373c12ad3c77 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 08:32:42 +0800 Subject: [PATCH 03/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/help.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/assets/help.md b/app/src/main/assets/help.md index 145139a13..9f77645b6 100644 --- a/app/src/main/assets/help.md +++ b/app/src/main/assets/help.md @@ -1,3 +1,5 @@ +## 常见问题 + 1.为什么第一次安装好之后什么东西都没有? * 因为阅读只是一个转码工具,不提供内容,第一次安装app,需要自己手动导入书源,可以从QQ群、公众号“开源阅读软件”、酷安评论里获取由书友制作分享的书源。 From 3b33b12bd09800f29518b430a89776cf9d7c23d0 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 11:16:36 +0800 Subject: [PATCH 04/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/help/LauncherIconHelp.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt b/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt index 90c63e4b7..b859336e4 100644 --- a/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt +++ b/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt @@ -19,7 +19,8 @@ object LauncherIconHelp { ComponentName(App.INSTANCE, Launcher2::class.java.name), ComponentName(App.INSTANCE, Launcher3::class.java.name), ComponentName(App.INSTANCE, Launcher4::class.java.name), - ComponentName(App.INSTANCE, Launcher5::class.java.name) + ComponentName(App.INSTANCE, Launcher5::class.java.name), + ComponentName(App.INSTANCE, Launcher6::class.java.name) ) fun changeIcon(icon: String?) { From 93de67f4ff51d89a055c1f791bc0b9dbbc2bf7fd Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 13:56:57 +0800 Subject: [PATCH 05/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/service/help/ReadBook.kt | 7 ++- .../app/ui/book/read/ReadBookViewModel.kt | 12 ++-- app/src/main/res/drawable/ic_launcher1_b.xml | 52 +++++++++--------- app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 4369 -> 4287 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2871 -> 2895 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 6457 -> 6380 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 11550 -> 11381 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 17591 -> 17309 bytes 8 files changed, 38 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index 68b1601ac..50cadd655 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -33,7 +33,7 @@ object ReadBook { var curTextChapter: TextChapter? = null var nextTextChapter: TextChapter? = null var webBook: WebBook? = null - var msg: String? = null + private var msg: String? = null private val loadingChapters = arrayListOf() fun resetData(book: Book, noSource: (name: String, author: String) -> Unit) { @@ -64,6 +64,11 @@ object ReadBook { } } + fun upMsg(msg: String?) { + this.msg = msg + callBack?.upContent() + } + fun moveToNextPage() { durPageIndex++ callBack?.upContent() diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 90d559f8d..25d94bdea 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -117,15 +117,16 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { App.db.bookChapterDao().insert(*cList.toTypedArray()) App.db.bookDao().update(book) ReadBook.chapterSize = cList.size + ReadBook.upMsg(null) ReadBook.loadContent(resetPageOffset = true) } else { changeDruChapterIndex(cList) } } else { - toast(R.string.error_load_toc) + ReadBook.upMsg(context.getString(R.string.error_load_toc)) } }?.onError { - toast(R.string.error_load_toc) + ReadBook.upMsg(context.getString(R.string.error_load_toc)) } } } @@ -133,6 +134,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { fun changeTo(book1: Book) { execute { + ReadBook.upMsg(null) ReadBook.book?.let { App.db.bookDao().delete(it) } @@ -171,11 +173,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } } }.onStart { - ReadBook.msg = "正在自动换源" - ReadBook.callBack?.upContent() + ReadBook.upMsg("正在自动换源") }.onFinally { - ReadBook.msg = null - ReadBook.callBack?.upContent() + ReadBook.upMsg(null) } } diff --git a/app/src/main/res/drawable/ic_launcher1_b.xml b/app/src/main/res/drawable/ic_launcher1_b.xml index 559d07c34..c5b40eb91 100644 --- a/app/src/main/res/drawable/ic_launcher1_b.xml +++ b/app/src/main/res/drawable/ic_launcher1_b.xml @@ -7,49 +7,49 @@ - + - - - + + - + android:fillColor="#f2f2f2" /> + - - - - - - + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index 0fe58e361ba22b88bf786ead77d25e338defb1f5..d2c418293453ed5d4b603eb290aaa860340b949b 100644 GIT binary patch delta 4128 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 f)HblNGB5xDL9PWBJ!9*D00000NkvXXu0mjf8k{

!N0Wkmo3KU61K~!ko&6!2!t_*e5ykotfu7?|=XQoag^M=RF9Ntu?;yr$;;<$M=0a&%;`a zF^1C8QWA*-TI);z%$YNX*I$2qOmI>G%F|wuBunL_mVW};)z!tJLx)(fU;$NCRWvj- zuw==S^mz8!XZh^2 hP7`QuZlm*nm&&#`&)W_Ik@L3w$3Ap`;rc{VBE_j%@-XE=NIET>ML zV*dR3JpcUj?Af!2O`A5+(a|wR7ehlsX#)NI{cPE?1!D|KDLOhj@H~&|>S|tm@kKg1 zI*3N2D5VGl0tg{+949XZ`FWAdSpd)TC@n1|8h?#)@!~})Dk?a8_AF9LrcIlc_Dg}E zJ9jR|7+!hh70#SFLvL>{wY9ZGA`wbTO1N_63aeMIMhHQDeLZvM&gJd5-$rYlSCJ@8 z`ZtNpm@$Kc2M-bshpDZtrMtVEZQHi7diClt1dK5R0s%@&N@#3sq^_=x?(S|*oH#*A zNq-6F&Yh#Tx0lnWPjl?pF=ox0MR#|1-q9~;1u{YTzR&RRFsDzSCKL)$TwDymFTeaU z##abII2VYoouvAJ=vBE~^C- zu-4)@4)J)Lef##&)zw8!O${ecp5*%V>wg4;!L*;05+MXisWJN4y?Zx5{`ezbe)%Op zy7CMU52KVS2v5N&1kl&l$2;%5!;&RS_~MH%XlQ8Q{rBG|91iD+4HS$csx!?NeR`})vR2(l2fNnF-1j1baZqub?Q_gS!Pq6Ia}n7 zF*uHcwU+kwc1A`<*tKgHhYlU$=+UFZ8}jePjwhq$hr&e;_! zR?ybgc3Tc>t#KR&r4%hKEes3{;D7r*jg5`OVlfUMK1@?n6I-`#rJ|x@jAnjU0;vh5 zlyr7>GB7ZJQi^yy&d!}X`Rc2$`1s?GDJm*T`=z4)#v5-C33@gr+O>=G z=g;GL9>HJ`Ap{2w9N?2rKH<{Ei#V?0k%fQa--rK&kf5YQNciA`eNs~O94t7 zgtZ_jD24J~0i_g!gM%ogD1R?6r@p?P?(S}`UcH*G_m1Nb4u{jEeBWpG?Ai2PyGE@W zVDq#Rj8uq(Mrt2F5C91_rukcBfKjUo7G**HsYut<)bQ=M-_q37gb;$JrY2_1nnf@e zWbN9uG&D323C&ZCR8;W96HoBulTUK|_;ISLs%UI%+Vm@6JRWCsbbpjcB*O6UFoT1G^!N8O zG&F?P8l_aan~oX7lA=cP;Z=U z0-nq!5_{^i@B1jF*s^8Igy6wqqfw-=*<))h#!0SNuHxj}exGHc?1T}Lp@$yI z;VmE%#R{Fh{9B84gOeVaIJtmLMrN{=$b7-iGCMXJ0ijWLeEtY91|2HCMK<3R7!PKm z=%)lb{(rvwQ&M2#gE-d3pbkg87(;M9|R@^nzN{{DlbV;4WK1qI^1L zbQlr|ERZV^`7OZe^S3He3XC@*VOU|8RmN6O_ zAL+t!nhEdC?g*?}i<DqN2BQ0n7DWHoHeF@>e0m6$zk}IuHm1{+2;bGBUC00000NkvXX Hu0mjf1}q?% diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 58b1e389a51dc361eabc311380ce55c978a94276..4b44e0bba472de3710d678399ba5476e83389b04 100644 GIT binary patch delta 6265 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 d9nBx{{{cTh6cGRb diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index ea95882bdcc7753a5ea72df2db7b87abe6e1f65c..df9bfebb8386aa71764de2326d44e6f5b8795b77 100644 GIT binary patch literal 11381 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 literal 11550 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 ANdN!< diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index fe1be3fdea372481629d313f3ff34e4a52ae2e5c..6d101d80649eef54b39441843d6adbf9cf277178 100644 GIT binary patch literal 17309 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 From bfbbbee0106cc0672b52232da694b914fe2b8a1f Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 13:58:12 +0800 Subject: [PATCH 06/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/service/help/ReadBook.kt | 2 +- .../main/java/io/legado/app/ui/book/read/ReadBookActivity.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index 50cadd655..f18280d49 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -33,7 +33,7 @@ object ReadBook { var curTextChapter: TextChapter? = null var nextTextChapter: TextChapter? = null var webBook: WebBook? = null - private var msg: String? = null + var msg: String? = null private val loadingChapters = arrayListOf() fun resetData(book: Book, noSource: (name: String, author: String) -> Unit) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 8cd1915cc..90e7eade1 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -682,6 +682,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo mHandler.removeCallbacks(keepScreenRunnable) textActionMenu?.dismiss() page_view.onDestroy() + ReadBook.msg = null if (!BuildConfig.DEBUG) { SyncBookProgress.uploadBookProgress() Backup.autoBack(this) From 5ba70883b4aa29b8c1e84687b5f33f5697e625ec Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 13:59:35 +0800 Subject: [PATCH 07/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 90e7eade1..e81d7cad9 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -92,6 +92,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override val headerHeight: Int get() = page_view.curPage.headerHeight override fun onCreate(savedInstanceState: Bundle?) { + ReadBook.msg = null Help.setOrientation(this) super.onCreate(savedInstanceState) } From 84073b006927b73884f74b7c4832d2be073083cf Mon Sep 17 00:00:00 2001 From: yangyxd Date: Wed, 18 Mar 2020 14:31:04 +0800 Subject: [PATCH 08/36] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=B9=9D=E5=AE=AB?= =?UTF-8?q?=E6=A0=BC,=E6=90=9C=E7=B4=A2=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/search/SearchActivity.kt | 2 + .../io/legado/app/ui/widget/SearchView.java | 119 +++++++++++ .../io/legado/app/ui/widget/ShadowLayout.java | 195 ++++++++++++++++++ app/src/main/res/drawable/ic_help.xml | 27 ++- app/src/main/res/drawable/ic_search_hint.xml | 8 + app/src/main/res/layout/activity_main.xml | 2 +- .../main/res/layout/item_bookshelf_grid.xml | 90 ++++---- app/src/main/res/layout/view_search.xml | 11 +- app/src/main/res/values/attrs.xml | 13 ++ 9 files changed, 408 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/widget/SearchView.java create mode 100644 app/src/main/java/io/legado/app/ui/widget/ShadowLayout.java create mode 100644 app/src/main/res/drawable/ic_search_hint.xml diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt index 8de6a489c..4373dd41a 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt @@ -3,6 +3,7 @@ package io.legado.app.ui.book.search import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import androidx.appcompat.widget.SearchView @@ -128,6 +129,7 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se openOrCloseHistory(hasFocus) } } + openOrCloseHistory(true) } private fun initRecyclerView() { diff --git a/app/src/main/java/io/legado/app/ui/widget/SearchView.java b/app/src/main/java/io/legado/app/ui/widget/SearchView.java new file mode 100644 index 000000000..3ee0bd69b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/SearchView.java @@ -0,0 +1,119 @@ +package io.legado.app.ui.widget; + +import android.app.SearchableInfo; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ImageSpan; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.legado.app.R; + +public class SearchView extends androidx.appcompat.widget.SearchView { + private Drawable mSearchHintIcon = null; + private TextView textView = null; + + public SearchView(Context context) { + this(context, null); + } + + public SearchView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SearchView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + try { + if (textView == null) { + textView = (TextView) this.findViewById(androidx.appcompat.R.id.search_src_text); + mSearchHintIcon = this.getContext().getDrawable(R.drawable.ic_search_hint); + updateQueryHint(); + } + // 改变字体 + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + textView.setGravity(Gravity.CENTER_VERTICAL); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + private CharSequence getDecoratedHint(CharSequence hintText) { + // If the field is always expanded or we don't have a search hint icon, + // then don't add the search icon to the hint. + if (mSearchHintIcon == null) { + return hintText; + } + + final int textSize = (int) (textView.getTextSize() * 0.8); + mSearchHintIcon.setBounds(0, 0, textSize, textSize); + + final SpannableStringBuilder ssb = new SpannableStringBuilder(" "); + ssb.setSpan(new CenteredImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(hintText); + return ssb; + } + + private void updateQueryHint() { + if (textView != null) { + final CharSequence hint = getQueryHint(); + textView.setHint(getDecoratedHint(hint == null ? "" : hint)); + } + } + + @Override + public void setIconifiedByDefault(boolean iconified) { + super.setIconifiedByDefault(iconified); + updateQueryHint(); + } + + @Override + public void setSearchableInfo(SearchableInfo searchable) { + super.setSearchableInfo(searchable); + if (searchable != null) + updateQueryHint(); + } + + @Override + public void setQueryHint(@Nullable CharSequence hint) { + super.setQueryHint(hint); + updateQueryHint(); + } +} + +class CenteredImageSpan extends ImageSpan { + + public CenteredImageSpan(final Drawable drawable) { + super(drawable); + } + + @Override + public void draw(@NonNull Canvas canvas, CharSequence text, + int start, int end, float x, + int top, int y, int bottom, @NonNull Paint paint) { + // image to draw + Drawable b = getDrawable(); + // font metrics of text to be replaced + Paint.FontMetricsInt fm = paint.getFontMetricsInt(); + int transY = (y + fm.descent + y + fm.ascent) / 2 + - b.getBounds().bottom / 2; + + canvas.save(); + canvas.translate(x, transY); + b.draw(canvas); + canvas.restore(); + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.java b/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.java new file mode 100644 index 000000000..4ef6ffd2e --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.java @@ -0,0 +1,195 @@ +package io.legado.app.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.View; +import android.widget.RelativeLayout; +import io.legado.app.R; + +/** + * ShadowLayout.java + *

+ * Created by lijiankun on 17/8/11. + */ + +public class ShadowLayout extends RelativeLayout { + + public static final int ALL = 0x1111; + + public static final int LEFT = 0x0001; + + public static final int TOP = 0x0010; + + public static final int RIGHT = 0x0100; + + public static final int BOTTOM = 0x1000; + + public static final int SHAPE_RECTANGLE = 0x0001; + + public static final int SHAPE_OVAL = 0x0010; + + private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private RectF mRectF = new RectF(); + + /** + * 阴影的颜色 + */ + private int mShadowColor = Color.TRANSPARENT; + + /** + * 阴影的大小范围 + */ + private float mShadowRadius = 0; + + /** + * 阴影 x 轴的偏移量 + */ + private float mShadowDx = 0; + + /** + * 阴影 y 轴的偏移量 + */ + private float mShadowDy = 0; + + /** + * 阴影显示的边界 + */ + private int mShadowSide = ALL; + + /** + * 阴影的形状,圆形/矩形 + */ + private int mShadowShape = SHAPE_RECTANGLE; + + public ShadowLayout(Context context) { + this(context, null); + } + + public ShadowLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + float effect = mShadowRadius + dip2px(5); + float rectLeft = 0; + float rectTop = 0; + float rectRight = this.getMeasuredWidth(); + float rectBottom = this.getMeasuredHeight(); + int paddingLeft = 0; + int paddingTop = 0; + int paddingRight = 0; + int paddingBottom = 0; + this.getWidth(); + if ((mShadowSide & LEFT) == LEFT) { + rectLeft = effect; + paddingLeft = (int) effect; + } + if ((mShadowSide & TOP) == TOP) { + rectTop = effect; + paddingTop = (int) effect; + } + if ((mShadowSide & RIGHT) == RIGHT) { + rectRight = this.getMeasuredWidth() - effect; + paddingRight = (int) effect; + } + if ((mShadowSide & BOTTOM) == BOTTOM) { + rectBottom = this.getMeasuredHeight() - effect; + paddingBottom = (int) effect; + } + if (mShadowDy != 0.0f) { + rectBottom = rectBottom - mShadowDy; + paddingBottom = paddingBottom + (int) mShadowDy; + } + if (mShadowDx != 0.0f) { + rectRight = rectRight - mShadowDx; + paddingRight = paddingRight + (int) mShadowDx; + } + mRectF.left = rectLeft; + mRectF.top = rectTop; + mRectF.right = rectRight; + mRectF.bottom = rectBottom; + this.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); + } + + /** + * 真正绘制阴影的方法 + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + setUpShadowPaint(); + if (mShadowShape == SHAPE_RECTANGLE) { + canvas.drawRect(mRectF, mPaint); + } else if (mShadowShape == SHAPE_OVAL) { + canvas.drawCircle(mRectF.centerX(), mRectF.centerY(), Math.min(mRectF.width(), mRectF.height()) / 2, mPaint); + } + } + + public void setShadowColor(int shadowColor) { + mShadowColor = shadowColor; + requestLayout(); + postInvalidate(); + } + + public void setShadowRadius(float shadowRadius) { + mShadowRadius = shadowRadius; + requestLayout(); + postInvalidate(); + } + + /** + * 读取设置的阴影的属性 + * + * @param attrs 从其中获取设置的值 + */ + private void init(AttributeSet attrs) { + setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 关闭硬件加速 + this.setWillNotDraw(false); // 调用此方法后,才会执行 onDraw(Canvas) 方法 + + TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ShadowLayout); + if (typedArray != null) { + mShadowColor = typedArray.getColor(R.styleable.ShadowLayout_shadowColor, + getContext().getResources().getColor(android.R.color.black)); + mShadowRadius = typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, dip2px(0)); + mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, dip2px(0)); + mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, dip2px(0)); + mShadowSide = typedArray.getInt(R.styleable.ShadowLayout_shadowSide, ALL); + mShadowShape = typedArray.getInt(R.styleable.ShadowLayout_shadowShape, SHAPE_RECTANGLE); + typedArray.recycle(); + } + setUpShadowPaint(); + } + + private void setUpShadowPaint() { + mPaint.reset(); + mPaint.setAntiAlias(true); + mPaint.setColor(Color.TRANSPARENT); + mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor); + } + + /** + * dip2px dp 值转 px 值 + * + * @param dpValue dp 值 + * @return px 值 + */ + private float dip2px(float dpValue) { + DisplayMetrics dm = getContext().getResources().getDisplayMetrics(); + float scale = dm.density; + return (dpValue * scale + 0.5F); + } +} diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml index 931a86bf6..d5ecd8235 100644 --- a/app/src/main/res/drawable/ic_help.xml +++ b/app/src/main/res/drawable/ic_help.xml @@ -1,15 +1,12 @@ - - - - - + + + + diff --git a/app/src/main/res/drawable/ic_search_hint.xml b/app/src/main/res/drawable/ic_search_hint.xml new file mode 100644 index 000000000..42de64c26 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_hint.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 77ed85415..701dea9b5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,7 +7,7 @@ + android:paddingTop="2dp" + android:paddingRight="10dp" + android:paddingLeft="10dp"> - + app:shadowColor="#16000000" + app:shadowShape="rectangle" + app:shadowDx="1dp" + app:shadowDy="3dp" + app:shadowRadius="4dp" + app:shadowSide="all"> - + + + + - - + android:layout_gravity="right" + android:layout_margin="5dp" + android:includeFontPadding="false" + app:layout_constraintTop_toTopOf="@+id/bg_cover" + app:layout_constraintRight_toRightOf="@+id/bg_cover" + tools:ignore="RtlHardcoded" /> - - + \ No newline at end of file diff --git a/app/src/main/res/layout/view_search.xml b/app/src/main/res/layout/view_search.xml index 9b1d48efd..b1809c534 100644 --- a/app/src/main/res/layout/view_search.xml +++ b/app/src/main/res/layout/view_search.xml @@ -1,15 +1,18 @@ - \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index d5a4d11e1..9e920088b 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -173,4 +173,17 @@ + + + + + + + + + + + + + \ No newline at end of file From a0463171c058b7d0d1d57efaf149d1e5ac971350 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 14:35:46 +0800 Subject: [PATCH 09/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 28a759ced..482907c7c 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -7,6 +7,7 @@ * 尝试修复搜索时崩溃 * 解决看过书籍的移到顶部需要向上滚动才能看到的bug * 只有再书源被删除找不到书源时才会自动换源 +* 美化界面by yangyxd **2020/03/16** * 修复滚动模式切换章节位置不归0的bug From 6f260e4f1df41a5b1d893d848557cc2be8ac0f4e Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 14:58:02 +0800 Subject: [PATCH 10/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/ui/widget/SearchView.java | 119 ----------- .../io/legado/app/ui/widget/SearchView.kt | 113 ++++++++++ .../io/legado/app/ui/widget/ShadowLayout.java | 195 ------------------ .../io/legado/app/ui/widget/ShadowLayout.kt | 185 +++++++++++++++++ 4 files changed, 298 insertions(+), 314 deletions(-) delete mode 100644 app/src/main/java/io/legado/app/ui/widget/SearchView.java create mode 100644 app/src/main/java/io/legado/app/ui/widget/SearchView.kt delete mode 100644 app/src/main/java/io/legado/app/ui/widget/ShadowLayout.java create mode 100644 app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt diff --git a/app/src/main/java/io/legado/app/ui/widget/SearchView.java b/app/src/main/java/io/legado/app/ui/widget/SearchView.java deleted file mode 100644 index 3ee0bd69b..000000000 --- a/app/src/main/java/io/legado/app/ui/widget/SearchView.java +++ /dev/null @@ -1,119 +0,0 @@ -package io.legado.app.ui.widget; - -import android.app.SearchableInfo; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.text.Spannable; -import android.text.SpannableStringBuilder; -import android.text.style.ImageSpan; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.Gravity; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import io.legado.app.R; - -public class SearchView extends androidx.appcompat.widget.SearchView { - private Drawable mSearchHintIcon = null; - private TextView textView = null; - - public SearchView(Context context) { - this(context, null); - } - - public SearchView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public SearchView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - try { - if (textView == null) { - textView = (TextView) this.findViewById(androidx.appcompat.R.id.search_src_text); - mSearchHintIcon = this.getContext().getDrawable(R.drawable.ic_search_hint); - updateQueryHint(); - } - // 改变字体 - textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); - textView.setGravity(Gravity.CENTER_VERTICAL); - } catch (Exception e) { - e.printStackTrace(); - } - } - - - private CharSequence getDecoratedHint(CharSequence hintText) { - // If the field is always expanded or we don't have a search hint icon, - // then don't add the search icon to the hint. - if (mSearchHintIcon == null) { - return hintText; - } - - final int textSize = (int) (textView.getTextSize() * 0.8); - mSearchHintIcon.setBounds(0, 0, textSize, textSize); - - final SpannableStringBuilder ssb = new SpannableStringBuilder(" "); - ssb.setSpan(new CenteredImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - ssb.append(hintText); - return ssb; - } - - private void updateQueryHint() { - if (textView != null) { - final CharSequence hint = getQueryHint(); - textView.setHint(getDecoratedHint(hint == null ? "" : hint)); - } - } - - @Override - public void setIconifiedByDefault(boolean iconified) { - super.setIconifiedByDefault(iconified); - updateQueryHint(); - } - - @Override - public void setSearchableInfo(SearchableInfo searchable) { - super.setSearchableInfo(searchable); - if (searchable != null) - updateQueryHint(); - } - - @Override - public void setQueryHint(@Nullable CharSequence hint) { - super.setQueryHint(hint); - updateQueryHint(); - } -} - -class CenteredImageSpan extends ImageSpan { - - public CenteredImageSpan(final Drawable drawable) { - super(drawable); - } - - @Override - public void draw(@NonNull Canvas canvas, CharSequence text, - int start, int end, float x, - int top, int y, int bottom, @NonNull Paint paint) { - // image to draw - Drawable b = getDrawable(); - // font metrics of text to be replaced - Paint.FontMetricsInt fm = paint.getFontMetricsInt(); - int transY = (y + fm.descent + y + fm.ascent) / 2 - - b.getBounds().bottom / 2; - - canvas.save(); - canvas.translate(x, transY); - b.draw(canvas); - canvas.restore(); - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt new file mode 100644 index 000000000..a1d8bd5f6 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt @@ -0,0 +1,113 @@ +package io.legado.app.ui.widget + +import android.app.SearchableInfo +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.drawable.Drawable +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.style.ImageSpan +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.widget.TextView +import androidx.appcompat.widget.SearchView +import io.legado.app.R + +class SearchView : SearchView { + private var mSearchHintIcon: Drawable? = null + private var textView: TextView? = null + + constructor( + context: Context?, + attrs: AttributeSet? = null + ) : super(context, attrs) + + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) + + override fun onLayout( + changed: Boolean, + left: Int, + top: Int, + right: Int, + bottom: Int + ) { + super.onLayout(changed, left, top, right, bottom) + try { + if (textView == null) { + textView = + findViewById(androidx.appcompat.R.id.search_src_text) as TextView + mSearchHintIcon = this.context.getDrawable(R.drawable.ic_search_hint) + updateQueryHint() + } + // 改变字体 + textView!!.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) + textView!!.gravity = Gravity.CENTER_VERTICAL + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun getDecoratedHint(hintText: CharSequence): CharSequence { + // If the field is always expanded or we don't have a search hint icon, + // then don't add the search icon to the hint. + if (mSearchHintIcon == null) { + return hintText + } + val textSize = (textView!!.textSize * 0.8).toInt() + mSearchHintIcon!!.setBounds(0, 0, textSize, textSize) + val ssb = SpannableStringBuilder(" ") + ssb.setSpan(CenteredImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + ssb.append(hintText) + return ssb + } + + private fun updateQueryHint() { + if (textView != null) { + val hint = queryHint + textView!!.hint = getDecoratedHint(hint ?: "") + } + } + + override fun setIconifiedByDefault(iconified: Boolean) { + super.setIconifiedByDefault(iconified) + updateQueryHint() + } + + override fun setSearchableInfo(searchable: SearchableInfo?) { + super.setSearchableInfo(searchable) + searchable?.let { + updateQueryHint() + } + } + + override fun setQueryHint(hint: CharSequence?) { + super.setQueryHint(hint) + updateQueryHint() + } +} + +internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) { + override fun draw( + canvas: Canvas, text: CharSequence, + start: Int, end: Int, x: Float, + top: Int, y: Int, bottom: Int, paint: Paint + ) { + // image to draw + val b = drawable + // font metrics of text to be replaced + val fm = paint.fontMetricsInt + val transY = ((y + fm.descent + y + fm.ascent) / 2 + - b.bounds.bottom / 2) + canvas.save() + canvas.translate(x, transY.toFloat()) + b.draw(canvas) + canvas.restore() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.java b/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.java deleted file mode 100644 index 4ef6ffd2e..000000000 --- a/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.java +++ /dev/null @@ -1,195 +0,0 @@ -package io.legado.app.ui.widget; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.View; -import android.widget.RelativeLayout; -import io.legado.app.R; - -/** - * ShadowLayout.java - *

- * Created by lijiankun on 17/8/11. - */ - -public class ShadowLayout extends RelativeLayout { - - public static final int ALL = 0x1111; - - public static final int LEFT = 0x0001; - - public static final int TOP = 0x0010; - - public static final int RIGHT = 0x0100; - - public static final int BOTTOM = 0x1000; - - public static final int SHAPE_RECTANGLE = 0x0001; - - public static final int SHAPE_OVAL = 0x0010; - - private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - - private RectF mRectF = new RectF(); - - /** - * 阴影的颜色 - */ - private int mShadowColor = Color.TRANSPARENT; - - /** - * 阴影的大小范围 - */ - private float mShadowRadius = 0; - - /** - * 阴影 x 轴的偏移量 - */ - private float mShadowDx = 0; - - /** - * 阴影 y 轴的偏移量 - */ - private float mShadowDy = 0; - - /** - * 阴影显示的边界 - */ - private int mShadowSide = ALL; - - /** - * 阴影的形状,圆形/矩形 - */ - private int mShadowShape = SHAPE_RECTANGLE; - - public ShadowLayout(Context context) { - this(context, null); - } - - public ShadowLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(attrs); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - float effect = mShadowRadius + dip2px(5); - float rectLeft = 0; - float rectTop = 0; - float rectRight = this.getMeasuredWidth(); - float rectBottom = this.getMeasuredHeight(); - int paddingLeft = 0; - int paddingTop = 0; - int paddingRight = 0; - int paddingBottom = 0; - this.getWidth(); - if ((mShadowSide & LEFT) == LEFT) { - rectLeft = effect; - paddingLeft = (int) effect; - } - if ((mShadowSide & TOP) == TOP) { - rectTop = effect; - paddingTop = (int) effect; - } - if ((mShadowSide & RIGHT) == RIGHT) { - rectRight = this.getMeasuredWidth() - effect; - paddingRight = (int) effect; - } - if ((mShadowSide & BOTTOM) == BOTTOM) { - rectBottom = this.getMeasuredHeight() - effect; - paddingBottom = (int) effect; - } - if (mShadowDy != 0.0f) { - rectBottom = rectBottom - mShadowDy; - paddingBottom = paddingBottom + (int) mShadowDy; - } - if (mShadowDx != 0.0f) { - rectRight = rectRight - mShadowDx; - paddingRight = paddingRight + (int) mShadowDx; - } - mRectF.left = rectLeft; - mRectF.top = rectTop; - mRectF.right = rectRight; - mRectF.bottom = rectBottom; - this.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom); - } - - /** - * 真正绘制阴影的方法 - */ - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - setUpShadowPaint(); - if (mShadowShape == SHAPE_RECTANGLE) { - canvas.drawRect(mRectF, mPaint); - } else if (mShadowShape == SHAPE_OVAL) { - canvas.drawCircle(mRectF.centerX(), mRectF.centerY(), Math.min(mRectF.width(), mRectF.height()) / 2, mPaint); - } - } - - public void setShadowColor(int shadowColor) { - mShadowColor = shadowColor; - requestLayout(); - postInvalidate(); - } - - public void setShadowRadius(float shadowRadius) { - mShadowRadius = shadowRadius; - requestLayout(); - postInvalidate(); - } - - /** - * 读取设置的阴影的属性 - * - * @param attrs 从其中获取设置的值 - */ - private void init(AttributeSet attrs) { - setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 关闭硬件加速 - this.setWillNotDraw(false); // 调用此方法后,才会执行 onDraw(Canvas) 方法 - - TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ShadowLayout); - if (typedArray != null) { - mShadowColor = typedArray.getColor(R.styleable.ShadowLayout_shadowColor, - getContext().getResources().getColor(android.R.color.black)); - mShadowRadius = typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, dip2px(0)); - mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, dip2px(0)); - mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, dip2px(0)); - mShadowSide = typedArray.getInt(R.styleable.ShadowLayout_shadowSide, ALL); - mShadowShape = typedArray.getInt(R.styleable.ShadowLayout_shadowShape, SHAPE_RECTANGLE); - typedArray.recycle(); - } - setUpShadowPaint(); - } - - private void setUpShadowPaint() { - mPaint.reset(); - mPaint.setAntiAlias(true); - mPaint.setColor(Color.TRANSPARENT); - mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor); - } - - /** - * dip2px dp 值转 px 值 - * - * @param dpValue dp 值 - * @return px 值 - */ - private float dip2px(float dpValue) { - DisplayMetrics dm = getContext().getResources().getDisplayMetrics(); - float scale = dm.density; - return (dpValue * scale + 0.5F); - } -} diff --git a/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt b/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt new file mode 100644 index 000000000..0f6ad3200 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt @@ -0,0 +1,185 @@ +package io.legado.app.ui.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import android.widget.RelativeLayout +import io.legado.app.R +import io.legado.app.utils.getCompatColor + +/** + * ShadowLayout.java + * + * + * Created by lijiankun on 17/8/11. + */ +class ShadowLayout( + context: Context, + attrs: AttributeSet? = null +) : RelativeLayout(context, attrs) { + private val mPaint = + Paint(Paint.ANTI_ALIAS_FLAG) + private val mRectF = RectF() + + /** + * 阴影的颜色 + */ + private var mShadowColor = Color.TRANSPARENT + + /** + * 阴影的大小范围 + */ + private var mShadowRadius = 0f + + /** + * 阴影 x 轴的偏移量 + */ + private var mShadowDx = 0f + + /** + * 阴影 y 轴的偏移量 + */ + private var mShadowDy = 0f + + /** + * 阴影显示的边界 + */ + private var mShadowSide = ALL + + /** + * 阴影的形状,圆形/矩形 + */ + private var mShadowShape = SHAPE_RECTANGLE + + + init { + setLayerType(View.LAYER_TYPE_SOFTWARE, null) // 关闭硬件加速 + setWillNotDraw(false) // 调用此方法后,才会执行 onDraw(Canvas) 方法 + val typedArray = + context.obtainStyledAttributes(attrs, R.styleable.ShadowLayout) + mShadowColor = typedArray.getColor( + R.styleable.ShadowLayout_shadowColor, + context.getCompatColor(android.R.color.black) + ) + mShadowRadius = + typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, dip2px(0f)) + mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, dip2px(0f)) + mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, dip2px(0f)) + mShadowSide = + typedArray.getInt(R.styleable.ShadowLayout_shadowSide, ALL) + mShadowShape = typedArray.getInt( + R.styleable.ShadowLayout_shadowShape, + SHAPE_RECTANGLE + ) + typedArray.recycle() + + setUpShadowPaint() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val effect = mShadowRadius + dip2px(5f) + var rectLeft = 0f + var rectTop = 0f + var rectRight = this.measuredWidth.toFloat() + var rectBottom = this.measuredHeight.toFloat() + var paddingLeft = 0 + var paddingTop = 0 + var paddingRight = 0 + var paddingBottom = 0 + this.width + if (mShadowSide and LEFT == LEFT) { + rectLeft = effect + paddingLeft = effect.toInt() + } + if (mShadowSide and TOP == TOP) { + rectTop = effect + paddingTop = effect.toInt() + } + if (mShadowSide and RIGHT == RIGHT) { + rectRight = this.measuredWidth - effect + paddingRight = effect.toInt() + } + if (mShadowSide and BOTTOM == BOTTOM) { + rectBottom = this.measuredHeight - effect + paddingBottom = effect.toInt() + } + if (mShadowDy != 0.0f) { + rectBottom -= mShadowDy + paddingBottom += mShadowDy.toInt() + } + if (mShadowDx != 0.0f) { + rectRight -= mShadowDx + paddingRight += mShadowDx.toInt() + } + mRectF.left = rectLeft + mRectF.top = rectTop + mRectF.right = rectRight + mRectF.bottom = rectBottom + setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom) + } + + /** + * 真正绘制阴影的方法 + */ + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + setUpShadowPaint() + if (mShadowShape == SHAPE_RECTANGLE) { + canvas.drawRect(mRectF, mPaint) + } else if (mShadowShape == SHAPE_OVAL) { + canvas.drawCircle( + mRectF.centerX(), + mRectF.centerY(), + mRectF.width().coerceAtMost(mRectF.height()) / 2, + mPaint + ) + } + } + + fun setShadowColor(shadowColor: Int) { + mShadowColor = shadowColor + requestLayout() + postInvalidate() + } + + fun setShadowRadius(shadowRadius: Float) { + mShadowRadius = shadowRadius + requestLayout() + postInvalidate() + } + + private fun setUpShadowPaint() { + mPaint.reset() + mPaint.isAntiAlias = true + mPaint.color = Color.TRANSPARENT + mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor) + } + + /** + * dip2px dp 值转 px 值 + * + * @param dpValue dp 值 + * @return px 值 + */ + private fun dip2px(dpValue: Float): Float { + val dm = context.resources.displayMetrics + val scale = dm.density + return dpValue * scale + 0.5f + } + + companion object { + const val ALL = 0x1111 + const val LEFT = 0x0001 + const val TOP = 0x0010 + const val RIGHT = 0x0100 + const val BOTTOM = 0x1000 + const val SHAPE_RECTANGLE = 0x0001 + const val SHAPE_OVAL = 0x0010 + } + +} \ No newline at end of file From 922d17d0261a9a9effc23e4a1b7b75c24739c323 Mon Sep 17 00:00:00 2001 From: yangyxd Date: Wed, 18 Mar 2020 16:19:22 +0800 Subject: [PATCH 11/36] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=97=B6cash=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/ui/book/search/DiffCallBack.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index 07a3b16c5..d0c2f2973 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -49,28 +49,29 @@ class DiffCallBack(private val oldItems: List, private val newItems: } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { - val oldItem = oldItems[oldItemPosition] - val newItem = newItems[newItemPosition] val payload = Bundle() - if (oldItem.name != newItem.name) { + val newItem: SearchBook? = if ((newItemPosition >= 0) && (newItemPosition < newItems.size)) newItems[newItemPosition] else null + val oldItem: SearchBook? = if ((oldItemPosition >= 0) && (oldItemPosition < oldItems.size)) oldItems[oldItemPosition] else null + if (newItem == null) return payload + if (oldItem?.name != newItem.name) { payload.putString("name", newItem.name) } - if (oldItem.author != newItem.author) { + if (oldItem?.author != newItem.author) { payload.putString("author", newItem.author) } - if (oldItem.origins.size != newItem.origins.size) { + if (oldItem?.origins?.size != newItem.origins.size) { payload.putInt("origins", newItem.origins.size) } - if (oldItem.coverUrl != newItem.coverUrl) { + if (oldItem?.coverUrl != newItem.coverUrl) { payload.putString("cover", newItem.coverUrl) } - if (oldItem.kind != newItem.kind) { + if (oldItem?.kind != newItem.kind) { payload.putString("kind", newItem.kind) } - if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { + if (oldItem?.latestChapterTitle != newItem.latestChapterTitle) { payload.putString("last", newItem.latestChapterTitle) } - if (oldItem.intro != newItem.intro) { + if (oldItem?.intro != newItem.intro) { payload.putString("intro", newItem.intro) } return payload From c7b2e1c7b29066d84ea2317bd6f3e43659289341 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 17:40:25 +0800 Subject: [PATCH 12/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/legado/app/ui/book/search/DiffCallBack.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index d0c2f2973..999f2a941 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -50,8 +50,8 @@ class DiffCallBack(private val oldItems: List, private val newItems: override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val payload = Bundle() - val newItem: SearchBook? = if ((newItemPosition >= 0) && (newItemPosition < newItems.size)) newItems[newItemPosition] else null - val oldItem: SearchBook? = if ((oldItemPosition >= 0) && (oldItemPosition < oldItems.size)) oldItems[oldItemPosition] else null + val newItem = newItems.getOrNull(newItemPosition) + val oldItem = oldItems.getOrNull(oldItemPosition) if (newItem == null) return payload if (oldItem?.name != newItem.name) { payload.putString("name", newItem.name) From 41ff8c1c65a007f5d4e91e53d032d315b8362960 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 17:55:03 +0800 Subject: [PATCH 13/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/legado/app/ui/book/search/SearchActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt index 4373dd41a..e81545adc 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt @@ -3,7 +3,6 @@ package io.legado.app.ui.book.search import android.os.Bundle import android.view.Menu import android.view.MenuItem -import android.view.View import android.view.View.GONE import android.view.View.VISIBLE import androidx.appcompat.widget.SearchView @@ -113,6 +112,7 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se viewModel.saveSearchKey(query) viewModel.search(it) } + openOrCloseHistory(false) return true } From 97b644db0f82faa84204de03225f9831bce0fbe5 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 18:08:48 +0800 Subject: [PATCH 14/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/ui/widget/SearchView.kt | 36 +++++++++---------- app/src/main/res/layout/view_search.xml | 1 - 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt index a1d8bd5f6..13d2cbeaf 100644 --- a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt @@ -91,23 +91,23 @@ class SearchView : SearchView { super.setQueryHint(hint) updateQueryHint() } -} -internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) { - override fun draw( - canvas: Canvas, text: CharSequence, - start: Int, end: Int, x: Float, - top: Int, y: Int, bottom: Int, paint: Paint - ) { - // image to draw - val b = drawable - // font metrics of text to be replaced - val fm = paint.fontMetricsInt - val transY = ((y + fm.descent + y + fm.ascent) / 2 - - b.bounds.bottom / 2) - canvas.save() - canvas.translate(x, transY.toFloat()) - b.draw(canvas) - canvas.restore() + internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) { + override fun draw( + canvas: Canvas, text: CharSequence, + start: Int, end: Int, x: Float, + top: Int, y: Int, bottom: Int, paint: Paint + ) { + // image to draw + val b = drawable + // font metrics of text to be replaced + val fm = paint.fontMetricsInt + val transY = ((y + fm.descent + y + fm.ascent) / 2 + - b.bounds.bottom / 2) + canvas.save() + canvas.translate(x, transY.toFloat()) + b.draw(canvas) + canvas.restore() + } } -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/view_search.xml b/app/src/main/res/layout/view_search.xml index b1809c534..96ff311da 100644 --- a/app/src/main/res/layout/view_search.xml +++ b/app/src/main/res/layout/view_search.xml @@ -14,5 +14,4 @@ app:queryBackground="@null" app:submitBackground="@null" app:searchHintIcon="@drawable/ic_search_hint" - app:goIcon="@drawable/ic_search" app:defaultQueryHint="搜索"/> \ No newline at end of file From 1a699bb4b82a7125dfff08613320b3f92c6f67c4 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 18:13:31 +0800 Subject: [PATCH 15/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/ui/widget/SearchView.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt index 13d2cbeaf..765a0a580 100644 --- a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt @@ -11,7 +11,6 @@ import android.text.style.ImageSpan import android.util.AttributeSet import android.util.TypedValue import android.view.Gravity -import android.view.View import android.widget.TextView import androidx.appcompat.widget.SearchView import io.legado.app.R @@ -41,8 +40,7 @@ class SearchView : SearchView { super.onLayout(changed, left, top, right, bottom) try { if (textView == null) { - textView = - findViewById(androidx.appcompat.R.id.search_src_text) as TextView + textView = findViewById(androidx.appcompat.R.id.search_src_text) mSearchHintIcon = this.context.getDrawable(R.drawable.ic_search_hint) updateQueryHint() } @@ -69,9 +67,8 @@ class SearchView : SearchView { } private fun updateQueryHint() { - if (textView != null) { - val hint = queryHint - textView!!.hint = getDecoratedHint(hint ?: "") + textView?.let { + it.hint = getDecoratedHint(queryHint ?: "") } } From a67156b0cfe0def64310b2e2e298a96a24a97cbe Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 18:59:23 +0800 Subject: [PATCH 16/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/help/coroutine/Coroutine.kt | 39 +++++++++++++++---- .../book/changecover/ChangeCoverViewModel.kt | 5 ++- .../changesource/ChangeSourceViewModel.kt | 5 ++- .../app/ui/book/search/SearchViewModel.kt | 6 ++- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index e9fd0df86..c8ff66a8a 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -6,7 +6,7 @@ import kotlin.coroutines.CoroutineContext class Coroutine( - scope: CoroutineScope, + val scope: CoroutineScope, context: CoroutineContext = Dispatchers.IO, block: suspend CoroutineScope.() -> T ) { @@ -31,6 +31,7 @@ class Coroutine( private var success: Callback? = null private var error: Callback? = null private var finally: VoidCallback? = null + private var cancel: VoidCallback? = null private var timeMillis: Long? = null private var errorReturn: Result? = null @@ -45,7 +46,7 @@ class Coroutine( get() = job.isCompleted init { - this.job = executeInternal(scope, context, block) + this.job = executeInternal(context, block) } fun timeout(timeMillis: () -> Long): Coroutine { @@ -100,9 +101,28 @@ class Coroutine( return this@Coroutine } + fun onCancel( + context: CoroutineContext? = null, + block: suspend CoroutineScope.() -> Unit + ): Coroutine { + this.cancel = VoidCallback(context, block) + return this@Coroutine + } + //取消当前任务 fun cancel(cause: CancellationException? = null) { job.cancel(cause) + cancel?.let { + scope.plus(Dispatchers.Main).launch { + if (null == it.context) { + it.block.invoke(scope) + } else { + withContext(scope.coroutineContext.plus(it.context)) { + it.block.invoke(this) + } + } + } + } } fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle { @@ -110,7 +130,6 @@ class Coroutine( } private fun executeInternal( - scope: CoroutineScope, context: CoroutineContext, block: suspend CoroutineScope.() -> T ): Job { @@ -118,21 +137,27 @@ class Coroutine( try { start?.let { dispatchVoidCallback(this, it) } val value = executeBlock(scope, context, timeMillis ?: 0L, block) - success?.let { dispatchCallback(this, value, it) } + if (isActive) { + success?.let { dispatchCallback(this, value, it) } + } } catch (e: Throwable) { if (BuildConfig.DEBUG) { e.printStackTrace() } val consume: Boolean = errorReturn?.value?.let { value -> - success?.let { dispatchCallback(this, value, it) } + if (isActive) { + success?.let { dispatchCallback(this, value, it) } + } true } ?: false - if (!consume) { + if (!consume && isActive) { error?.let { dispatchCallback(this, e, it) } } } finally { - finally?.let { dispatchVoidCallback(this, it) } + if (isActive) { + finally?.let { dispatchVoidCallback(this, it) } + } } } } diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt index 627bacc09..bcd0e61d0 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt @@ -51,7 +51,6 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application fun search() { task = execute { - searchStateData.postValue(true) val bookSourceList = App.db.bookSourceDao().allEnabled for (item in bookSourceList) { //task取消时自动取消 by (scope = this@execute) @@ -72,6 +71,10 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application } } } + }.onStart { + searchStateData.postValue(true) + }.onCancel { + searchStateData.postValue(false) } task?.invokeOnCompletion { diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt index e0800c0a1..5b1c7e939 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt @@ -72,7 +72,6 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio fun search() { task = execute { - searchStateData.postValue(true) val bookSourceList = App.db.bookSourceDao().allEnabled for (item in bookSourceList) { //task取消时自动取消 by (scope = this@execute) @@ -95,6 +94,10 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } } } + }.onStart { + searchStateData.postValue(true) + }.onCancel { + searchStateData.postValue(false) } task?.invokeOnCompletion { diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index 47671626d..1e00157a2 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -43,7 +43,6 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { searchKey = key searchBooks.clear() } - isSearchLiveData.postValue(true) task = execute { val searchGroup = context.getPrefString("searchGroup") ?: "" val bookSourceList = if (searchGroup.isBlank()) { @@ -71,6 +70,11 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { } } } + }.onStart { + isSearchLiveData.postValue(true) + }.onCancel { + isSearchLiveData.postValue(false) + isLoading = false } task?.invokeOnCompletion { From 13ab8c35b8157a6d8404a8d5ed5bb825b7aaf728 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 19:17:09 +0800 Subject: [PATCH 17/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/ui/book/search/DiffCallBack.kt | 22 ++++++++++--------- .../app/ui/book/search/SearchViewModel.kt | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index 999f2a941..6f14699a9 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -49,31 +49,33 @@ class DiffCallBack(private val oldItems: List, private val newItems: } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { + val oldItem = oldItems[oldItemPosition] + val newItem = newItems[newItemPosition] val payload = Bundle() - val newItem = newItems.getOrNull(newItemPosition) - val oldItem = oldItems.getOrNull(oldItemPosition) - if (newItem == null) return payload - if (oldItem?.name != newItem.name) { + if (oldItem.name != newItem.name) { payload.putString("name", newItem.name) } - if (oldItem?.author != newItem.author) { + if (oldItem.author != newItem.author) { payload.putString("author", newItem.author) } - if (oldItem?.origins?.size != newItem.origins.size) { + if (oldItem.origins.size != newItem.origins.size) { payload.putInt("origins", newItem.origins.size) } - if (oldItem?.coverUrl != newItem.coverUrl) { + if (oldItem.coverUrl != newItem.coverUrl) { payload.putString("cover", newItem.coverUrl) } - if (oldItem?.kind != newItem.kind) { + if (oldItem.kind != newItem.kind) { payload.putString("kind", newItem.kind) } - if (oldItem?.latestChapterTitle != newItem.latestChapterTitle) { + if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { payload.putString("last", newItem.latestChapterTitle) } - if (oldItem?.intro != newItem.intro) { + if (oldItem.intro != newItem.intro) { payload.putString("intro", newItem.intro) } + if (payload.isEmpty) { + return null + } return payload } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index 1e00157a2..191ceeafc 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -55,7 +55,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { WebBook(item).searchBook( searchKey, searchPage, - scope = this@execute, + scope = this, context = searchPool ) .timeout(30000L) From ca67f585d14eca497c3232062d6cc3c7c0ef9a55 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 19:50:24 +0800 Subject: [PATCH 18/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/help/coroutine/Coroutine.kt | 3 +- .../app/ui/book/search/SearchViewModel.kt | 34 ++++++++++++------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index c8ff66a8a..eb0a29375 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -113,7 +113,7 @@ class Coroutine( fun cancel(cause: CancellationException? = null) { job.cancel(cause) cancel?.let { - scope.plus(Dispatchers.Main).launch { + MainScope().launch { if (null == it.context) { it.block.invoke(scope) } else { @@ -177,6 +177,7 @@ class Coroutine( value: R, callback: Callback ) { + if (!scope.isActive) return if (null == callback.context) { callback.block.invoke(scope, value) } else { diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index 191ceeafc..f1a4fc858 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -12,8 +12,10 @@ import io.legado.app.help.coroutine.Coroutine import io.legado.app.model.WebBook import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefString +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.isActive import java.util.concurrent.Executors class SearchViewModel(application: Application) : BaseViewModel(application) { @@ -53,19 +55,21 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { for (item in bookSourceList) { //task取消时自动取消 by (scope = this@execute) WebBook(item).searchBook( - searchKey, - searchPage, + searchKey, + searchPage, scope = this, - context = searchPool - ) + context = searchPool + ) .timeout(30000L) .onSuccess(IO) { - it?.let { list -> - if (context.getPrefBoolean(PreferKey.precisionSearch)) { - precisionSearch(list) - } else { - App.db.searchBookDao().insert(*list.toTypedArray()) - mergeItems(list) + if (isActive) { + it?.let { list -> + if (context.getPrefBoolean(PreferKey.precisionSearch)) { + precisionSearch(this, list) + } else { + App.db.searchBookDao().insert(*list.toTypedArray()) + mergeItems(this, list) + } } } } @@ -86,7 +90,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { /** * 精确搜索处理 */ - private fun precisionSearch(searchBooks: List) { + private fun precisionSearch(scope: CoroutineScope, searchBooks: List) { val books = arrayListOf() searchBooks.forEach { searchBook -> if (searchBook.name.contains(searchKey, true) @@ -94,14 +98,16 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { ) books.add(searchBook) } App.db.searchBookDao().insert(*books.toTypedArray()) - mergeItems(books) + if (scope.isActive) { + mergeItems(scope, books) + } } /** * 合并搜索结果并排序 */ @Synchronized - private fun mergeItems(newDataS: List) { + private fun mergeItems(scope: CoroutineScope, newDataS: List) { if (newDataS.isNotEmpty()) { val copyDataS = ArrayList(searchBooks) val searchBooksAdd = ArrayList() @@ -145,6 +151,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { } } } + if (!scope.isActive) return searchBooks.sortWith(Comparator { o1, o2 -> if (o1.name == searchKey && o2.name != searchKey) { 1 @@ -170,6 +177,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { 0 } }) + if (!scope.isActive) return searchBooks = copyDataS searchBookLiveData.postValue(copyDataS) } From bed5c2e34a156f1fdf027b15651b5ec06a5347a6 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 20:52:37 +0800 Subject: [PATCH 19/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 1 + .../java/io/legado/app/model/SearchBook.kt | 5 + .../main/java/io/legado/app/model/WebBook.kt | 5 +- .../io/legado/app/model/webBook/BookList.kt | 32 ++++++- .../app/ui/book/read/ReadBookViewModel.kt | 2 +- .../legado/app/ui/book/search/DiffCallBack.kt | 22 ++--- .../app/ui/book/search/SearchViewModel.kt | 93 ++++++++++--------- .../legado/app/ui/rss/read/VisibleWebView.kt | 17 ++++ app/src/main/res/layout/activity_rss_read.xml | 2 +- 9 files changed, 116 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/io/legado/app/model/SearchBook.kt create mode 100644 app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 482907c7c..d6a435788 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -8,6 +8,7 @@ * 解决看过书籍的移到顶部需要向上滚动才能看到的bug * 只有再书源被删除找不到书源时才会自动换源 * 美化界面by yangyxd +* 订阅后台播放 **2020/03/16** * 修复滚动模式切换章节位置不归0的bug diff --git a/app/src/main/java/io/legado/app/model/SearchBook.kt b/app/src/main/java/io/legado/app/model/SearchBook.kt new file mode 100644 index 000000000..7b344e60f --- /dev/null +++ b/app/src/main/java/io/legado/app/model/SearchBook.kt @@ -0,0 +1,5 @@ +package io.legado.app.model + +class SearchBook { + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 7d14ea8bc..6fee7add6 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -29,11 +29,12 @@ class WebBook(val bookSource: BookSource) { context: CoroutineContext = Dispatchers.IO ): Coroutine> { return Coroutine.async(scope, context) { - searchBookSuspend(key, page) + searchBookSuspend(scope, key, page) } } suspend fun searchBookSuspend( + scope: CoroutineScope, key: String, page: Int? = 1 ): ArrayList { @@ -47,6 +48,7 @@ class WebBook(val bookSource: BookSource) { ) val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) return BookList.analyzeBookList( + scope, res.body, bookSource, analyzeUrl, @@ -75,6 +77,7 @@ class WebBook(val bookSource: BookSource) { ) val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) BookList.analyzeBookList( + scope, res.body, bookSource, analyzeUrl, diff --git a/app/src/main/java/io/legado/app/model/webBook/BookList.kt b/app/src/main/java/io/legado/app/model/webBook/BookList.kt index a205c950a..372a4eec6 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookList.kt @@ -9,11 +9,15 @@ import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.NetworkUtils +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.isActive object BookList { @Throws(Exception::class) fun analyzeBookList( + scope: CoroutineScope, body: String?, bookSource: BookSource, analyzeUrl: AnalyzeUrl, @@ -28,12 +32,13 @@ object BookList { ) ) Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}") + if (!scope.isActive) throw CancellationException() val analyzeRule = AnalyzeRule(null) analyzeRule.setContent(body, baseUrl) bookSource.bookUrlPattern?.let { if (baseUrl.matches(it.toRegex())) { Debug.log(bookSource.bookSourceUrl, "≡链接为详情页") - getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook -> + getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook -> searchBook.infoHtml = body bookList.add(searchBook) } @@ -59,7 +64,7 @@ object BookList { collections = analyzeRule.getElements(ruleList) if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) { Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析") - getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook -> + getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook -> searchBook.infoHtml = body bookList.add(searchBook) } @@ -74,8 +79,9 @@ object BookList { val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount) Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}") for ((index, item) in collections.withIndex()) { + if (!scope.isActive) throw CancellationException() getSearchItem( - item, analyzeRule, bookSource, baseUrl, index == 0, + scope, item, analyzeRule, bookSource, baseUrl, index == 0, ruleName = ruleName, ruleBookUrl = ruleBookUrl, ruleAuthor = ruleAuthor, ruleCoverUrl = ruleCoverUrl, ruleIntro = ruleIntro, ruleKind = ruleKind, ruleLastChapter = ruleLastChapter, ruleWordCount = ruleWordCount @@ -93,7 +99,9 @@ object BookList { return bookList } + @Throws(Exception::class) private fun getInfoItem( + scope: CoroutineScope, analyzeRule: AnalyzeRule, bookSource: BookSource, baseUrl: String @@ -108,29 +116,37 @@ object BookList { with(bookSource.getBookInfoRule()) { init?.let { if (it.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则") analyzeRule.setContent(analyzeRule.getElement(it)) } } + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取书名") searchBook.name = analyzeRule.getString(name) Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}") if (searchBook.name.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取作者") searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(author)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取分类") searchBook.kind = analyzeRule.getStringList(kind)?.joinToString(",") Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取字数") searchBook.wordCount = analyzeRule.getString(wordCount) Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取最新章节") searchBook.latestChapterTitle = analyzeRule.getString(lastChapter) Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取简介") searchBook.intro = analyzeRule.getString(intro) Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", true) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") searchBook.coverUrl = analyzeRule.getString(coverUrl, true) Debug.log(bookSource.bookSourceUrl, "└${searchBook.coverUrl}") @@ -140,7 +156,9 @@ object BookList { return null } + @Throws(Exception::class) private fun getSearchItem( + scope: CoroutineScope, item: Any, analyzeRule: AnalyzeRule, bookSource: BookSource, @@ -162,30 +180,38 @@ object BookList { searchBook.originOrder = bookSource.customOrder analyzeRule.book = searchBook analyzeRule.setContent(item) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取书名", log) searchBook.name = analyzeRule.getString(ruleName) Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}", log) if (searchBook.name.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取作者", log) searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(ruleAuthor)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取分类", log) searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",") Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取字数", log) searchBook.wordCount = analyzeRule.getString(ruleWordCount) Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log) searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter) Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取简介", log) searchBook.intro = analyzeRule.getString(ruleIntro) Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", log, true) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log) analyzeRule.getString(ruleCoverUrl).let { if (it.isNotEmpty()) searchBook.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it) } Debug.log(bookSource.bookSourceUrl, "└${searchBook.coverUrl}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log) searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, true) if (searchBook.bookUrl.isEmpty()) { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 25d94bdea..2994dcac2 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -161,7 +161,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { execute { App.db.bookSourceDao().allTextEnabled.forEach { source -> try { - val searchBooks = WebBook(source).searchBookSuspend(name) + val searchBooks = WebBook(source).searchBookSuspend(this, name) searchBooks.getOrNull(0)?.let { if (it.name == name && (it.author == author || author == "")) { changeTo(it.toBook()) diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index 6f14699a9..999f2a941 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -49,33 +49,31 @@ class DiffCallBack(private val oldItems: List, private val newItems: } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { - val oldItem = oldItems[oldItemPosition] - val newItem = newItems[newItemPosition] val payload = Bundle() - if (oldItem.name != newItem.name) { + val newItem = newItems.getOrNull(newItemPosition) + val oldItem = oldItems.getOrNull(oldItemPosition) + if (newItem == null) return payload + if (oldItem?.name != newItem.name) { payload.putString("name", newItem.name) } - if (oldItem.author != newItem.author) { + if (oldItem?.author != newItem.author) { payload.putString("author", newItem.author) } - if (oldItem.origins.size != newItem.origins.size) { + if (oldItem?.origins?.size != newItem.origins.size) { payload.putInt("origins", newItem.origins.size) } - if (oldItem.coverUrl != newItem.coverUrl) { + if (oldItem?.coverUrl != newItem.coverUrl) { payload.putString("cover", newItem.coverUrl) } - if (oldItem.kind != newItem.kind) { + if (oldItem?.kind != newItem.kind) { payload.putString("kind", newItem.kind) } - if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { + if (oldItem?.latestChapterTitle != newItem.latestChapterTitle) { payload.putString("last", newItem.latestChapterTitle) } - if (oldItem.intro != newItem.intro) { + if (oldItem?.intro != newItem.intro) { payload.putString("intro", newItem.intro) } - if (payload.isEmpty) { - return null - } return payload } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index f1a4fc858..6577c88fe 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import java.util.concurrent.Executors class SearchViewModel(application: Application) : BaseViewModel(application) { @@ -33,57 +34,59 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { * 开始搜索 */ fun search(key: String) { - task?.cancel() - if (key.isEmpty() && searchKey.isEmpty()) { - return - } else if (key.isEmpty()) { - isLoading = true - searchPage++ - } else if (key.isNotEmpty()) { - isLoading = true - searchPage = 1 - searchKey = key - searchBooks.clear() - } - task = execute { - val searchGroup = context.getPrefString("searchGroup") ?: "" - val bookSourceList = if (searchGroup.isBlank()) { - App.db.bookSourceDao().allEnabled - } else { - App.db.bookSourceDao().getEnabledByGroup(searchGroup) + launch { + task?.cancel() + if (key.isEmpty() && searchKey.isEmpty()) { + return@launch + } else if (key.isEmpty()) { + isLoading = true + searchPage++ + } else if (key.isNotEmpty()) { + isLoading = true + searchPage = 1 + searchKey = key + searchBooks.clear() } - for (item in bookSourceList) { - //task取消时自动取消 by (scope = this@execute) - WebBook(item).searchBook( - searchKey, - searchPage, - scope = this, - context = searchPool - ) - .timeout(30000L) - .onSuccess(IO) { - if (isActive) { - it?.let { list -> - if (context.getPrefBoolean(PreferKey.precisionSearch)) { - precisionSearch(this, list) - } else { - App.db.searchBookDao().insert(*list.toTypedArray()) - mergeItems(this, list) + task = execute { + val searchGroup = context.getPrefString("searchGroup") ?: "" + val bookSourceList = if (searchGroup.isBlank()) { + App.db.bookSourceDao().allEnabled + } else { + App.db.bookSourceDao().getEnabledByGroup(searchGroup) + } + for (item in bookSourceList) { + //task取消时自动取消 by (scope = this@execute) + WebBook(item).searchBook( + searchKey, + searchPage, + scope = this, + context = searchPool + ) + .timeout(30000L) + .onSuccess(IO) { + if (isActive) { + it?.let { list -> + if (context.getPrefBoolean(PreferKey.precisionSearch)) { + precisionSearch(this, list) + } else { + App.db.searchBookDao().insert(*list.toTypedArray()) + mergeItems(this, list) + } } } } - } + } + }.onStart { + isSearchLiveData.postValue(true) + }.onCancel { + isSearchLiveData.postValue(false) + isLoading = false } - }.onStart { - isSearchLiveData.postValue(true) - }.onCancel { - isSearchLiveData.postValue(false) - isLoading = false - } - task?.invokeOnCompletion { - isSearchLiveData.postValue(false) - isLoading = false + task?.invokeOnCompletion { + isSearchLiveData.postValue(false) + isLoading = false + } } } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt new file mode 100644 index 000000000..0e6e1b40b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt @@ -0,0 +1,17 @@ +package io.legado.app.ui.rss.read + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.webkit.WebView + +class VisibleWebView( + context: Context, + attrs: AttributeSet? = null +) : WebView(context, attrs) { + + override fun onWindowVisibilityChanged(visibility: Int) { + super.onWindowVisibilityChanged(View.VISIBLE) + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_rss_read.xml b/app/src/main/res/layout/activity_rss_read.xml index 76f630002..ea4131764 100644 --- a/app/src/main/res/layout/activity_rss_read.xml +++ b/app/src/main/res/layout/activity_rss_read.xml @@ -15,7 +15,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> - From 78a2f6ce2f55f7a10ac406650d5143910b62980b Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 20:56:09 +0800 Subject: [PATCH 20/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/book/search/SearchViewModel.kt | 93 +++++++++---------- 1 file changed, 45 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index 6577c88fe..f1a4fc858 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch import java.util.concurrent.Executors class SearchViewModel(application: Application) : BaseViewModel(application) { @@ -34,59 +33,57 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { * 开始搜索 */ fun search(key: String) { - launch { - task?.cancel() - if (key.isEmpty() && searchKey.isEmpty()) { - return@launch - } else if (key.isEmpty()) { - isLoading = true - searchPage++ - } else if (key.isNotEmpty()) { - isLoading = true - searchPage = 1 - searchKey = key - searchBooks.clear() + task?.cancel() + if (key.isEmpty() && searchKey.isEmpty()) { + return + } else if (key.isEmpty()) { + isLoading = true + searchPage++ + } else if (key.isNotEmpty()) { + isLoading = true + searchPage = 1 + searchKey = key + searchBooks.clear() + } + task = execute { + val searchGroup = context.getPrefString("searchGroup") ?: "" + val bookSourceList = if (searchGroup.isBlank()) { + App.db.bookSourceDao().allEnabled + } else { + App.db.bookSourceDao().getEnabledByGroup(searchGroup) } - task = execute { - val searchGroup = context.getPrefString("searchGroup") ?: "" - val bookSourceList = if (searchGroup.isBlank()) { - App.db.bookSourceDao().allEnabled - } else { - App.db.bookSourceDao().getEnabledByGroup(searchGroup) - } - for (item in bookSourceList) { - //task取消时自动取消 by (scope = this@execute) - WebBook(item).searchBook( - searchKey, - searchPage, - scope = this, - context = searchPool - ) - .timeout(30000L) - .onSuccess(IO) { - if (isActive) { - it?.let { list -> - if (context.getPrefBoolean(PreferKey.precisionSearch)) { - precisionSearch(this, list) - } else { - App.db.searchBookDao().insert(*list.toTypedArray()) - mergeItems(this, list) - } + for (item in bookSourceList) { + //task取消时自动取消 by (scope = this@execute) + WebBook(item).searchBook( + searchKey, + searchPage, + scope = this, + context = searchPool + ) + .timeout(30000L) + .onSuccess(IO) { + if (isActive) { + it?.let { list -> + if (context.getPrefBoolean(PreferKey.precisionSearch)) { + precisionSearch(this, list) + } else { + App.db.searchBookDao().insert(*list.toTypedArray()) + mergeItems(this, list) } } } - } - }.onStart { - isSearchLiveData.postValue(true) - }.onCancel { - isSearchLiveData.postValue(false) - isLoading = false + } } + }.onStart { + isSearchLiveData.postValue(true) + }.onCancel { + isSearchLiveData.postValue(false) + isLoading = false + } - task?.invokeOnCompletion { - isSearchLiveData.postValue(false) - isLoading = false - } + task?.invokeOnCompletion { + isSearchLiveData.postValue(false) + isLoading = false } } From 8395ad61f669f04fef8ce05a577ced9e80482bf2 Mon Sep 17 00:00:00 2001 From: kunfei Date: Wed, 18 Mar 2020 21:10:04 +0800 Subject: [PATCH 21/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt index faf9cef8a..ccfa5bf82 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt @@ -24,7 +24,7 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick class ExploreAdapter(context: Context, private val scope: CoroutineScope, val callBack: CallBack) : SimpleRecyclerAdapter(context, R.layout.item_find_book) { - private var exIndex = 0 + private var exIndex = -1 private var scrollTo = -1 override fun convert(holder: ItemViewHolder, item: BookSource, payloads: MutableList) { From cc41f5130e47ce12436a2b0220928ea655fc2e73 Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 14:08:52 +0800 Subject: [PATCH 22/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/help/storage/Backup.kt | 57 ++++++++++--------- .../io/legado/app/help/storage/OldBook.kt | 1 - 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index 8c4fa8fc2..32bc2b765 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -54,35 +54,37 @@ object Backup { suspend fun backup(context: Context, path: String = legadoPath) { context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis()) withContext(IO) { - writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath) - writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath) - writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath) - writeListToJson(App.db.rssSourceDao().all, "rssSource.json", backupPath) - writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath) - writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath) - GSON.toJson(ReadBookConfig.configList)?.let { - FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName) - .writeText(it) - } - Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp -> - val edit = sp.edit() - App.INSTANCE.defaultSharedPreferences.all.map { - when (val value = it.value) { - is Int -> edit.putInt(it.key, value) - is Boolean -> edit.putBoolean(it.key, value) - is Long -> edit.putLong(it.key, value) - is Float -> edit.putFloat(it.key, value) - is String -> edit.putString(it.key, value) - else -> Unit + synchronized(this@Backup) { + writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath) + writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath) + writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath) + writeListToJson(App.db.rssSourceDao().all, "rssSource.json", backupPath) + writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath) + writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath) + GSON.toJson(ReadBookConfig.configList)?.let { + FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName) + .writeText(it) + } + Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp -> + val edit = sp.edit() + App.INSTANCE.defaultSharedPreferences.all.map { + when (val value = it.value) { + is Int -> edit.putInt(it.key, value) + is Boolean -> edit.putBoolean(it.key, value) + is Long -> edit.putLong(it.key, value) + is Float -> edit.putFloat(it.key, value) + is String -> edit.putString(it.key, value) + else -> Unit + } } + edit.commit() + } + WebDavHelp.backUpWebDav(backupPath) + if (path.isContentPath()) { + copyBackup(context, Uri.parse(path)) + } else { + copyBackup(File(path)) } - edit.commit() - } - WebDavHelp.backUpWebDav(backupPath) - if (path.isContentPath()) { - copyBackup(context, Uri.parse(path)) - } else { - copyBackup(File(path)) } } } @@ -96,6 +98,7 @@ object Backup { @Throws(java.lang.Exception::class) private fun copyBackup(context: Context, uri: Uri) { + DocumentFile.fromTreeUri(context, uri)?.let { treeDoc -> for (fileName in backupFileNames) { val file = File(backupPath + File.separator + fileName) diff --git a/app/src/main/java/io/legado/app/help/storage/OldBook.kt b/app/src/main/java/io/legado/app/help/storage/OldBook.kt index f8321973c..a782cf5f5 100644 --- a/app/src/main/java/io/legado/app/help/storage/OldBook.kt +++ b/app/src/main/java/io/legado/app/help/storage/OldBook.kt @@ -40,7 +40,6 @@ object OldBook { book.durChapterTitle = jsonItem.readString("$.durChapterName") book.durChapterPos = jsonItem.readInt("$.durChapterPage") ?: 0 book.durChapterTime = jsonItem.readLong("$.finalDate") ?: 0 - book.group = jsonItem.readInt("$.group") ?: 0 book.intro = jsonItem.readString("$.bookInfoBean.introduce") book.latestChapterTitle = jsonItem.readString("$.lastChapterName") book.lastCheckCount = jsonItem.readInt("$.newChapters") ?: 0 From b662e2a087b4b3fef5482fb7bb386ba95f7b01eb Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 15:45:58 +0800 Subject: [PATCH 23/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/data/entities/Book.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9e50a6d4c..0dd5cab9e 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 @@ -32,7 +32,7 @@ data class Book( var customIntro: String? = null, // 简介内容(用户修改) var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍) var type: Int = 0, // 0:text 1:audio - var group: Int = 1, // 自定义分组索引号 + var group: Int = 0, // 自定义分组索引号 var latestChapterTitle: String? = null, // 最新章节标题 var latestChapterTime: Long = System.currentTimeMillis(), // 最新章节标题更新时间 var lastCheckTime: Long = System.currentTimeMillis(), // 最近一次更新书籍信息的时间 From 201fbe843e6c23696acd610e64d34a650afd3952 Mon Sep 17 00:00:00 2001 From: yangyxd Date: Thu, 19 Mar 2020 17:37:26 +0800 Subject: [PATCH 24/36] =?UTF-8?q?=E8=B0=83=E6=95=B4"=E6=88=91=E7=9A=84"UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/widget/prefs/ColorPreference.kt | 19 +++- .../app/ui/widget/prefs/EditTextPreference.kt | 22 ++++ .../app/ui/widget/prefs/IconListPreference.kt | 11 +- .../app/ui/widget/prefs/NameListPreference.kt | 7 +- .../legado/app/ui/widget/prefs/Preference.kt | 101 ++++++++++++++++++ .../app/ui/widget/prefs/PreferenceCategory.kt | 29 ++++- .../app/ui/widget/prefs/SwitchPreference.kt | 12 ++- app/src/main/res/drawable/bg_prefs_color.xml | 10 ++ app/src/main/res/drawable/ic_cfg_about.xml | 15 +++ app/src/main/res/drawable/ic_cfg_backup.xml | 9 ++ app/src/main/res/drawable/ic_cfg_e_lnk.xml | 5 + app/src/main/res/drawable/ic_cfg_jz.xml | 12 +++ app/src/main/res/drawable/ic_cfg_other.xml | 12 +++ app/src/main/res/drawable/ic_cfg_replace.xml | 12 +++ app/src/main/res/drawable/ic_cfg_source.xml | 12 +++ app/src/main/res/drawable/ic_cfg_theme.xml | 9 ++ app/src/main/res/drawable/ic_cfg_web.xml | 9 ++ app/src/main/res/layout/view_preference.xml | 67 ++++++++++++ .../res/layout/view_preference_category.xml | 29 +++++ app/src/main/res/layout/view_preference_s.xml | 66 ++++++++++++ app/src/main/res/values-night/colors.xml | 1 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/xml/about.xml | 30 ++++-- app/src/main/res/xml/donate.xml | 25 +++-- app/src/main/res/xml/pref_config_backup.xml | 24 +++-- app/src/main/res/xml/pref_config_other.xml | 8 ++ app/src/main/res/xml/pref_config_theme.xml | 14 ++- app/src/main/res/xml/pref_main.xml | 44 ++++++-- 29 files changed, 569 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt create mode 100644 app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt create mode 100644 app/src/main/res/drawable/bg_prefs_color.xml create mode 100644 app/src/main/res/drawable/ic_cfg_about.xml create mode 100644 app/src/main/res/drawable/ic_cfg_backup.xml create mode 100644 app/src/main/res/drawable/ic_cfg_e_lnk.xml create mode 100644 app/src/main/res/drawable/ic_cfg_jz.xml create mode 100644 app/src/main/res/drawable/ic_cfg_other.xml create mode 100644 app/src/main/res/drawable/ic_cfg_replace.xml create mode 100644 app/src/main/res/drawable/ic_cfg_source.xml create mode 100644 app/src/main/res/drawable/ic_cfg_theme.xml create mode 100644 app/src/main/res/drawable/ic_cfg_web.xml create mode 100644 app/src/main/res/layout/view_preference.xml create mode 100644 app/src/main/res/layout/view_preference_category.xml create mode 100644 app/src/main/res/layout/view_preference_s.xml diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt index 6bea75a51..998e9c7c4 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt @@ -6,14 +6,24 @@ import android.content.res.TypedArray import android.graphics.Color import android.os.Bundle import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView import androidx.annotation.ColorInt import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.SwitchCompat +import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.jaredrummler.android.colorpicker.* import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.accentColor +import org.jetbrains.anko.layoutInflater +import org.jetbrains.anko.px2dip class ColorPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs), ColorPickerDialogListener { @@ -37,6 +47,8 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex init { isPersistent = true + layoutResource = io.legado.app.R.layout.view_preference_s + val a = context.obtainStyledAttributes(attrs, R.styleable.ColorPreference) showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true) @@ -110,8 +122,11 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex override fun onBindViewHolder(holder: PreferenceViewHolder) { super.onBindViewHolder(holder) - val preview = holder.itemView.findViewById(R.id.cpv_preference_preview_color_panel) as ColorPanelView - preview.color = color + val v = io.legado.app.ui.widget.prefs.Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, + io.legado.app.R.id.cpv_preference_preview_color_panel, 30, 30) + if (v is ColorPanelView) { + v?.color = color + } } override fun onSetInitialValue(defaultValue: Any?) { diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt new file mode 100644 index 000000000..b914afcf5 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt @@ -0,0 +1,22 @@ +package io.legado.app.ui.widget.prefs + +import android.content.Context +import android.util.AttributeSet +import android.widget.TextView +import androidx.preference.PreferenceViewHolder +import io.legado.app.R + +class EditTextPreference(context: Context, attrs: AttributeSet) : androidx.preference.EditTextPreference(context, attrs) { + + init { + // isPersistent = true + layoutResource = R.layout.view_preference + } + + override fun onBindViewHolder(holder: PreferenceViewHolder?) { + super.onBindViewHolder(holder) + val txt = if (text == null || text.isEmpty()) summary else text + val v = Preference.bindView(context, holder, icon, title, txt, null, null) + } + +} diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt index b295e5ad3..2e043b4c3 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt @@ -10,17 +10,22 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentActivity import androidx.preference.ListPreference import androidx.preference.PreferenceViewHolder import androidx.recyclerview.widget.LinearLayoutManager +import com.jaredrummler.android.colorpicker.ColorPanelView import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.lib.theme.accentColor import io.legado.app.utils.getCompatDrawable import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_icon_preference.view.* +import org.jetbrains.anko.layoutInflater import org.jetbrains.anko.sdk27.listeners.onClick @@ -29,6 +34,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference private val mEntryDrawables = arrayListOf() init { + layoutResource = io.legado.app.R.layout.view_preference widgetLayoutResource = R.layout.view_icon val a = context.theme.obtainStyledAttributes(attrs, R.styleable.IconListPreference, 0, 0) @@ -52,11 +58,12 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference override fun onBindViewHolder(holder: PreferenceViewHolder?) { super.onBindViewHolder(holder) - holder?.itemView?.findViewById(R.id.preview)?.let { + val v = Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, R.id.preview, 50, 50) + if (v is ImageView) { val selectedIndex = findIndexOfValue(value) if (selectedIndex >= 0) { val drawable = mEntryDrawables[selectedIndex] - it.setImageDrawable(drawable) + v?.setImageDrawable(drawable) } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt index d8b51e90f..f4c1ccac2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt @@ -11,12 +11,15 @@ import io.legado.app.R class NameListPreference(context: Context, attrs: AttributeSet) : ListPreference(context, attrs) { init { + layoutResource = R.layout.view_preference widgetLayoutResource = R.layout.item_fillet_text } override fun onBindViewHolder(holder: PreferenceViewHolder?) { super.onBindViewHolder(holder) - val textView = holder?.itemView?.findViewById(R.id.text_view) - textView?.text = entry + val v = Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, R.id.text_view) + if (v is TextView) { + v?.text = entry + } } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt new file mode 100644 index 000000000..30603c89d --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt @@ -0,0 +1,101 @@ +package io.legado.app.ui.widget.prefs + +import android.content.Context +import android.content.ContextWrapper +import android.content.res.TypedArray +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.media.Image +import android.os.Bundle +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.SwitchCompat +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.fragment.app.FragmentActivity +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import com.jaredrummler.android.colorpicker.* +import io.legado.app.lib.theme.ATH +import io.legado.app.R +import io.legado.app.lib.theme.accentColor +import org.jetbrains.anko.imageBitmap +import org.jetbrains.anko.layoutInflater +import kotlin.math.roundToInt + +class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Preference(context, attrs) { + + init { + // isPersistent = true + layoutResource = R.layout.view_preference + } + + companion object { + + fun bindView(context: Context, it: PreferenceViewHolder?, icon: Drawable?, title: CharSequence?, summary: CharSequence?, weightLayoutRes: Int?, viewId: Int?, + weightWidth: Int = 0, weightHeight: Int = 0): T? { + if (it == null) return null + val view = it.findViewById(R.id.preference_title) + if (view is TextView) { // && !view.isInEditMode + view.text = title + view.isVisible = title != null && title.isNotEmpty() + + val tv_summary = it.findViewById(R.id.preference_desc) + if(tv_summary is TextView) { + tv_summary.text = summary + tv_summary.isVisible = summary != null && summary.isNotEmpty() + } + + val _icon = it.findViewById(R.id.preference_icon) + if (_icon is ImageView) { + _icon.isVisible = icon != null && icon.isVisible + _icon.setImageDrawable(icon) + _icon.setColorFilter(context.accentColor) + } + } + + if (weightLayoutRes != null && weightLayoutRes != 0 && viewId != null && viewId != 0) { + val lay = it.findViewById(R.id.preference_widget) + if (lay is FrameLayout) { + var v = it.itemView.findViewById(viewId) + if (v == null) { + val inflater: LayoutInflater = context.layoutInflater + val childView = inflater.inflate(weightLayoutRes, null) + lay.removeAllViews() + lay.addView(childView) + lay.isVisible = true + v = lay.findViewById(viewId) + } + + if (weightWidth > 0 || weightHeight > 0) { + val lp = lay.layoutParams + if (weightHeight > 0) + lp.height = (context.resources.displayMetrics.density * weightHeight).roundToInt() + if (weightWidth > 0) + lp.width = (context.resources.displayMetrics.density * weightWidth).roundToInt() + lay.layoutParams = lp + } + + return v + } + } + + return null + } + + } + + override fun onBindViewHolder(holder: PreferenceViewHolder?) { + super.onBindViewHolder(holder) + bindView(context, holder, icon, title, summary, null, null) + } + +} diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt index e1458b76e..a7dcd2e6c 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt @@ -1,22 +1,41 @@ package io.legado.app.ui.widget.prefs import android.content.Context +import android.graphics.Color import android.util.AttributeSet +import android.view.View import android.widget.TextView +import androidx.core.view.isVisible import androidx.preference.PreferenceCategory import androidx.preference.PreferenceViewHolder +import io.legado.app.R import io.legado.app.lib.theme.accentColor -class PreferenceCategory(context: Context, attrs: AttributeSet) : - PreferenceCategory(context, attrs) { +class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCategory(context, attrs) { + + init { + isPersistent = true + layoutResource = R.layout.view_preference_category + } override fun onBindViewHolder(holder: PreferenceViewHolder?) { super.onBindViewHolder(holder) holder?.let { - val view = it.findViewById(android.R.id.title) - if (view is TextView && !view.isInEditMode) { - view.setTextColor(context.accentColor)//设置title文本的颜色 + val view = it.findViewById(R.id.preference_title) + if (view is TextView) { // && !view.isInEditMode + view.text = title + view.setTextColor(context.accentColor) //设置title文本的颜色 + view.isVisible = title != null && title.isNotEmpty() + + val da = it.findViewById(R.id.preference_divider_above) + if (da is View) { + da.isVisible = it.isDividerAllowedAbove + } + val db = it.findViewById(R.id.preference_divider_below) + if (db is View) { + db.isVisible = it.isDividerAllowedBelow + } } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt index 4fe66af7b..91532bbb5 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt @@ -12,13 +12,15 @@ import io.legado.app.lib.theme.accentColor class SwitchPreference(context: Context, attrs: AttributeSet) : SwitchPreferenceCompat(context, attrs) { + init { + layoutResource = R.layout.view_preference + } + override fun onBindViewHolder(holder: PreferenceViewHolder?) { super.onBindViewHolder(holder) - holder?.let { - val view = it.findViewById(R.id.switchWidget) - if (view is SwitchCompat) { - ATH.setTint(view, context.accentColor) - } + val v = Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, R.id.switchWidget) + if (v is SwitchCompat) { + ATH.setTint(v, context.accentColor) } } diff --git a/app/src/main/res/drawable/bg_prefs_color.xml b/app/src/main/res/drawable/bg_prefs_color.xml new file mode 100644 index 000000000..ff4a5de4e --- /dev/null +++ b/app/src/main/res/drawable/bg_prefs_color.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cfg_about.xml b/app/src/main/res/drawable/ic_cfg_about.xml new file mode 100644 index 000000000..42e3d30d3 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_cfg_backup.xml b/app/src/main/res/drawable/ic_cfg_backup.xml new file mode 100644 index 000000000..9c647c009 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_backup.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cfg_e_lnk.xml b/app/src/main/res/drawable/ic_cfg_e_lnk.xml new file mode 100644 index 000000000..b94a8eb4d --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_e_lnk.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_jz.xml b/app/src/main/res/drawable/ic_cfg_jz.xml new file mode 100644 index 000000000..710bae7fd --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_jz.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_other.xml b/app/src/main/res/drawable/ic_cfg_other.xml new file mode 100644 index 000000000..a3d26b642 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_other.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_replace.xml b/app/src/main/res/drawable/ic_cfg_replace.xml new file mode 100644 index 000000000..53c9d1d04 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_replace.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_source.xml b/app/src/main/res/drawable/ic_cfg_source.xml new file mode 100644 index 000000000..e71bb109d --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_source.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_theme.xml b/app/src/main/res/drawable/ic_cfg_theme.xml new file mode 100644 index 000000000..2fe949d19 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_theme.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cfg_web.xml b/app/src/main/res/drawable/ic_cfg_web.xml new file mode 100644 index 000000000..7a34ae004 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_web.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/view_preference.xml b/app/src/main/res/layout/view_preference.xml new file mode 100644 index 000000000..203241bf0 --- /dev/null +++ b/app/src/main/res/layout/view_preference.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_preference_category.xml b/app/src/main/res/layout/view_preference_category.xml new file mode 100644 index 000000000..28f4e73ff --- /dev/null +++ b/app/src/main/res/layout/view_preference_category.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_preference_s.xml b/app/src/main/res/layout/view_preference_s.xml new file mode 100644 index 000000000..37a354c52 --- /dev/null +++ b/app/src/main/res/layout/view_preference_s.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 78d372bbd..8ac7aa66a 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -7,6 +7,7 @@ @color/md_grey_900 @color/md_grey_850 @color/md_grey_800 + #10303030 #69000000 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 58f27cdec..0fe64b7ca 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -16,6 +16,7 @@ @color/md_grey_50 @color/md_grey_100 @color/md_grey_200 + @color/md_grey_50 #00000000 #10000000 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 558e90cbe..bc219ab39 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ 删除 替换 替换净化 + 配置替换净化规则 暂无 启用 替换净化-搜索 @@ -58,6 +59,7 @@ 添加本地 书源 书源管理 + 新建/导入/编辑/管理书源 设置 主题设置 与界面/颜色相关的一些设置 @@ -80,6 +82,7 @@ 加载中… 重试 Web 服务 + 启用Web服务 web编辑书源 http://%1$s:%2$d 离线下载 @@ -442,6 +445,7 @@ 选中时点击可弹出菜单 主题 主题模式 + 选择主题模式 默认主题 恢复主题为默认配色 加入QQ群 @@ -457,6 +461,7 @@ 导入本地书籍需存储权限 夜间模式 E-Ink 模式 + 电子墨水屏模式 本软件需要存储权限来存储备份书籍信息 再按一次退出程序 导入本地书籍需存储权限 diff --git a/app/src/main/res/xml/about.xml b/app/src/main/res/xml/about.xml index 130e16e60..2970ab12b 100644 --- a/app/src/main/res/xml/about.xml +++ b/app/src/main/res/xml/about.xml @@ -2,68 +2,78 @@ - - - - - - - - - - diff --git a/app/src/main/res/xml/donate.xml b/app/src/main/res/xml/donate.xml index 13d448544..8732f864c 100644 --- a/app/src/main/res/xml/donate.xml +++ b/app/src/main/res/xml/donate.xml @@ -4,51 +4,64 @@ - - - - - - - - - - + - - - - diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index b049bcf22..2f6d23f9c 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -4,6 +4,9 @@ - - + xmlns:app="http://schemas.android.com/apk/res-auto" + android:divider="@color/transparent" + app:allowDividerBelow="false" + app:allowDividerAbove="false" + > - - + app:iconSpaceReserved="false" + app:allowDividerAbove="true" + app:allowDividerBelow="false" > - - - - - From 71ac26980c53d8244fb43e1196cb529ea1ffbd8f Mon Sep 17 00:00:00 2001 From: yangyxd Date: Thu, 19 Mar 2020 17:39:00 +0800 Subject: [PATCH 25/36] =?UTF-8?q?=E8=B0=83=E6=95=B4"=E6=88=91=E7=9A=84"UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/ui/widget/prefs/ColorPreference.kt | 2 +- app/src/main/res/layout/view_preference_s.xml | 66 ------------------- 2 files changed, 1 insertion(+), 67 deletions(-) delete mode 100644 app/src/main/res/layout/view_preference_s.xml diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt index 998e9c7c4..9b619c551 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt @@ -47,7 +47,7 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex init { isPersistent = true - layoutResource = io.legado.app.R.layout.view_preference_s + layoutResource = io.legado.app.R.layout.view_preference val a = context.obtainStyledAttributes(attrs, R.styleable.ColorPreference) showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true) diff --git a/app/src/main/res/layout/view_preference_s.xml b/app/src/main/res/layout/view_preference_s.xml deleted file mode 100644 index 37a354c52..000000000 --- a/app/src/main/res/layout/view_preference_s.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file From 0f923e99a71b4714a95f702fb0703ec901c1d898 Mon Sep 17 00:00:00 2001 From: yangyxd Date: Thu, 19 Mar 2020 18:00:12 +0800 Subject: [PATCH 26/36] . --- app/src/main/res/xml/pref_config_other.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index 2f6d23f9c..560692986 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -40,22 +40,22 @@ app:allowDividerBelow="false" app:iconSpaceReserved="false"> - - - - From afba849286c1d71601a7b2e210029d69bb975eb0 Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 18:41:35 +0800 Subject: [PATCH 27/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index d6a435788..750f6139d 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -3,11 +3,14 @@ * 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 * 弄了个企业公众号[开源阅读](),后面弄好后会把原来的开源阅读软件迁移过来 +**2020/03/19** +* 美化界面我的 by yangyxd + **2020/03/18** * 尝试修复搜索时崩溃 * 解决看过书籍的移到顶部需要向上滚动才能看到的bug * 只有再书源被删除找不到书源时才会自动换源 -* 美化界面by yangyxd +* 美化界面 by yangyxd * 订阅后台播放 **2020/03/16** From 9d9519d508ba5ce7c1f138002bec7415e8f12701 Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 19:28:09 +0800 Subject: [PATCH 28/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 2 +- .../app/ui/widget/prefs/ColorPreference.kt | 14 +------ .../app/ui/widget/prefs/EditTextPreference.kt | 3 +- .../app/ui/widget/prefs/IconListPreference.kt | 9 +---- .../app/ui/widget/prefs/NameListPreference.kt | 4 +- .../legado/app/ui/widget/prefs/Preference.kt | 40 ++++++------------- .../app/ui/widget/prefs/SwitchPreference.kt | 12 +++++- 7 files changed, 30 insertions(+), 54 deletions(-) diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 750f6139d..a78b9ae50 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -1,7 +1,7 @@ ## 更新日志 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 -* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的开源阅读软件迁移过来 +* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的[开源阅读软件]()迁移过来 **2020/03/19** * 美化界面我的 by yangyxd diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt index 9b619c551..101b6678a 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt @@ -6,24 +6,14 @@ import android.content.res.TypedArray import android.graphics.Color import android.os.Bundle import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView import androidx.annotation.ColorInt import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog -import androidx.appcompat.widget.SwitchCompat -import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.jaredrummler.android.colorpicker.* import io.legado.app.lib.theme.ATH -import io.legado.app.lib.theme.accentColor -import org.jetbrains.anko.layoutInflater -import org.jetbrains.anko.px2dip class ColorPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs), ColorPickerDialogListener { @@ -121,12 +111,12 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex } override fun onBindViewHolder(holder: PreferenceViewHolder) { - super.onBindViewHolder(holder) val v = io.legado.app.ui.widget.prefs.Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, io.legado.app.R.id.cpv_preference_preview_color_panel, 30, 30) if (v is ColorPanelView) { - v?.color = color + v.color = color } + super.onBindViewHolder(holder) } override fun onSetInitialValue(defaultValue: Any?) { diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt index b914afcf5..8c0e39da3 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt @@ -14,9 +14,8 @@ class EditTextPreference(context: Context, attrs: AttributeSet) : androidx.prefe } override fun onBindViewHolder(holder: PreferenceViewHolder?) { + Preference.bindView(context, holder, icon, title, summary, null, null) super.onBindViewHolder(holder) - val txt = if (text == null || text.isEmpty()) summary else text - val v = Preference.bindView(context, holder, icon, title, txt, null, null) } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt index 2e043b4c3..fa8b4eeb7 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt @@ -10,22 +10,17 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView -import android.widget.TextView -import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentActivity import androidx.preference.ListPreference import androidx.preference.PreferenceViewHolder import androidx.recyclerview.widget.LinearLayoutManager -import com.jaredrummler.android.colorpicker.ColorPanelView import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.lib.theme.accentColor import io.legado.app.utils.getCompatDrawable import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_icon_preference.view.* -import org.jetbrains.anko.layoutInflater import org.jetbrains.anko.sdk27.listeners.onClick @@ -34,7 +29,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference private val mEntryDrawables = arrayListOf() init { - layoutResource = io.legado.app.R.layout.view_preference + layoutResource = R.layout.view_preference widgetLayoutResource = R.layout.view_icon val a = context.theme.obtainStyledAttributes(attrs, R.styleable.IconListPreference, 0, 0) @@ -63,7 +58,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference val selectedIndex = findIndexOfValue(value) if (selectedIndex >= 0) { val drawable = mEntryDrawables[selectedIndex] - v?.setImageDrawable(drawable) + v.setImageDrawable(drawable) } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt index f4c1ccac2..0763c8474 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt @@ -16,10 +16,10 @@ class NameListPreference(context: Context, attrs: AttributeSet) : ListPreference } override fun onBindViewHolder(holder: PreferenceViewHolder?) { - super.onBindViewHolder(holder) val v = Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, R.id.text_view) if (v is TextView) { - v?.text = entry + v.text = entry } + super.onBindViewHolder(holder) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt index 30603c89d..0190dcd9f 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt @@ -1,33 +1,17 @@ package io.legado.app.ui.widget.prefs import android.content.Context -import android.content.ContextWrapper -import android.content.res.TypedArray -import android.graphics.Color import android.graphics.drawable.Drawable -import android.media.Image -import android.os.Bundle import android.util.AttributeSet import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView -import androidx.annotation.ColorInt -import androidx.annotation.StringRes -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.widget.SwitchCompat import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams -import androidx.fragment.app.FragmentActivity -import androidx.preference.Preference import androidx.preference.PreferenceViewHolder -import com.jaredrummler.android.colorpicker.* -import io.legado.app.lib.theme.ATH import io.legado.app.R import io.legado.app.lib.theme.accentColor -import org.jetbrains.anko.imageBitmap import org.jetbrains.anko.layoutInflater import kotlin.math.roundToInt @@ -44,21 +28,21 @@ class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Pr weightWidth: Int = 0, weightHeight: Int = 0): T? { if (it == null) return null val view = it.findViewById(R.id.preference_title) - if (view is TextView) { // && !view.isInEditMode + if (view is TextView) { view.text = title view.isVisible = title != null && title.isNotEmpty() - val tv_summary = it.findViewById(R.id.preference_desc) - if(tv_summary is TextView) { - tv_summary.text = summary - tv_summary.isVisible = summary != null && summary.isNotEmpty() + val tvSummary = it.findViewById(R.id.preference_desc) + if (tvSummary is TextView) { + tvSummary.text = summary + tvSummary.isVisible = summary != null && summary.isNotEmpty() } - val _icon = it.findViewById(R.id.preference_icon) - if (_icon is ImageView) { - _icon.isVisible = icon != null && icon.isVisible - _icon.setImageDrawable(icon) - _icon.setColorFilter(context.accentColor) + val iconView = it.findViewById(R.id.preference_icon) + if (iconView is ImageView) { + iconView.isVisible = icon != null && icon.isVisible + iconView.setImageDrawable(icon) + iconView.setColorFilter(context.accentColor) } } @@ -72,7 +56,7 @@ class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Pr lay.removeAllViews() lay.addView(childView) lay.isVisible = true - v = lay.findViewById(viewId) + v = lay.findViewById(viewId) } if (weightWidth > 0 || weightHeight > 0) { @@ -94,8 +78,8 @@ class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Pr } override fun onBindViewHolder(holder: PreferenceViewHolder?) { - super.onBindViewHolder(holder) bindView(context, holder, icon, title, summary, null, null) + super.onBindViewHolder(holder) } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt index 91532bbb5..2f6becfc2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt @@ -17,11 +17,19 @@ class SwitchPreference(context: Context, attrs: AttributeSet) : } override fun onBindViewHolder(holder: PreferenceViewHolder?) { - super.onBindViewHolder(holder) - val v = Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, R.id.switchWidget) + val v = Preference.bindView( + context, + holder, + icon, + title, + summary, + widgetLayoutResource, + R.id.switchWidget + ) if (v is SwitchCompat) { ATH.setTint(v, context.accentColor) } + super.onBindViewHolder(holder) } } From 5933e33a4c06463c9c6a7ec714787f19a6abc400 Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 19:32:22 +0800 Subject: [PATCH 29/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/layout/view_preference.xml | 28 ++++++++++----------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/layout/view_preference.xml b/app/src/main/res/layout/view_preference.xml index 203241bf0..87b10ef5b 100644 --- a/app/src/main/res/layout/view_preference.xml +++ b/app/src/main/res/layout/view_preference.xml @@ -1,7 +1,6 @@ - + android:orientation="horizontal" + android:focusable="true"> - + android:layout_height="24dp" + tools:ignore="ContentDescription,RtlHardcoded" /> + android:layout_height="wrap_content" /> + android:layout_height="wrap_content" /> - + android:layout_height="wrap_content" + tools:ignore="RtlHardcoded" /> \ No newline at end of file From 87a5409eac4c187d7c178c75b18db0ae13d2048f Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 20:15:16 +0800 Subject: [PATCH 30/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/model/SearchBook.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/model/SearchBook.kt b/app/src/main/java/io/legado/app/model/SearchBook.kt index 7b344e60f..f70d931a2 100644 --- a/app/src/main/java/io/legado/app/model/SearchBook.kt +++ b/app/src/main/java/io/legado/app/model/SearchBook.kt @@ -1,5 +1,25 @@ package io.legado.app.model -class SearchBook { +import io.legado.app.data.entities.BookSource +import io.legado.app.help.AppConfig +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +class SearchBook(scope: CoroutineScope, callBack: CallBack) { + private var searchPool = + Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + private var searchId = System.currentTimeMillis() + private var searchPage = 1 + private var searchKey: String = "" + private val searchEngines = arrayListOf() + + + private data class SearchEngine(val bookSource: BookSource, var hasMore: Boolean = true) + + interface CallBack { + fun onSearchSuccess() + fun onSearchFinish() + } } \ No newline at end of file From 5b4e4d4e26d54059b17bf0b42e799f693c11361f Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 21:03:37 +0800 Subject: [PATCH 31/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/model/SearchBook.kt | 25 ------ .../io/legado/app/model/SearchBookModel.kt | 78 +++++++++++++++++++ .../main/java/io/legado/app/model/WebBook.kt | 2 +- 3 files changed, 79 insertions(+), 26 deletions(-) delete mode 100644 app/src/main/java/io/legado/app/model/SearchBook.kt create mode 100644 app/src/main/java/io/legado/app/model/SearchBookModel.kt diff --git a/app/src/main/java/io/legado/app/model/SearchBook.kt b/app/src/main/java/io/legado/app/model/SearchBook.kt deleted file mode 100644 index f70d931a2..000000000 --- a/app/src/main/java/io/legado/app/model/SearchBook.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.legado.app.model - -import io.legado.app.data.entities.BookSource -import io.legado.app.help.AppConfig -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.asCoroutineDispatcher -import java.util.concurrent.Executors - -class SearchBook(scope: CoroutineScope, callBack: CallBack) { - private var searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() - private var searchId = System.currentTimeMillis() - private var searchPage = 1 - private var searchKey: String = "" - private val searchEngines = arrayListOf() - - - private data class SearchEngine(val bookSource: BookSource, var hasMore: Boolean = true) - - interface CallBack { - fun onSearchSuccess() - fun onSearchFinish() - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/SearchBookModel.kt new file mode 100644 index 000000000..8b415bc9f --- /dev/null +++ b/app/src/main/java/io/legado/app/model/SearchBookModel.kt @@ -0,0 +1,78 @@ +package io.legado.app.model + +import io.legado.app.App +import io.legado.app.data.entities.BookSource +import io.legado.app.data.entities.SearchBook +import io.legado.app.help.AppConfig +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.utils.getPrefString +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +class SearchBookModel(private val scope: CoroutineScope, private val callBack: CallBack) { + private var searchPool = + Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + private var mSearchId = System.currentTimeMillis() + private var searchPage = 1 + private var searchKey: String = "" + private var task: Coroutine<*>? = null + + fun search(searchId: Long, key: String) { + if (searchId != mSearchId) { + task?.cancel() + mSearchId = searchId + searchPage = 1 + if (key.isEmpty()) { + return + } else { + this.searchKey = key + } + } else { + searchPage++ + } + task = Coroutine.async(scope, searchPool) { + val searchGroup = App.INSTANCE.getPrefString("searchGroup") ?: "" + val bookSourceList = if (searchGroup.isBlank()) { + App.db.bookSourceDao().allEnabled + } else { + App.db.bookSourceDao().getEnabledByGroup(searchGroup) + } + for (item in bookSourceList) { + //task取消时自动取消 by (scope = this@execute) + WebBook(item).searchBook( + searchKey, + searchPage, + scope = this, + context = searchPool + ) + .timeout(30000L) + .onSuccess(IO) { + it?.let { + if (searchId == mSearchId) { + callBack.onSearchSuccess(it) + } + } + } + } + }.onStart { + + }.onCancel { + + } + + task?.invokeOnCompletion { + + } + } + + private data class SearchEngine(val bookSource: BookSource, var hasMore: Boolean = true) + + interface CallBack { + fun onSearchSuccess(searchBooks: ArrayList) + fun onSearchFinish() + fun onSearchCancel() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 6fee7add6..cb1742132 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -27,7 +27,7 @@ class WebBook(val bookSource: BookSource) { page: Int? = 1, scope: CoroutineScope = Coroutine.DEFAULT, context: CoroutineContext = Dispatchers.IO - ): Coroutine> { + ): Coroutine> { return Coroutine.async(scope, context) { searchBookSuspend(scope, key, page) } From 83e5a5d91f16859f177e49ed0d1501749980e484 Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 21:23:43 +0800 Subject: [PATCH 32/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/help/coroutine/Coroutine.kt | 6 +-- .../main/java/io/legado/app/model/Debug.kt | 50 ++++++++----------- .../io/legado/app/model/SearchBookModel.kt | 6 +-- .../io/legado/app/service/AudioPlayService.kt | 4 +- .../io/legado/app/service/DownloadService.kt | 6 +-- .../io/legado/app/service/help/ReadBook.kt | 2 +- .../legado/app/ui/audio/AudioPlayViewModel.kt | 2 +- .../book/changecover/ChangeCoverViewModel.kt | 2 +- .../changesource/ChangeSourceViewModel.kt | 16 +++--- .../ui/book/explore/ExploreShowViewModel.kt | 8 ++- .../app/ui/book/info/BookInfoViewModel.kt | 32 ++++++------ .../app/ui/book/read/ReadBookViewModel.kt | 2 +- .../app/ui/book/search/SearchViewModel.kt | 12 ++--- .../book/source/manage/BookSourceViewModel.kt | 2 +- .../io/legado/app/ui/main/MainViewModel.kt | 8 ++- .../ui/main/bookshelf/BookshelfViewModel.kt | 6 +-- .../ui/rss/article/RssArticlesViewModel.kt | 8 +-- .../rss/source/manage/RssSourceViewModel.kt | 2 +- 18 files changed, 74 insertions(+), 100 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index eb0a29375..0811eee10 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -28,7 +28,7 @@ class Coroutine( private val job: Job private var start: VoidCallback? = null - private var success: Callback? = null + private var success: Callback? = null private var error: Callback? = null private var finally: VoidCallback? = null private var cancel: VoidCallback? = null @@ -79,7 +79,7 @@ class Coroutine( fun onSuccess( context: CoroutineContext? = null, - block: suspend CoroutineScope.(T?) -> Unit + block: suspend CoroutineScope.(T) -> Unit ): Coroutine { this.success = Callback(context, block) return this@Coroutine @@ -192,7 +192,7 @@ class Coroutine( context: CoroutineContext, timeMillis: Long, noinline block: suspend CoroutineScope.() -> T - ): T? { + ): T { return withContext(scope.coroutineContext.plus(context)) { if (timeMillis > 0L) withTimeout(timeMillis) { block() diff --git a/app/src/main/java/io/legado/app/model/Debug.kt b/app/src/main/java/io/legado/app/model/Debug.kt index 7d7ec3627..2acd8ac9e 100644 --- a/app/src/main/java/io/legado/app/model/Debug.kt +++ b/app/src/main/java/io/legado/app/model/Debug.kt @@ -56,9 +56,7 @@ object Debug { log(debugSource, "︾开始解析") Rss.getArticles(rssSource, null) .onSuccess { - if (it == null) { - log(debugSource, "︽解析失败", state = -1) - } else if(it.articles.isEmpty()) { + if (it.articles.isEmpty()) { log(debugSource, "⇒列表页解析成功,为空") log(debugSource, "︽解析完成", state = 1000) } else { @@ -140,14 +138,12 @@ object Debug { log(debugSource, "︾开始解析发现页") val explore = webBook.exploreBook(url, 1) .onSuccess { exploreBooks -> - exploreBooks?.let { - if (exploreBooks.isNotEmpty()) { - log(debugSource, "︽发现页解析完成") - log(debugSource, showTime = false) - infoDebug(webBook, exploreBooks[0].toBook()) - } else { - log(debugSource, "︽未获取到书籍", state = -1) - } + if (exploreBooks.isNotEmpty()) { + log(debugSource, "︽发现页解析完成") + log(debugSource, showTime = false) + infoDebug(webBook, exploreBooks[0].toBook()) + } else { + log(debugSource, "︽未获取到书籍", state = -1) } } .onError { @@ -164,14 +160,12 @@ object Debug { log(debugSource, "︾开始解析搜索页") val search = webBook.searchBook(key, 1) .onSuccess { searchBooks -> - searchBooks?.let { - if (searchBooks.isNotEmpty()) { - log(debugSource, "︽搜索页解析完成") - log(debugSource, showTime = false) - infoDebug(webBook, searchBooks[0].toBook()) - } else { - log(debugSource, "︽未获取到书籍", state = -1) - } + if (searchBooks.isNotEmpty()) { + log(debugSource, "︽搜索页解析完成") + log(debugSource, showTime = false) + infoDebug(webBook, searchBooks[0].toBook()) + } else { + log(debugSource, "︽未获取到书籍", state = -1) } } .onError { @@ -197,16 +191,14 @@ object Debug { private fun tocDebug(webBook: WebBook, book: Book) { log(debugSource, "︾开始解析目录页") val chapterList = webBook.getChapterList(book) - .onSuccess { chapterList -> - chapterList?.let { - if (it.isNotEmpty()) { - log(debugSource, "︽目录页解析完成") - log(debugSource, showTime = false) - val nextChapterUrl = if (it.size > 1) it[1].url else null - contentDebug(webBook, book, it[0], nextChapterUrl) - } else { - log(debugSource, "︽目录列表为空", state = -1) - } + .onSuccess { + if (it.isNotEmpty()) { + log(debugSource, "︽目录页解析完成") + log(debugSource, showTime = false) + val nextChapterUrl = if (it.size > 1) it[1].url else null + contentDebug(webBook, book, it[0], nextChapterUrl) + } else { + log(debugSource, "︽目录列表为空", state = -1) } } .onError { diff --git a/app/src/main/java/io/legado/app/model/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/SearchBookModel.kt index 8b415bc9f..e48b45a95 100644 --- a/app/src/main/java/io/legado/app/model/SearchBookModel.kt +++ b/app/src/main/java/io/legado/app/model/SearchBookModel.kt @@ -49,10 +49,8 @@ class SearchBookModel(private val scope: CoroutineScope, private val callBack: C ) .timeout(30000L) .onSuccess(IO) { - it?.let { - if (searchId == mSearchId) { - callBack.onSearchSuccess(it) - } + if (searchId == mSearchId) { + callBack.onSearchSuccess(it) } } } diff --git a/app/src/main/java/io/legado/app/service/AudioPlayService.kt b/app/src/main/java/io/legado/app/service/AudioPlayService.kt index c165f3142..986e7c4ed 100644 --- a/app/src/main/java/io/legado/app/service/AudioPlayService.kt +++ b/app/src/main/java/io/legado/app/service/AudioPlayService.kt @@ -18,9 +18,9 @@ import androidx.core.app.NotificationCompat import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseService -import io.legado.app.constant.IntentAction import io.legado.app.constant.AppConst import io.legado.app.constant.EventBus +import io.legado.app.constant.IntentAction import io.legado.app.constant.Status import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp @@ -275,7 +275,7 @@ class AudioPlayService : BaseService(), AudioPlay.book?.let { book -> AudioPlay.webBook?.getContent(book, chapter, scope = this) ?.onSuccess(IO) { content -> - if (content.isNullOrEmpty()) { + if (content.isEmpty()) { withContext(Main) { toast("未获取到资源链接") } diff --git a/app/src/main/java/io/legado/app/service/DownloadService.kt b/app/src/main/java/io/legado/app/service/DownloadService.kt index 732506449..dd48933e8 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -130,10 +130,8 @@ class DownloadService : BaseService() { // notificationContent = "启动:" + chapter.title //} .onSuccess(IO) { content -> - content?.let { - downloadCount[entry.key]?.increaseSuccess() - BookHelp.saveContent(book, chapter, content) - } + downloadCount[entry.key]?.increaseSuccess() + BookHelp.saveContent(book, chapter, content) } .onFinally(IO) { synchronized(this@DownloadService) { diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index f18280d49..f48f13d11 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -245,7 +245,7 @@ object ReadBook { book?.let { book -> webBook?.getContent(book, chapter) ?.onSuccess(Dispatchers.IO) { content -> - if (content.isNullOrEmpty()) { + if (content.isEmpty()) { contentLoadFinish( chapter, App.INSTANCE.getString(R.string.content_empty), diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt index b5dd5bfba..892a99f41 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt @@ -72,7 +72,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application) execute { AudioPlay.webBook?.getChapterList(book, this) ?.onSuccess(Dispatchers.IO) { cList -> - if (!cList.isNullOrEmpty()) { + if (cList.isNotEmpty()) { if (changeDruChapterIndex == null) { App.db.bookChapterDao().insert(*cList.toTypedArray()) AudioPlay.chapterSize = cList.size diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt index bcd0e61d0..0c91fe0f9 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt @@ -57,7 +57,7 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application WebBook(item).searchBook(name, scope = this@execute, context = searchPool) .timeout(30000L) .onSuccess(Dispatchers.IO) { - if (it != null && it.isNotEmpty()) { + if (it.isNotEmpty()) { val searchBook = it[0] if (searchBook.name == name && searchBook.author == author && !searchBook.coverUrl.isNullOrEmpty() diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt index 5b1c7e939..b375aed11 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt @@ -78,7 +78,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio WebBook(item).searchBook(name, scope = this@execute, context = searchPool) .timeout(30000L) .onSuccess(IO) { - it?.forEach { searchBook -> + it.forEach { searchBook -> if (searchBook.name == name && searchBook.author == author) { if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { if (searchBook.tocUrl.isEmpty()) { @@ -110,7 +110,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getBookInfo(book, this) .onSuccess { - it?.let { loadChapter(it) } + loadChapter(it) }.onError { debug { context.getString(R.string.error_get_book_info) } } @@ -122,13 +122,11 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio execute { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getChapterList(book, this) - .onSuccess(IO) { - it?.let { chapters -> - if (chapters.isNotEmpty()) { - book.latestChapterTitle = chapters.last().title - val searchBook: SearchBook = book.toSearchBook() - searchFinish(searchBook) - } + .onSuccess(IO) { chapters -> + if (chapters.isNotEmpty()) { + book.latestChapterTitle = chapters.last().title + val searchBook: SearchBook = book.toSearchBook() + searchFinish(searchBook) } }.onError { debug { context.getString(R.string.error_get_chapter_list) } diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt index d34e21916..bd22b3968 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt @@ -35,11 +35,9 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application WebBook(source).exploreBook(url, page, this) .timeout(30000L) .onSuccess(IO) { searchBooks -> - searchBooks?.let { - booksData.postValue(searchBooks) - App.db.searchBookDao().insert(*searchBooks.toTypedArray()) - page++ - } + booksData.postValue(searchBooks) + App.db.searchBookDao().insert(*searchBooks.toTypedArray()) + page++ } } } diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index 485d704ce..a8d8f59ef 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -59,13 +59,11 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getBookInfo(book, this) .onSuccess(IO) { - it?.let { - bookData.postValue(book) - if (inBookshelf) { - App.db.bookDao().update(book) - } - loadChapter(it, changeDruChapterIndex) + bookData.postValue(book) + if (inBookshelf) { + App.db.bookDao().update(book) } + loadChapter(it, changeDruChapterIndex) }.onError { toast(R.string.error_get_book_info) } @@ -92,20 +90,18 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getChapterList(book, this) .onSuccess(IO) { - it?.let { - if (it.isNotEmpty()) { - if (inBookshelf) { - App.db.bookDao().update(book) - App.db.bookChapterDao().insert(*it.toTypedArray()) - } - if (changeDruChapterIndex == null) { - chapterListData.postValue(it) - } else { - changeDruChapterIndex(it) - } + if (it.isNotEmpty()) { + if (inBookshelf) { + App.db.bookDao().update(book) + App.db.bookChapterDao().insert(*it.toTypedArray()) + } + if (changeDruChapterIndex == null) { + chapterListData.postValue(it) } else { - toast(R.string.chapter_list_empty) + changeDruChapterIndex(it) } + } else { + toast(R.string.chapter_list_empty) } }.onError { chapterListData.postValue(null) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 2994dcac2..231f83e11 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -112,7 +112,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } else { ReadBook.webBook?.getChapterList(book, this) ?.onSuccess(IO) { cList -> - if (!cList.isNullOrEmpty()) { + if (cList.isNotEmpty()) { if (changeDruChapterIndex == null) { App.db.bookChapterDao().insert(*cList.toTypedArray()) App.db.bookDao().update(book) diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index f1a4fc858..fd0226271 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -63,13 +63,11 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { .timeout(30000L) .onSuccess(IO) { if (isActive) { - it?.let { list -> - if (context.getPrefBoolean(PreferKey.precisionSearch)) { - precisionSearch(this, list) - } else { - App.db.searchBookDao().insert(*list.toTypedArray()) - mergeItems(this, list) - } + if (context.getPrefBoolean(PreferKey.precisionSearch)) { + precisionSearch(this, it) + } else { + App.db.searchBookDao().insert(*it.toTypedArray()) + mergeItems(this, it) } } } diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt index 85e3239bc..d44efd3ad 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt @@ -211,7 +211,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) }.onError { finally(it.localizedMessage ?: "") }.onSuccess { - finally(it ?: "导入完成") + finally(it) } } diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt index 4fe57d758..61c951ae5 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt @@ -33,11 +33,9 @@ class MainViewModel(application: Application) : BaseViewModel(application) { updateList.remove(book.bookUrl) postEvent(EventBus.UP_BOOK, book.bookUrl) } - it?.let { - App.db.bookDao().update(book) - App.db.bookChapterDao().delByBook(book.bookUrl) - App.db.bookChapterDao().insert(*it.toTypedArray()) - } + App.db.bookDao().update(book) + App.db.bookChapterDao().delByBook(book.bookUrl) + App.db.bookChapterDao().insert(*it.toTypedArray()) } .onError { synchronized(this) { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index 87d2b3e06..2415db1be 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -43,10 +43,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application) ) WebBook(bookSource).getBookInfo(book, this) .onSuccess(IO) { - it?.let { book -> - App.db.bookDao().insert(book) - successCount++ - } + App.db.bookDao().insert(it) + successCount++ }.onError { throw Exception(it.localizedMessage) } diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt index ccfff44df..61a2b14be 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt @@ -44,8 +44,8 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application rssSource?.let { rssSource -> Rss.getArticles(rssSource, null) .onSuccess(IO) { - nextPageUrl = it?.nextPageUrl - it?.articles?.let { list -> + nextPageUrl = it.nextPageUrl + it.articles.let { list -> list.forEach { rssArticle -> rssArticle.order = order-- } @@ -76,8 +76,8 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application if (source != null && !pageUrl.isNullOrEmpty()) { Rss.getArticles(source, pageUrl) .onSuccess(IO) { - nextPageUrl = it?.nextPageUrl - it?.articles?.let { list -> + nextPageUrl = it.nextPageUrl + it.articles.let { list -> if (list.isEmpty()) { callBack?.loadFinally(false) return@let diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt index 9160155b3..82bad9584 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt @@ -187,7 +187,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) }.onError { finally(it.localizedMessage ?: "") }.onSuccess { - finally(it ?: "导入完成") + finally(it) } } From e13f7b0f9cf5341156d4624bd53c375375ce9bf7 Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 22:05:09 +0800 Subject: [PATCH 33/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/legado/app/model/SearchBookModel.kt | 23 +++-- .../app/ui/book/search/SearchViewModel.kt | 94 +++++++------------ 2 files changed, 51 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/io/legado/app/model/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/SearchBookModel.kt index e48b45a95..9e1ad126b 100644 --- a/app/src/main/java/io/legado/app/model/SearchBookModel.kt +++ b/app/src/main/java/io/legado/app/model/SearchBookModel.kt @@ -1,7 +1,6 @@ package io.legado.app.model import io.legado.app.App -import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook import io.legado.app.help.AppConfig import io.legado.app.help.coroutine.Coroutine @@ -22,6 +21,7 @@ class SearchBookModel(private val scope: CoroutineScope, private val callBack: C fun search(searchId: Long, key: String) { if (searchId != mSearchId) { task?.cancel() + callBack.onSearchCancel() mSearchId = searchId searchPage = 1 if (key.isEmpty()) { @@ -55,19 +55,30 @@ class SearchBookModel(private val scope: CoroutineScope, private val callBack: C } } }.onStart { - - }.onCancel { - + callBack.onSearchStart() } task?.invokeOnCompletion { - + if (searchId == mSearchId) { + callBack.onSearchFinish() + } } } - private data class SearchEngine(val bookSource: BookSource, var hasMore: Boolean = true) + fun cancelSearch() { + task?.cancel() + mSearchId = 0 + callBack.onSearchCancel() + } + + fun close() { + task?.cancel() + mSearchId = 0 + searchPool.close() + } interface CallBack { + fun onSearchStart() fun onSearchSuccess(searchBooks: ArrayList) fun onSearchFinish() fun onSearchCancel() diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index fd0226271..ccfc5dd15 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -7,84 +7,58 @@ import io.legado.app.base.BaseViewModel import io.legado.app.constant.PreferKey import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchKeyword -import io.legado.app.help.AppConfig -import io.legado.app.help.coroutine.Coroutine -import io.legado.app.model.WebBook +import io.legado.app.model.SearchBookModel import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.getPrefString import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.isActive -import java.util.concurrent.Executors -class SearchViewModel(application: Application) : BaseViewModel(application) { - private var searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() - private var task: Coroutine<*>? = null +class SearchViewModel(application: Application) : BaseViewModel(application) + , SearchBookModel.CallBack { + private val searchBookModel = SearchBookModel(this, this) var isSearchLiveData = MutableLiveData() var searchBookLiveData = MutableLiveData>() var searchKey: String = "" - var searchPage = 1 var isLoading = false var searchBooks = arrayListOf() + private var searchID = 0L /** * 开始搜索 */ fun search(key: String) { - task?.cancel() - if (key.isEmpty() && searchKey.isEmpty()) { - return - } else if (key.isEmpty()) { - isLoading = true - searchPage++ - } else if (key.isNotEmpty()) { - isLoading = true - searchPage = 1 - searchKey = key + if (searchKey != key && key.isNotEmpty()) { + searchBookModel.cancelSearch() searchBooks.clear() + searchBookLiveData.postValue(searchBooks) + searchID = System.currentTimeMillis() + searchKey = key } - task = execute { - val searchGroup = context.getPrefString("searchGroup") ?: "" - val bookSourceList = if (searchGroup.isBlank()) { - App.db.bookSourceDao().allEnabled - } else { - App.db.bookSourceDao().getEnabledByGroup(searchGroup) - } - for (item in bookSourceList) { - //task取消时自动取消 by (scope = this@execute) - WebBook(item).searchBook( - searchKey, - searchPage, - scope = this, - context = searchPool - ) - .timeout(30000L) - .onSuccess(IO) { - if (isActive) { - if (context.getPrefBoolean(PreferKey.precisionSearch)) { - precisionSearch(this, it) - } else { - App.db.searchBookDao().insert(*it.toTypedArray()) - mergeItems(this, it) - } - } - } - } - }.onStart { - isSearchLiveData.postValue(true) - }.onCancel { - isSearchLiveData.postValue(false) - isLoading = false - } + searchBookModel.search(searchID, searchKey) + } + + override fun onSearchStart() { + isSearchLiveData.postValue(true) + } - task?.invokeOnCompletion { - isSearchLiveData.postValue(false) - isLoading = false + override fun onSearchSuccess(searchBooks: ArrayList) { + if (context.getPrefBoolean(PreferKey.precisionSearch)) { + precisionSearch(this, searchBooks) + } else { + App.db.searchBookDao().insert(*searchBooks.toTypedArray()) + mergeItems(this, searchBooks) } } + override fun onSearchFinish() { + isSearchLiveData.postValue(false) + isLoading = false + } + + override fun onSearchCancel() { + isSearchLiveData.postValue(false) + isLoading = false + } + /** * 精确搜索处理 */ @@ -185,7 +159,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { * 停止搜索 */ fun stop() { - task?.cancel() + searchBookModel.cancelSearch() } /** @@ -221,7 +195,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { override fun onCleared() { super.onCleared() - searchPool.close() + searchBookModel.close() } } From e9f40e699ec1c78589fe3a92c43bd77ef40c9e8c Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 22:10:27 +0800 Subject: [PATCH 34/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../legado/app/ui/book/search/DiffCallBack.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index 999f2a941..b03e54fb7 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -50,30 +50,32 @@ class DiffCallBack(private val oldItems: List, private val newItems: override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val payload = Bundle() - val newItem = newItems.getOrNull(newItemPosition) - val oldItem = oldItems.getOrNull(oldItemPosition) - if (newItem == null) return payload - if (oldItem?.name != newItem.name) { + val newItem = newItems[newItemPosition] + val oldItem = oldItems[oldItemPosition] + if (oldItem.name != newItem.name) { payload.putString("name", newItem.name) } - if (oldItem?.author != newItem.author) { + if (oldItem.author != newItem.author) { payload.putString("author", newItem.author) } - if (oldItem?.origins?.size != newItem.origins.size) { + if (oldItem.origins.size != newItem.origins.size) { payload.putInt("origins", newItem.origins.size) } - if (oldItem?.coverUrl != newItem.coverUrl) { + if (oldItem.coverUrl != newItem.coverUrl) { payload.putString("cover", newItem.coverUrl) } - if (oldItem?.kind != newItem.kind) { + if (oldItem.kind != newItem.kind) { payload.putString("kind", newItem.kind) } - if (oldItem?.latestChapterTitle != newItem.latestChapterTitle) { + if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { payload.putString("last", newItem.latestChapterTitle) } - if (oldItem?.intro != newItem.intro) { + if (oldItem.intro != newItem.intro) { payload.putString("intro", newItem.intro) } + if (payload.isEmpty) { + return null + } return payload } } \ No newline at end of file From 444c141c45841416e343d5926ad1afad4754b81a Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 22:12:46 +0800 Subject: [PATCH 35/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/model/SearchBookModel.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/model/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/SearchBookModel.kt index 9e1ad126b..1f6cac065 100644 --- a/app/src/main/java/io/legado/app/model/SearchBookModel.kt +++ b/app/src/main/java/io/legado/app/model/SearchBookModel.kt @@ -18,10 +18,16 @@ class SearchBookModel(private val scope: CoroutineScope, private val callBack: C private var searchKey: String = "" private var task: Coroutine<*>? = null + private fun initSearchPool() { + searchPool = + Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + } + fun search(searchId: Long, key: String) { if (searchId != mSearchId) { task?.cancel() - callBack.onSearchCancel() + searchPool.close() + initSearchPool() mSearchId = searchId searchPage = 1 if (key.isEmpty()) { From c72f025f6f61d34e87be6a9e7eb74bb4fdde4ea0 Mon Sep 17 00:00:00 2001 From: kunfei Date: Thu, 19 Mar 2020 22:17:54 +0800 Subject: [PATCH 36/36] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/assets/updateLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index a78b9ae50..cb38140fe 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -5,6 +5,7 @@ **2020/03/19** * 美化界面我的 by yangyxd +* 优化搜索 **2020/03/18** * 尝试修复搜索时崩溃