Added replace rule

pull/31/head
atbest 6 years ago
parent 7441fcd156
commit 2f33336d74
  1. 5
      app/build.gradle
  2. 1
      app/src/main/AndroidManifest.xml
  3. 6
      app/src/main/java/io/legado/app/App.kt
  4. 4
      app/src/main/java/io/legado/app/constant/AppConst.kt
  5. 2
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  6. 49
      app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt
  7. 18
      app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt
  8. 93
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  9. 53
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  10. 70
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt
  11. 5
      app/src/main/java/io/legado/app/utils/FileUtils.kt
  12. 9
      app/src/main/java/io/legado/app/utils/MiscExtensions.kt
  13. 4
      app/src/main/res/drawable/bg_ib_pre_round.xml
  14. 17
      app/src/main/res/drawable/ic_clear_all.xml
  15. 14
      app/src/main/res/drawable/ic_edit.xml
  16. 30
      app/src/main/res/layout/activity_replace_rule.xml
  17. 57
      app/src/main/res/layout/item_relace_rule.xml
  18. 20
      app/src/main/res/menu/activity_main_drawer.xml
  19. 2
      app/src/main/res/values/colors.xml
  20. 13
      app/src/main/res/values/strings.xml

@ -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'

@ -39,6 +39,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ui.replacerule.ReplaceRuleActivity"/>
</application>
</manifest>

@ -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)
}
}

@ -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
}

@ -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
}

@ -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<Int, ReplaceRule>
@Query("SELECT id FROM replace_rules ORDER BY sortOrder ASC")
fun observeAllIds(): LiveData<List<Int>>
@get:Query("SELECT MAX(sortOrder) FROM replace_rules")
val maxOrder: Int
@get:Query("SELECT * FROM replace_rules ORDER BY sortOrder ASC")
val all: List<ReplaceRule>
@get:Query("SELECT * FROM replace_rules WHERE isEnabled = 1 ORDER BY sortOrder ASC")
val allEnabled: List<ReplaceRule>
@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<ReplaceRule>
@Query("SELECT * FROM replace_rules WHERE isEnabled = 1 AND scope LIKE '%' || :scope || '%'")
fun findEnabledByScope(scope: String): List<ReplaceRule>
@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)
}

@ -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

@ -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<MainDataBinding, MainViewModel>(), 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<MainDataBinding, MainViewModel>(), 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<ReplaceRuleActivity>()
R.id.nav_send -> {
}
@ -81,4 +98,60 @@ class MainActivity : BaseActivity<MainDataBinding, MainViewModel>(), 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<ReplaceRule>()
if (rFile.exists()) try {
val items: List<Map<String, Any>> = 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<String>, grantResults: IntArray) {
// Forward results to EasyPermissions
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
}
}

@ -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<PagedList<ReplaceRule>>? = 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<PagedList<ReplaceRule>> { adapter.submitList(it) })
}
}

@ -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<ReplaceRule, ReplaceRuleAdapter.MyViewHolder>(DIFF_CALLBACK) {
var onClickListener: OnClickListener? = null
companion object {
@JvmField
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ReplaceRule>() {
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)
}
}

@ -0,0 +1,5 @@
package io.legado.app.utils
import android.os.Environment
fun getSdPath() = Environment.getExternalStorageDirectory().absolutePath

