Merge pull request #29 from gedoor/master

up
pull/379/head
口口吕 5 years ago committed by GitHub
commit 384aea9339
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/src/main/AndroidManifest.xml
  2. 9
      app/src/main/assets/updateLog.md
  3. 1
      app/src/main/java/io/legado/app/constant/EventBus.kt
  4. 14
      app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt
  5. 2
      app/src/main/java/io/legado/app/help/permission/Request.kt
  6. 21
      app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
  7. 6
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt
  8. 16
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt
  9. 7
      app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt
  10. 22
      app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt
  11. 3
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  12. 24
      app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt
  13. 12
      app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt
  14. 2
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  15. 36
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt
  16. 58
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt
  17. 4
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  18. 38
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
  19. 35
      app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt
  20. 50
      app/src/main/res/layout/activity_import_book.xml
  21. 452
      app/src/main/res/layout/dialog_read_aloud.xml
  22. 26
      app/src/main/res/layout/dialog_text_view.xml
  23. 2
      app/src/main/res/layout/fragment_bookshelf.xml
  24. 2
      app/src/main/res/layout/view_read_menu.xml
  25. 5
      app/src/main/res/values/strings.xml

@ -23,6 +23,7 @@
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
android:theme="@style/AppTheme.Light"
tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute">
<!--主入口-->
@ -91,7 +92,7 @@
<!--书籍详情页-->
<activity
android:name=".ui.book.info.BookInfoActivity"
android:launchMode="singleTask" />
android:launchMode="singleTop" />
<!--书籍信息编辑-->
<activity
android:name="io.legado.app.ui.book.info.edit.BookInfoEditActivity"

@ -2,11 +2,18 @@
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
* 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
**2020/03/13**
* 更改书架控件,ViewPager2替换回2.0使用的ViewPager,解决下拉不流畅问题
* 修复点击作者搜索后,打开的详情页还是原来的书籍的bug
* 修改朗读菜单
**2020/03/12**
* 导入本地添加需要权限模式
**2020/03/11**
* 修复调节上边距时下边距一起动的bug
* 适配沚水的web阅读 by 六月
* 分组管理页面调整 by yangyxd
* 导入本地添加需要权限模式
**2020/03/10**
* 优化文字选择菜单弹出位置

