From 2f33336d748646ec47ab7f86190645ec4940cfd2 Mon Sep 17 00:00:00 2001 From: atbest Date: Thu, 23 May 2019 21:17:40 -0400 Subject: [PATCH] Added replace rule --- app/build.gradle | 5 + app/src/main/AndroidManifest.xml | 1 + app/src/main/java/io/legado/app/App.kt | 6 ++ .../java/io/legado/app/constant/AppConst.kt | 4 + .../java/io/legado/app/data/AppDatabase.kt | 2 + .../io/legado/app/data/dao/ReplaceRuleDao.kt | 49 ++++++++++ .../legado/app/data/entities/ReplaceRule.kt | 18 ++-- .../io/legado/app/ui/main/MainActivity.kt | 93 +++++++++++++++++-- .../app/ui/replacerule/ReplaceRuleActivity.kt | 53 +++++++++++ .../app/ui/replacerule/ReplaceRuleAdapter.kt | 70 ++++++++++++++ .../java/io/legado/app/utils/FileUtils.kt | 5 + .../io/legado/app/utils/MiscExtensions.kt | 9 ++ app/src/main/res/drawable/bg_ib_pre_round.xml | 4 + app/src/main/res/drawable/ic_clear_all.xml | 17 ++++ app/src/main/res/drawable/ic_edit.xml | 14 +++ .../main/res/layout/activity_replace_rule.xml | 30 ++++++ app/src/main/res/layout/item_relace_rule.xml | 57 ++++++++++++ .../main/res/menu/activity_main_drawer.xml | 20 ++-- app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 13 ++- 20 files changed, 439 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt create mode 100644 app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt create mode 100644 app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt create mode 100644 app/src/main/java/io/legado/app/utils/FileUtils.kt create mode 100644 app/src/main/java/io/legado/app/utils/MiscExtensions.kt create mode 100644 app/src/main/res/drawable/bg_ib_pre_round.xml create mode 100644 app/src/main/res/drawable/ic_clear_all.xml create mode 100644 app/src/main/res/drawable/ic_edit.xml create mode 100644 app/src/main/res/layout/activity_replace_rule.xml create mode 100644 app/src/main/res/layout/item_relace_rule.xml diff --git a/app/build.gradle b/app/build.gradle index 1c6c7a570..3a59b113a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,6 +69,11 @@ dependencies { //协程 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1' + implementation 'pub.devrel:easypermissions:3.0.0' + implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.jayway.jsonpath:json-path:2.4.0' + implementation 'org.jsoup:jsoup:1.12.1' + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 10ee7aea6..7c5e32f4a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,6 +39,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index d94919f22..6ef0e877d 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -1,6 +1,7 @@ package io.legado.app import android.app.Application +import io.legado.app.data.AppDatabase class App : Application() { @@ -8,10 +9,15 @@ class App : Application() { @JvmStatic lateinit var INSTANCE: App private set + + @JvmStatic + lateinit var db: AppDatabase + private set } override fun onCreate() { super.onCreate() INSTANCE = this + db = AppDatabase.createDatabase(INSTANCE) } } diff --git a/app/src/main/java/io/legado/app/constant/AppConst.kt b/app/src/main/java/io/legado/app/constant/AppConst.kt index 6f9e7b6b9..89a1d204f 100644 --- a/app/src/main/java/io/legado/app/constant/AppConst.kt +++ b/app/src/main/java/io/legado/app/constant/AppConst.kt @@ -4,4 +4,8 @@ object AppConst { const val channelIdDownload = "channel_download" const val channelIdReadAloud = "channel_read_aloud" const val channelIdWeb = "channel_web" + + const val APP_TAG = "Legado" + const val RC_IMPORT_YUEDU_DATA = 100 + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index 327651493..f5db6c908 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -7,6 +7,7 @@ import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import io.legado.app.data.dao.BookDao +import io.legado.app.data.dao.ReplaceRuleDao import io.legado.app.data.entities.Book import io.legado.app.data.entities.Chapter import io.legado.app.data.entities.ReplaceRule @@ -51,5 +52,6 @@ abstract class AppDatabase : RoomDatabase() { } abstract fun bookDao(): BookDao + abstract fun replaceRuleDao(): ReplaceRuleDao } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt new file mode 100644 index 000000000..9b92e4c6a --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt @@ -0,0 +1,49 @@ +package io.legado.app.data.dao + +import androidx.lifecycle.LiveData +import androidx.paging.DataSource +import androidx.room.* +import io.legado.app.data.entities.ReplaceRule + + +@Dao +interface ReplaceRuleDao { + + @Query("SELECT * FROM replace_rules ORDER BY sortOrder ASC") + fun observeAll(): DataSource.Factory + + @Query("SELECT id FROM replace_rules ORDER BY sortOrder ASC") + fun observeAllIds(): LiveData> + + @get:Query("SELECT MAX(sortOrder) FROM replace_rules") + val maxOrder: Int + + @get:Query("SELECT * FROM replace_rules ORDER BY sortOrder ASC") + val all: List + + @get:Query("SELECT * FROM replace_rules WHERE isEnabled = 1 ORDER BY sortOrder ASC") + val allEnabled: List + + @Query("SELECT * FROM replace_rules WHERE id = :id") + fun findById(id: Int): ReplaceRule? + + @Query("SELECT * FROM replace_rules WHERE id in (:ids)") + fun findByIds(vararg ids: Int): List + + @Query("SELECT * FROM replace_rules WHERE isEnabled = 1 AND scope LIKE '%' || :scope || '%'") + fun findEnabledByScope(scope: String): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg replaceRules: ReplaceRule) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(replaceRule: ReplaceRule): Long + + @Update + fun update(vararg replaceRules: ReplaceRule) + + @Delete + fun delete(vararg replaceRules: ReplaceRule) + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt b/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt index 2436fb641..8b5a0fda6 100644 --- a/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt @@ -1,6 +1,7 @@ package io.legado.app.data.entities import android.os.Parcelable +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey @@ -11,14 +12,15 @@ import kotlinx.android.parcel.Parcelize indices = [(Index(value = ["id"]))]) data class ReplaceRule( @PrimaryKey(autoGenerate = true) - val id: Int = 0, - val summary: String? = null, - val pattern: String? = null, - val replacement: String? = null, - val scope: String? = null, - val isEnabled: Boolean = true, - val isRegex: Boolean = true, - val order: Int = 0 + var id: Int = 0, + var name: String? = null, + var pattern: String? = null, + var replacement: String? = null, + var scope: String? = null, + var isEnabled: Boolean = true, + var isRegex: Boolean = true, + @ColumnInfo(name = "sortOrder") + var order: Int = 0 ) : Parcelable diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index cf4a2329d..db76d4916 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -1,7 +1,9 @@ package io.legado.app.ui.main +import android.Manifest import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.Menu import android.view.MenuItem import androidx.appcompat.app.ActionBarDrawerToggle @@ -9,18 +11,37 @@ import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import androidx.lifecycle.ViewModelProvider import com.google.android.material.navigation.NavigationView +import com.jayway.jsonpath.Configuration +import com.jayway.jsonpath.JsonPath +import com.jayway.jsonpath.Option +import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseActivity +import io.legado.app.constant.AppConst.APP_TAG +import io.legado.app.constant.AppConst.RC_IMPORT_YUEDU_DATA +import io.legado.app.data.entities.ReplaceRule +import io.legado.app.ui.replacerule.ReplaceRuleActivity import io.legado.app.ui.search.SearchActivity +import io.legado.app.utils.getSdPath +import io.legado.app.utils.readBool +import io.legado.app.utils.readInt +import io.legado.app.utils.readString import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.app_bar_main.* -import kotlinx.android.synthetic.main.view_titlebar.* +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.startActivity +import org.jetbrains.anko.uiThread +import pub.devrel.easypermissions.AfterPermissionGranted +import pub.devrel.easypermissions.EasyPermissions +import java.io.File +import java.lang.Exception class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { override val viewModel: MainViewModel get() = ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(MainViewModel::class.java) override val layoutID: Int get() = R.layout.activity_main + private val PERMISSONS = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) override fun onViewModelCreated(viewModel: MainViewModel, savedInstanceState: Bundle?) { fab.setOnClickListener { startActivity(Intent(this, SearchActivity::class.java)) } @@ -58,21 +79,17 @@ class MainActivity : BaseActivity(), NavigationV override fun onNavigationItemSelected(item: MenuItem): Boolean { // Handle navigation view item clicks here. when (item.itemId) { - R.id.nav_home -> { + R.id.nav_backup -> { // Handle the camera action } - R.id.nav_gallery -> { + R.id.nav_import -> { } - R.id.nav_slideshow -> { - - } - R.id.nav_tools -> { - - } - R.id.nav_share -> { + R.id.nav_import_old -> importYueDu() + R.id.nav_import_github -> { } + R.id.nav_replace_rule -> startActivity() R.id.nav_send -> { } @@ -81,4 +98,60 @@ class MainActivity : BaseActivity(), NavigationV drawerLayout.closeDrawer(GravityCompat.START) return true } + + /* + * import from YueDu backup data + * */ + @AfterPermissionGranted(RC_IMPORT_YUEDU_DATA) + fun importYueDu() { + if (!EasyPermissions.hasPermissions(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + EasyPermissions.requestPermissions(this, getString(R.string.perm_request_storage), RC_IMPORT_YUEDU_DATA, Manifest.permission.WRITE_EXTERNAL_STORAGE) + return + } + + val yuedu = File(getSdPath(), "YueDu") + val jsonPath = JsonPath.using(Configuration.builder() + .options(Option.SUPPRESS_EXCEPTIONS) + .build()) + + // Replace rules + val rFile = File(yuedu, "myBookReplaceRule.json") + val replaceRules = mutableListOf() + if (rFile.exists()) try { + val items: List> = jsonPath.parse(rFile.readText()).read("$.*") + for (item in items) { + val jsonItem = jsonPath.parse(item) + val rRule = ReplaceRule() + rRule.name = jsonItem.readString("$.replaceSummary") + rRule.pattern = jsonItem.readString("$.regex") + rRule.replacement = jsonItem.readString("$.replacement") + rRule.isRegex = jsonItem.readBool("$.isRegex") + rRule.scope = jsonItem.readString("$.useTo") + rRule.isEnabled = jsonItem.readBool("$.enable") + rRule.order = jsonItem.readInt("$.serialNumber") + replaceRules.add(rRule) + // Log.e(APP_TAG, rRule.toString()) + } + + doAsync { + App.db.replaceRuleDao().insert(*replaceRules.toTypedArray()) + val count = App.db.replaceRuleDao().all.size + val maxId = App.db.replaceRuleDao().maxOrder + uiThread { + Log.e(APP_TAG, "$count records were inserted to database, and max id is $maxId.") + } + } + + } catch (e: Exception) { + Log.e(APP_TAG, e.localizedMessage) + } + } + + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + // Forward results to EasyPermissions + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } + + } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt new file mode 100644 index 000000000..304000c53 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt @@ -0,0 +1,53 @@ +package io.legado.app.ui.replacerule + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.App +import io.legado.app.R +import io.legado.app.data.entities.ReplaceRule +import kotlinx.android.synthetic.main.activity_replace_rule.* +import org.jetbrains.anko.doAsync +import org.jetbrains.anko.toast + + +class ReplaceRuleActivity : AppCompatActivity() { + private lateinit var adapter: ReplaceRuleAdapter + private var rulesLiveData: LiveData>? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_replace_rule) + rv_replace_rule.layoutManager = LinearLayoutManager(this) + initRecyclerView() + initDataObservers() + } + + private fun initRecyclerView() { + rv_replace_rule.layoutManager = LinearLayoutManager(this) + adapter = ReplaceRuleAdapter(this) + adapter.onClickListener = object: ReplaceRuleAdapter.OnClickListener { + override fun update(rule: ReplaceRule) { + doAsync { App.db.replaceRuleDao().update(rule) } + } + override fun delete(rule: ReplaceRule) { + doAsync { App.db.replaceRuleDao().delete(rule) } + } + override fun edit(rule: ReplaceRule) { + toast("Edit function not implemented!") + } + } + rv_replace_rule.adapter = adapter + } + + private fun initDataObservers() { + rulesLiveData?.removeObservers(this) + rulesLiveData = LivePagedListBuilder(App.db.replaceRuleDao().observeAll(), 30).build() + rulesLiveData?.observe(this, Observer> { adapter.submitList(it) }) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt new file mode 100644 index 000000000..717e2bfff --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt @@ -0,0 +1,70 @@ +package io.legado.app.ui.replacerule + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isGone +import androidx.paging.PagedListAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import io.legado.app.R +import io.legado.app.data.entities.ReplaceRule +import kotlinx.android.synthetic.main.item_relace_rule.view.* +import org.jetbrains.anko.sdk27.listeners.onClick + + +class ReplaceRuleAdapter(context: Context) : + PagedListAdapter(DIFF_CALLBACK) { + + var onClickListener: OnClickListener? = null + + companion object { + + @JvmField + val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ReplaceRule, newItem: ReplaceRule): Boolean = + oldItem.id == newItem.id + + override fun areContentsTheSame(oldItem: ReplaceRule, newItem: ReplaceRule): Boolean = + oldItem.id == newItem.id + && oldItem.pattern == newItem.pattern + && oldItem.replacement == newItem.replacement + && oldItem.isRegex == newItem.isRegex + && oldItem.isEnabled == newItem.isEnabled + && oldItem.scope == newItem.scope + } + } + + init { + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { + return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_relace_rule, parent, false)) + } + + override fun onBindViewHolder(holder: MyViewHolder, pos: Int) { + getItem(pos)?.let { holder.bind(it, onClickListener, pos == itemCount - 1) } + } + + class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { + fun bind(rule: ReplaceRule, listener: OnClickListener?, hideDivider: Boolean) = with(itemView) { + cb_enable.text = rule.name + cb_enable.isChecked = rule.isEnabled + divider.isGone = hideDivider + iv_delete.onClick { listener?.delete(rule) } + iv_edit.onClick { listener?.edit(rule) } + cb_enable.onClick { + rule.isEnabled = cb_enable.isChecked + listener?.update(rule) + } + } + } + + interface OnClickListener { + fun update(rule: ReplaceRule) + fun delete(rule: ReplaceRule) + fun edit(rule: ReplaceRule) + } +} diff --git a/app/src/main/java/io/legado/app/utils/FileUtils.kt b/app/src/main/java/io/legado/app/utils/FileUtils.kt new file mode 100644 index 000000000..493347429 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/FileUtils.kt @@ -0,0 +1,5 @@ +package io.legado.app.utils + +import android.os.Environment + +fun getSdPath() = Environment.getExternalStorageDirectory().absolutePath diff --git a/app/src/main/java/io/legado/app/utils/MiscExtensions.kt b/app/src/main/java/io/legado/app/utils/MiscExtensions.kt new file mode 100644 index 000000000..24bf95c9a --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/MiscExtensions.kt @@ -0,0 +1,9 @@ +package io.legado.app.utils + +import com.jayway.jsonpath.ReadContext + +fun ReadContext.readString(path: String) = this.read(path, String::class.java) + +fun ReadContext.readBool(path: String) = this.read(path, Boolean::class.java) + +fun ReadContext.readInt(path: String) = this.read(path, Int::class.java) \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_ib_pre_round.xml b/app/src/main/res/drawable/bg_ib_pre_round.xml new file mode 100644 index 000000000..a370bb64b --- /dev/null +++ b/app/src/main/res/drawable/bg_ib_pre_round.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_clear_all.xml b/app/src/main/res/drawable/ic_clear_all.xml new file mode 100644 index 000000000..dfa860c00 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_all.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 000000000..2d249dde4 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_replace_rule.xml b/app/src/main/res/layout/activity_replace_rule.xml new file mode 100644 index 000000000..773e22242 --- /dev/null +++ b/app/src/main/res/layout/activity_replace_rule.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_relace_rule.xml b/app/src/main/res/layout/item_relace_rule.xml new file mode 100644 index 000000000..80295a529 --- /dev/null +++ b/app/src/main/res/layout/item_relace_rule.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index 9df2b3d76..3a491985e 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -5,29 +5,29 @@ + android:title="@string/menu_backup"/> + android:title="@string/menu_import"/> + android:title="@string/menu_import_old"/> + android:title="@string/menu_import_github"/> + android:title="@string/menu_replace_rule"/> #008577 #00574B #D81B60 + #222222 + #66666666 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c7def64d4..05315f8a9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,14 +7,17 @@ Navigation header Settings - Home - Gallery - Slideshow - Tools - Share + Home + 导入 + 导入阅读数据 + 导入Github数据 + 净化替换 Send 点击重试 正在加载 + 阅读需要访问存储卡权限: + 编辑 + 删除