@ -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)

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/transparent"/>
</selector>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#595757"
android:pathData="M14.434,20 L9.566,20 C7.535,20,5.863,18.665,5.761,16.961 L5.18,7.199 L4,7.199 L4,5.919 L8.402,5.919 L8.644,5.315 C8.957,4.526,9.827,4,10.813,4 L13.188,4 C14.172,4,15.043,4.526,15.355,5.313 L15.598,5.918 L20,5.918 L20,7.198 L18.82,7.198 L18.238,16.96 C18.137,18.665,16.466,20,14.434,20 Z M6.706,7.199 L7.283,16.898 C7.344,17.917,8.347,18.719,9.566,18.719 L14.433,18.719 C15.653,18.719,16.656,17.916,16.716,16.898 L17.293,7.199 L6.706,7.199 Z M10.01,5.919 L13.99,5.919 L13.91,5.718 C13.806,5.457,13.515,5.28,13.187,5.28 L10.812,5.28 C10.484,5.28,10.193,5.457,10.089,5.718 L10.01,5.919 Z" />
<path
android:fillColor="#595757"
android:pathData="M9.542,16.509 L9.127,8.83 L10.648,8.773 L11.064,16.449 L9.542,16.509 Z" />
<path
android:fillColor="#595757"
android:pathData="M14.457,16.509 L12.936,16.449 L13.352,8.773 L14.873,8.83 L14.457,16.509 Z" />
</vector>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#595757"
android:pathData="M5.6,20.009c-0.881,0-1.6-0.789-1.6-1.758V5.749C4,4.78,4.719,3.991,5.6,3.991h10.62 c0.882,0,1.599,0.789,1.599,1.758v1.258h-1.483l-0.076-1.258c0-0.122-0.062-0.172-0.062-0.172L5.6,5.582 c0.002,0.015-0.039,0.069-0.039,0.167v12.502c0,0.107,0.051,0.164,0.063,0.172l10.596-0.005c-0.002-0.014,0.039-0.067,0.039-0.167 l0.016-4.739h1.469l0.075,4.739c0,0.969-0.717,1.758-1.599,1.758H5.6z" />
<path
android:fillColor="#595757"
android:pathData="M 12.549 13.354 L 13.738 12.323 L 13.738 12.323 L 18.967 7.553 L 20 8.646 L 14.658 13.514 L 13.54 14.515 Z" />
</vector>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.legado.app.ui.search.SearchActivity">
<io.legado.app.ui.widget.TitleBar
android:id="@+id/titleBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:title="净化替换"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_replace_rule"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleBar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.0"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingEnd="8dp">
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/cb_enable"
android:text="Rule Name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:textSize="16sp"
android:textColor="@color/text_default"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_delete"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/bg_ib_pre_round"
android:contentDescription="@string/edit"
android:src="@drawable/ic_edit"
app:tint="@color/text_default"
app:layout_constraintEnd_toStartOf="@id/iv_delete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:background="@drawable/bg_ib_pre_round"
android:contentDescription="@string/delete"
android:src="@drawable/ic_clear_all"
app:tint="@color/text_default"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/divider"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -5,29 +5,29 @@
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_home"
android:id="@+id/nav_backup"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_home"/>
android:title="@string/menu_backup"/>
<item
android:id="@+id/nav_gallery"
android:id="@+id/nav_import"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery"/>
android:title="@string/menu_import"/>
<item
android:id="@+id/nav_slideshow"
android:id="@+id/nav_import_old"
android:icon="@drawable/ic_menu_slideshow"
android:title="@string/menu_slideshow"/>
android:title="@string/menu_import_old"/>
<item
android:id="@+id/nav_tools"
android:id="@+id/nav_import_github"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools"/>
android:title="@string/menu_import_github"/>
</group>
<item android:title="Communicate">
<menu>
<item
android:id="@+id/nav_share"
android:id="@+id/nav_replace_rule"
android:icon="@drawable/ic_menu_share"
android:title="@string/menu_share"/>
android:title="@string/menu_replace_rule"/>
<item
android:id="@+id/nav_send"
android:icon="@drawable/ic_menu_send"

@ -3,6 +3,8 @@
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="text_default">#222222</color>
<color name="divider">#66666666</color>
</resources>

@ -7,14 +7,17 @@
<string name="nav_header_desc">Navigation header</string>
<string name="action_settings">Settings</string>
<string name="menu_home">Home</string>
<string name="menu_gallery">Gallery</string>
<string name="menu_slideshow">Slideshow</string>
<string name="menu_tools">Tools</string>
<string name="menu_share">Share</string>
<string name="menu_backup">Home</string>
<string name="menu_import">导入</string>
<string name="menu_import_old">导入阅读数据</string>
<string name="menu_import_github">导入Github数据</string>
<string name="menu_replace_rule">净化替换</string>
<string name="menu_send">Send</string>
<string name="dynamic_click_retry">点击重试</string>
<string name="dynamic_loading">正在加载</string>
<string name="perm_request_storage">阅读需要访问存储卡权限:</string>
<string name="edit">编辑</string>
<string name="delete">删除</string>
</resources>

Loading…
Cancel
Save