@ -11,7 +11,6 @@ object EventBus {
const val TIME_CHANGED = "timeChanged"
const val UP_CONFIG = "upConfig"
const val OPEN_CHAPTER = "openChapter"
const val REPLACE = "replace"
const val AUDIO_SUB_TITLE = "audioSubTitle"
const val AUDIO_STATE = "audioState"
const val AUDIO_PROGRESS = "audioProgress"

@ -1,8 +1,11 @@
package io.legado.app.help.permission
import android.os.Build
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import org.jetbrains.anko.startActivity
import java.util.ArrayList
class PermissionsCompat private constructor() {
@ -12,6 +15,17 @@ class PermissionsCompat private constructor() {
RequestManager.pushRequest(request)
}
companion object {
// 检查权限, 如果已经拥有返回 true
fun check(activity: AppCompatActivity, vararg permissions: String): Boolean {
var request: Request = Request(activity)
var pers = ArrayList<String>()
pers.addAll(listOf(*permissions))
var data = request.getDeniedPermissions(pers.toTypedArray())
return data == null;
}
}
class Builder {
private val request: Request

@ -102,7 +102,7 @@ internal class Request : OnRequestPermissionsResultCallback {
deniedCallback = null
}
private fun getDeniedPermissions(permissions: Array<String>?): Array<String>? {
fun getDeniedPermissions(permissions: Array<String>?): Array<String>? {
if (permissions != null) {
val deniedPermissionList = ArrayList<String>()
for (permission in permissions) {

@ -29,7 +29,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
}
}
private var ttsIsSuccess: Boolean = false
private var ttsInitFinish = false
override fun onCreate() {
super.onCreate()
@ -42,22 +42,23 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
clearTTS()
}
@Synchronized
override fun onInit(status: Int) {
launch {
if (status == TextToSpeech.SUCCESS) {
textToSpeech?.language = Locale.CHINA
textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener())
ttsIsSuccess = true
play()
} else {
if (status == TextToSpeech.SUCCESS) {
textToSpeech?.language = Locale.CHINA
textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener())
ttsInitFinish = true
play()
} else {
launch {
toast(R.string.tts_init_failed)
}
}
}
@Suppress("DEPRECATION")
@Synchronized
override fun play() {
if (contentList.isEmpty() || !ttsIsSuccess) {
if (contentList.isEmpty() || !ttsInitFinish) {
return
}
if (requestFocus()) {

@ -6,12 +6,14 @@ import android.view.MenuItem
import androidx.appcompat.widget.PopupMenu
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookGroup
import io.legado.app.help.ItemTouchCallback
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
@ -64,6 +66,10 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
recycler_view.addItemDecoration(VerticalDivider(this))
adapter = ArrangeBookAdapter(this, this)
recycler_view.adapter = adapter
val itemTouchCallback = ItemTouchCallback()
itemTouchCallback.onItemTouchCallbackListener = adapter
itemTouchCallback.isCanDrag = true
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view)
select_action_bar.setMainActionText(R.string.move_to_group)
select_action_bar.inflateMenu(R.menu.arrange_book_sel)
select_action_bar.setOnMenuItemClickListener(this)

@ -7,14 +7,13 @@ import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookGroup
import io.legado.app.utils.gone
import io.legado.app.utils.visible
import io.legado.app.help.ItemTouchCallback
import kotlinx.android.synthetic.main.item_arrange_book.view.*
import org.jetbrains.anko.sdk27.listeners.onClick
class ArrangeBookAdapter(context: Context, val callBack: CallBack) :
SimpleRecyclerAdapter<Book>(context, R.layout.item_arrange_book) {
SimpleRecyclerAdapter<Book>(context, R.layout.item_arrange_book),
ItemTouchCallback.OnItemTouchCallbackListener {
val groupRequestCode = 12
private val selectedBooks: HashSet<Book> = hashSetOf()
var actionItem: Book? = null
@ -56,12 +55,7 @@ class ArrangeBookAdapter(context: Context, val callBack: CallBack) :
override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList<Any>) {
with(holder.itemView) {
tv_name.text = item.name
// tv_name.text = if (item.author.isEmpty()) {
// item.name
// } else {
// "${item.name}(${item.author})"
// }
tv_author.text = item.author // resources.getString(R.string.author_show, item.author)
tv_author.text = item.author
tv_author.visibility = if (item.author.isEmpty()) View.GONE else View.VISIBLE
tv_group_s.text = getGroupName(item.group)
checkbox.isChecked = selectedBooks.contains(item)
@ -115,7 +109,7 @@ class ArrangeBookAdapter(context: Context, val callBack: CallBack) :
groupNames.add(it.groupName)
}
}
return groupNames;
return groupNames
}
private fun getGroupName(groupId: Int): String {

@ -36,6 +36,7 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
private var durChapterIndex = 0
private lateinit var mLayoutManager: UpLinearLayoutManager
private var tocLiveData: LiveData<List<BookChapter>>? = null
private var scrollToDurChapter = false
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
viewModel.chapterCallBack = this
@ -74,7 +75,6 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
book?.let {
durChapterIndex = it.durChapterIndex
tv_current_chapter_info.text = it.durChapterTitle
mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)
initCacheFileNames(it)
}
}
@ -85,7 +85,10 @@ class ChapterListFragment : VMBaseFragment<ChapterListViewModel>(R.layout.fragme
tocLiveData = App.db.bookChapterDao().observeByBook(viewModel.bookUrl)
tocLiveData?.observe(viewLifecycleOwner, Observer {
adapter.setItems(it)
mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)
if (!scrollToDurChapter) {
mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)
scrollToDurChapter = true
}
})
}

@ -8,6 +8,7 @@ import android.os.Bundle
import android.provider.DocumentsContract
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LiveData
@ -17,6 +18,8 @@ import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.help.AppConfig
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.ui.filechooser.FileChooserDialog
import io.legado.app.ui.filechooser.FilePicker
import io.legado.app.ui.widget.SelectActionBar
@ -80,6 +83,7 @@ class ImportBookActivity : VMBaseActivity<ImportBookViewModel>(R.layout.activity
}
}
})
}
private fun initEvent() {
@ -124,6 +128,24 @@ class ImportBookActivity : VMBaseActivity<ImportBookViewModel>(R.layout.activity
subDocs.clear()
path = it
}
} ?: let {
// 没有权限就显示一个授权提示和按钮
if (PermissionsCompat.check(this, *Permissions.Group.STORAGE)) {
hint_per.visibility = View.GONE
} else {
hint_per.visibility = View.VISIBLE
tv_request_per.onClick {
PermissionsCompat.Builder(this)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
hint_per.visibility = View.GONE
initData()
upRootDoc()
}
.request()
}
}
}
upPath()
}

@ -735,9 +735,6 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
}
}
}
observeEvent<String>(EventBus.REPLACE) {
ReplaceEditDialog().show(supportFragmentManager, "replaceEditDialog")
}
observeEvent<Boolean>(PreferKey.keepLight) {
upScreenTimeOut()
}

@ -21,7 +21,6 @@ import io.legado.app.utils.observeEvent
import io.legado.app.utils.putPrefBoolean
import kotlinx.android.synthetic.main.dialog_read_aloud.*
import org.jetbrains.anko.sdk27.listeners.onClick
import org.jetbrains.anko.sdk27.listeners.onLongClick
class ReadAloudDialog : BaseDialogFragment() {
var callBack: CallBack? = null
@ -34,7 +33,7 @@ class ReadAloudDialog : BaseDialogFragment() {
it.windowManager?.defaultDisplay?.getMetrics(dm)
}
dialog?.window?.let {
it.setBackgroundDrawableResource(R.color.transparent)
it.setBackgroundDrawableResource(R.color.background)
it.decorView.setPadding(0, 0, 0, 0)
val attr = it.attributes
attr.dimAmount = 0.0f
@ -54,10 +53,10 @@ class ReadAloudDialog : BaseDialogFragment() {
}
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
ll_bottom_bg.setBackgroundColor(requireContext().bottomBackground)
root_view.setBackgroundColor(requireContext().bottomBackground)
initOnChange()
initData()
initOnClick()
initEvent()
}
private fun initData() {
@ -101,22 +100,19 @@ class ReadAloudDialog : BaseDialogFragment() {
})
}
private fun initOnClick() {
iv_menu.onClick { callBack?.showMenuBar(); dismiss() }
iv_other_config.onClick {
private fun initEvent() {
ll_main_menu.onClick { callBack?.showMenuBar(); dismiss() }
ll_setting.onClick {
ReadAloudConfigDialog().show(childFragmentManager, "readAloudConfigDialog")
}
tv_pre.onClick { ReadBook.moveToPrevChapter(upContent = true, toLast = false) }
tv_next.onClick { ReadBook.moveToNextChapter(true) }
iv_stop.onClick { ReadAloud.stop(requireContext()); dismiss() }
iv_play_pause.onClick { callBack?.onClickReadAloud() }
iv_play_prev.onClick { ReadAloud.prevParagraph(requireContext()) }
iv_play_prev.onLongClick {
ReadBook.moveToPrevChapter(upContent = true, toLast = false)
true
}
iv_play_next.onClick { ReadAloud.nextParagraph(requireContext()) }
iv_play_next.onLongClick { ReadBook.moveToNextChapter(true); true }
fabToc.onClick { callBack?.openChapterList() }
fabBack.onClick { callBack?.finish() }
ll_catalog.onClick { callBack?.openChapterList() }
ll_to_backstage.onClick { callBack?.finish() }
}
private fun upPlayState() {

@ -31,7 +31,7 @@ object FilePicker {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectDirIntent()
val intent = createSelectDirIntent()
activity.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
@ -68,7 +68,7 @@ object FilePicker {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectDirIntent()
val intent = createSelectDirIntent()
fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
e.printStackTrace()
@ -106,7 +106,7 @@ object FilePicker {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectFileIntent()
val intent = createSelectFileIntent()
intent.type = type//设置类型
activity.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
@ -147,7 +147,7 @@ object FilePicker {
0 -> default?.invoke()
1 -> {
try {
val intent = getSelectFileIntent()
val intent = createSelectFileIntent()
intent.type = type//设置类型
fragment.startActivityForResult(intent, requestCode)
} catch (e: java.lang.Exception) {
@ -168,14 +168,14 @@ object FilePicker {
}.show()
}
private fun getSelectFileIntent(): Intent {
private fun createSelectFileIntent(): Intent {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return intent
}
private fun getSelectDirIntent(): Intent {
private fun createSelectDirIntent(): Intent {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return intent

@ -78,7 +78,7 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode)
if (!BuildConfig.DEBUG) {
val log = String(assets.open("updateLog.md").readBytes())
TextDialog.show(supportFragmentManager, log, TextDialog.MD)
TextDialog.show(supportFragmentManager, log, TextDialog.MD, 5000)
}
}
}

@ -1,36 +0,0 @@
package io.legado.app.ui.main.bookshelf
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.main.bookshelf.books.BooksFragment
class BookshelfAdapter(fragment: Fragment, val callBack: CallBack) :
FragmentStateAdapter(fragment) {
private val ids = hashSetOf<Long>()
override fun getItemCount(): Int {
return callBack.groupSize
}
override fun getItemId(position: Int): Long {
return callBack.getGroup(position).groupId.toLong()
}
override fun containsItem(itemId: Long): Boolean {
return ids.contains(itemId)
}
override fun createFragment(position: Int): Fragment {
val groupId = callBack.getGroup(position).groupId
ids.add(groupId.toLong())
return BooksFragment.newInstance(position, groupId)
}
interface CallBack {
val groupSize: Int
fun getGroup(position: Int): BookGroup
}
}

@ -7,10 +7,12 @@ import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseFragment
@ -29,6 +31,7 @@ import io.legado.app.ui.book.download.DownloadActivity
import io.legado.app.ui.book.group.GroupManageDialog
import io.legado.app.ui.book.local.ImportBookActivity
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.main.bookshelf.books.BooksFragment
import io.legado.app.ui.widget.text.AutoCompleteTextView
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.*
@ -42,15 +45,14 @@ import org.jetbrains.anko.startActivity
class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_bookshelf),
TabLayout.OnTabSelectedListener,
SearchView.OnQueryTextListener,
GroupManageDialog.CallBack,
BookshelfAdapter.CallBack {
GroupManageDialog.CallBack {
override val viewModel: BookshelfViewModel
get() = getViewModel(BookshelfViewModel::class.java)
private lateinit var bookshelfAdapter: BookshelfAdapter
private var bookGroupLiveData: LiveData<List<BookGroup>>? = null
private val bookGroups = mutableListOf<BookGroup>()
private val fragmentMap = hashMapOf<Int, Fragment>()
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
setSupportToolbar(toolbar)
@ -79,26 +81,17 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
}
}
override val groupSize: Int
get() = bookGroups.size
override fun getGroup(position: Int): BookGroup {
return bookGroups[position]
}
private val selectedGroup: BookGroup
get() = bookGroups[view_pager_bookshelf.currentItem]
private fun initView() {
ATH.applyEdgeEffectColor(view_pager_bookshelf)
tab_layout.isTabIndicatorFullWidth = false
tab_layout.tabMode = TabLayout.MODE_SCROLLABLE
tab_layout.setSelectedTabIndicatorColor(requireContext().accentColor)
ATH.applyEdgeEffectColor(view_pager_bookshelf)
bookshelfAdapter = BookshelfAdapter(this, this)
view_pager_bookshelf.adapter = bookshelfAdapter
TabLayoutMediator(tab_layout, view_pager_bookshelf) { tab, position ->
tab.text = bookGroups[position].groupName
}.attach()
tab_layout.setupWithViewPager(view_pager_bookshelf)
view_pager_bookshelf.offscreenPageLimit = 1
view_pager_bookshelf.adapter = TabFragmentPageAdapter(childFragmentManager)
}
private fun initBookGroupData() {
@ -119,7 +112,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
bookGroups.add(AppConst.bookGroupAudio)
}
bookGroups.addAll(it)
bookshelfAdapter.notifyDataSetChanged()
view_pager_bookshelf.adapter?.notifyDataSetChanged()
tab_layout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select()
tab_layout.addOnTabSelectedListener(this)
}
@ -149,7 +142,7 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
if (AppConfig.bookGroupAllShow) {
bookGroups.add(0, AppConst.bookGroupAll)
}
bookshelfAdapter.notifyDataSetChanged()
view_pager_bookshelf.adapter?.notifyDataSetChanged()
}
}
@ -212,4 +205,31 @@ class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_b
putPrefInt(PreferKey.saveTabPosition, it)
}
}
private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getPageTitle(position: Int): CharSequence? {
return bookGroups[position].groupName
}
override fun getItemPosition(`object`: Any): Int {
return POSITION_NONE
}
override fun getItem(position: Int): Fragment {
val group = bookGroups[position]
var fragment = fragmentMap[group.groupId]
if (fragment == null) {
fragment = BooksFragment.newInstance(position, group.groupId)
fragmentMap[group.groupId] = fragment
}
return fragment
}
override fun getCount(): Int {
return bookGroups.size
}
}
}

@ -246,8 +246,8 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
@SuppressLint("SetJavaScriptEnabled")
private fun readAloud() {
if (viewModel.textToSpeech.isSpeaking) {
viewModel.textToSpeech.stop()
if (viewModel.textToSpeech?.isSpeaking == true) {
viewModel.textToSpeech?.stop()
upTtsMenu(false)
} else {
web_view.settings.javaScriptEnabled = true

@ -35,7 +35,9 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
val contentLiveData = MutableLiveData<String>()
val urlLiveData = MutableLiveData<AnalyzeUrl>()
var star = false
var textToSpeech: TextToSpeech = TextToSpeech(context, this)
var textToSpeech: TextToSpeech? = null
private var ttsInitFinish = false
private var ttsText = ""
fun initData(intent: Intent) {
execute {
@ -143,28 +145,42 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
}
}
@Synchronized
override fun onInit(status: Int) {
launch {
if (status == TextToSpeech.SUCCESS) {
textToSpeech.language = Locale.CHINA
textToSpeech.setOnUtteranceProgressListener(TTSUtteranceListener())
} else {
if (status == TextToSpeech.SUCCESS) {
textToSpeech?.language = Locale.CHINA
textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener())
ttsInitFinish = true
play()
} else {
launch {
toast(R.string.tts_init_failed)
}
}
}
@Synchronized
private fun play() {
if (!ttsInitFinish) return
textToSpeech?.stop()
ttsText.split("\n", " ", "  ").forEach {
textToSpeech?.speak(it, TextToSpeech.QUEUE_ADD, null, "rss")
}
}
fun readAloud(text: String) {
textToSpeech.stop()
text.split("\n", " ", "  ").forEach {
textToSpeech.speak(it, TextToSpeech.QUEUE_ADD, null, "rss")
ttsText = text
textToSpeech?.let {
play()
} ?: let {
textToSpeech = TextToSpeech(context, this)
}
}
override fun onCleared() {
super.onCleared()
textToSpeech.stop()
textToSpeech.shutdown()
textToSpeech?.stop()
textToSpeech?.shutdown()
}
/**

@ -5,29 +5,40 @@ import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import io.legado.app.R
import io.legado.app.base.BaseDialogFragment
import kotlinx.android.synthetic.main.dialog_text_view.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import ru.noties.markwon.Markwon
class TextDialog : DialogFragment() {
class TextDialog : BaseDialogFragment() {
companion object {
const val MD = 1
fun show(fragmentManager: FragmentManager, content: String?, mode: Int = 0) {
fun show(
fragmentManager: FragmentManager,
content: String?,
mode: Int = 0,
time: Long = 0
) {
TextDialog().apply {
val bundle = Bundle()
bundle.putString("content", content)
bundle.putInt("mode", mode)
bundle.putLong("time", time)
arguments = bundle
isCancelable = false
}.show(fragmentManager, "textDialog")
}
}
private var time = 0L
override fun onStart() {
super.onStart()
val dm = DisplayMetrics()
@ -43,8 +54,7 @@ class TextDialog : DialogFragment() {
return inflater.inflate(R.layout.dialog_text_view, container)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
arguments?.let {
val content = it.getString("content") ?: ""
when (it.getInt("mode")) {
@ -57,8 +67,21 @@ class TextDialog : DialogFragment() {
}
else -> text_view.text = content
}
time = it.getLong("time", 0L)
}
if (time > 0) {
badge_view.setBadgeCount((time / 1000).toInt())
launch {
while (time > 0) {
delay(1000)
time -= 1000
badge_view.setBadgeCount((time / 1000).toInt())
if (time <= 0) {
dialog?.setCancelable(true)
}
}
}
}
}
}

@ -1,20 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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="@string/book_local" />
<!--path-->
<LinearLayout
android:id="@+id/layTop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/titleBar"
android:background="@color/background"
android:minHeight="36dp"
android:padding="8dp"
@ -53,12 +56,51 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="@id/layTop"
app:layout_constraintBottom_toTopOf="@id/select_action_bar"
android:layout_height="0dp"
android:layout_weight="1" />
<io.legado.app.ui.widget.SelectActionBar
android:id="@+id/select_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" />
<LinearLayout
android:id="@+id/hint_per"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:orientation="vertical"
android:padding="36dp"
android:gravity="center"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:text="@string/tip_local_perm_request_storage"
android:layout_marginBottom="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TextView>
<io.legado.app.ui.widget.text.AccentBgTextView
android:id="@+id/tv_request_per"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:minHeight="42dp"
android:gravity="center"
android:text="@string/request_permission"
android:textSize="14sp"
app:radius="30dp" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,245 +1,333 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"
android:background="@color/background_menu"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
<TextView
android:id="@+id/tv_pre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="@string/previous_chapter"
android:textColor="@color/tv_text_default"
android:textSize="14sp" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabToc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:contentDescription="@string/chapter_list"
android:src="@drawable/ic_toc"
<ImageView
android:id="@+id/iv_play_prev"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:tooltipText="@string/prev_sentence"
android:contentDescription="@string/prev_sentence"
android:src="@drawable/ic_skip_previous"
android:tint="@color/tv_text_default"
android:tooltipText="@string/chapter_list"
app:backgroundTint="@color/background_menu"
app:elevation="2dp"
app:fabSize="mini"
app:pressedTranslationZ="2dp"
tools:ignore="UnusedAttribute" />
<Space
<ImageView
android:id="@+id/iv_play_pause"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:tooltipText="@string/audio_play"
android:contentDescription="@string/audio_play"
android:src="@drawable/ic_play_24dp"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
<ImageView
android:id="@+id/iv_stop"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/stop"
android:tooltipText="@string/stop"
android:src="@drawable/ic_stop_black_24dp"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
<ImageView
android:id="@+id/iv_play_next"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/next_sentence"
android:tooltipText="@string/next_sentence"
android:src="@drawable/ic_skip_next"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
<View
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="1dp"
android:layout_weight="1" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabBack"
<TextView
android:id="@+id/tv_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:contentDescription="@string/to_backstage"
android:src="@drawable/ic_visibility_off"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:text="@string/next_chapter"
android:textColor="@color/tv_text_default"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="6dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:tooltipText="@string/set_timer"
android:contentDescription="@string/set_timer"
android:src="@drawable/ic_time_add_24dp"
android:tint="@color/tv_text_default"
android:tooltipText="@string/to_backstage"
app:backgroundTint="@color/background_menu"
app:elevation="2dp"
app:fabSize="mini"
app:pressedTranslationZ="2dp"
tools:ignore="UnusedAttribute" />
<Space
<io.legado.app.lib.theme.view.ATESeekBar
android:id="@+id/seek_timer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
android:layout_weight="1"
android:max="60" />
</LinearLayout>
<TextView
android:id="@+id/tv_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/timer_m" />
<View style="@style/Style.Shadow.Bottom" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_bottom_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background_menu"
android:padding="16dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_tts_SpeechRate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_menu"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:tooltipText="@string/menu"
android:contentDescription="@string/menu"
android:src="@drawable/ic_menu"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
android:layout_gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/iv_play_prev"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:tooltipText="@string/prev_sentence"
android:contentDescription="@string/prev_sentence"
android:src="@drawable/ic_skip_previous"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
<ImageView
android:id="@+id/iv_play_pause"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:tooltipText="@string/audio_play"
android:contentDescription="@string/audio_play"
android:src="@drawable/ic_play_24dp"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
<ImageView
android:id="@+id/iv_stop"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/stop"
android:tooltipText="@string/stop"
android:src="@drawable/ic_stop_black_24dp"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
<ImageView
android:id="@+id/iv_play_next"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/next_sentence"
android:tooltipText="@string/next_sentence"
android:src="@drawable/ic_skip_next"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
<View
<TextView
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/iv_other_config"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:tooltipText="@string/other_aloud_setting"
android:contentDescription="@string/other_aloud_setting"
android:src="@drawable/ic_settings"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:text="@string/read_aloud_speed"
android:textColor="@color/tv_text_default"
android:textSize="14sp" />
<io.legado.app.lib.theme.view.ATESwitch
android:id="@+id/cb_tts_follow_sys"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="@string/flow_sys"
android:text="@string/flow_sys"
tools:ignore="RtlHardcoded" />
</LinearLayout>
<LinearLayout
<io.legado.app.lib.theme.view.ATESeekBar
android:id="@+id/seek_tts_SpeechRate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:padding="8dp">
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:max="45" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:baselineAligned="false"
android:orientation="horizontal">
<!--目录按钮-->
<LinearLayout
android:id="@+id/ll_catalog"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/chapter_list"
android:focusable="true"
android:orientation="vertical"
android:paddingBottom="7dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:tooltipText="@string/set_timer"
android:contentDescription="@string/set_timer"
android:src="@drawable/ic_time_add_24dp"
android:tint="@color/tv_text_default"
tools:ignore="UnusedAttribute" />
<io.legado.app.lib.theme.view.ATESeekBar
android:id="@+id/seek_timer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:max="60" />
android:contentDescription="@string/chapter_list"
android:src="@drawable/ic_toc"
app:tint="@color/tv_text_default"
tools:ignore="NestedWeights" />
<TextView
android:id="@+id/tv_timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/timer_m" />
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:text="@string/chapter_list"
android:textColor="@color/tv_text_default"
android:textSize="12sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.8dp"
android:background="@color/divider" />
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
<!--调节按钮-->
<LinearLayout
android:id="@+id/ll_main_menu"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/read_aloud"
android:focusable="true"
android:orientation="vertical"
android:paddingBottom="7dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:contentDescription="@string/main_menu"
android:src="@drawable/ic_menu"
app:tint="@color/tv_text_default"
tools:ignore="NestedWeights" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:text="@string/main_menu"
android:textColor="@color/tv_text_default"
android:textSize="12sp" />
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
<!--界面按钮-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:id="@+id/ll_to_backstage"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/interface_setting"
android:focusable="true"
android:orientation="vertical"
android:paddingBottom="7dp">
<LinearLayout
android:id="@+id/ll_tts_SpeechRate"
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:contentDescription="@string/to_backstage"
android:src="@drawable/ic_visibility_off"
app:tint="@color/tv_text_default"
tools:ignore="NestedWeights" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="horizontal"
android:padding="8dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:text="@string/read_aloud_speed"
android:textColor="@color/tv_text_default"
android:textSize="14sp" />
<io.legado.app.lib.theme.view.ATESwitch
android:id="@+id/cb_tts_follow_sys"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:contentDescription="@string/flow_sys"
android:text="@string/flow_sys"
tools:ignore="RtlHardcoded" />
</LinearLayout>
<io.legado.app.lib.theme.view.ATESeekBar
android:id="@+id/seek_tts_SpeechRate"
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:text="@string/to_backstage"
android:textColor="@color/tv_text_default"
android:textSize="12sp" />
</LinearLayout>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
<!--设置按钮-->
<LinearLayout
android:id="@+id/ll_setting"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/setting"
android:focusable="true"
android:orientation="vertical"
android:paddingBottom="7dp">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp"
android:max="45" />
android:layout_height="0dp"
android:layout_weight="1"
android:contentDescription="@string/aloud_config"
android:src="@drawable/ic_settings"
app:tint="@color/tv_text_default"
tools:ignore="NestedWeights" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="3dp"
android:text="@string/aloud_config"
android:textColor="@color/tv_text_default"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

@ -1,7 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<io.legado.app.ui.widget.text.InertiaScrollTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text_view"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp"
android:textIsSelectable="true" />
xmlns:app="http://schemas.android.com/apk/res-auto">
<io.legado.app.ui.widget.text.InertiaScrollTextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="12dp"
android:textIsSelectable="true" />
<io.legado.app.ui.widget.text.BadgeView
android:id="@+id/badge_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:visibility="invisible"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -14,7 +14,7 @@
app:contentLayout="@layout/view_tab_layout_min"
app:layout_constraintTop_toTopOf="parent" />
<androidx.viewpager2.widget.ViewPager2
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager_bookshelf"
android:layout_width="match_parent"
android:layout_height="0dp"

@ -210,7 +210,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">

@ -628,7 +628,6 @@
<string name="more_menu">更多菜单</string>
<string name="reduce"></string>
<string name="plus"></string>
<string name="other_aloud_setting">其它朗读设置</string>
<string name="system_typeface">系统内置字体样式</string>
<string name="delete_book_file">删除源文件</string>
<string name="default1">预设一</string>
@ -646,5 +645,7 @@
<string name="bar_elevation">导航栏阴影</string>
<string name="bar_elevation_s">当前阴影大小(elevation): %s</string>
<string name="btn_default_s">默认</string>
<string name="main_menu">主菜单</string>
<string name="request_permission">点击授予权限</string>
<string name="tip_local_perm_request_storage">阅读需要访问存储卡权限,请点击下方的"授予权限"按钮,或前往“设置”—“应用权限”—打开所需权限。如果授予权限后仍然不正常,请点击右上角的“选择文件夹”,使用系统文件夹选择器。</string>
</resources>

Loading…
Cancel
Save