diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bc92ac136..23f5edd36 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -98,6 +98,7 @@ + diff --git a/app/src/main/assets/updateLog.md b/app/src/main/assets/updateLog.md index 48dc30c5b..a067a90e9 100644 --- a/app/src/main/assets/updateLog.md +++ b/app/src/main/assets/updateLog.md @@ -3,6 +3,9 @@ * 旧版数据导入教程: * 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 +**2020/01/10 +* 合并KKL369提交的代码 + **2020/01/08** * 导入本地源不再需要存储权限 diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index 1ea320505..ff5ee0b7d 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -86,7 +86,7 @@ object BookHelp { } private fun getBookFolder(book: Book): String { - val bookFolder = formatFolderName(book.name + book.bookUrl) + val bookFolder = formatFolderName(book.name + MD5Utils.md5Encode16(book.bookUrl)) return "${getBookCachePath()}${File.separator}$bookFolder" } @@ -108,6 +108,9 @@ object BookHelp { ?: "" } + /** + * 找到相似度最高的章节 + */ fun getDurChapterIndexByChapterTitle( title: String?, index: Int, diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt index b3605e3e9..5335a6830 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt @@ -15,7 +15,7 @@ import io.legado.app.R * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) */ class ThemeStore @SuppressLint("CommitPrefEdits") -private constructor(private val mContext: Context) : ThemeStorePrefKeys, ThemeStoreInterface { +private constructor(private val mContext: Context) : ThemeStoreInterface { private val mEditor: SharedPreferences.Editor init { diff --git a/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt b/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt index 4fd4540fc..c934ef176 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt @@ -3,8 +3,7 @@ package io.legado.app.lib.theme /** * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid) */ -internal interface ThemeStorePrefKeys { - companion object { +object ThemeStorePrefKeys { const val CONFIG_PREFS_KEY_DEFAULT = "app_themes" const val IS_CONFIGURED_KEY = "is_configured" @@ -27,5 +26,4 @@ internal interface ThemeStorePrefKeys { const val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar" const val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar" const val KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark" - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index aeb83a093..cc63c93d4 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -26,9 +26,7 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.theme.ATH import io.legado.app.ui.book.source.debug.BookSourceDebugActivity import io.legado.app.ui.widget.KeyboardToolPop -import io.legado.app.utils.GSON -import io.legado.app.utils.applyTint -import io.legado.app.utils.getViewModel +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_book_source_edit.* import org.jetbrains.anko.displayMetrics import org.jetbrains.anko.startActivity @@ -87,17 +85,11 @@ class BookSourceEditActivity : } } R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } - R.id.menu_share_str -> { - GSON.toJson(getSource())?.let { sourceStr -> - try { - val textIntent = Intent(Intent.ACTION_SEND) - textIntent.type = "text/plain" - textIntent.putExtra(Intent.EXTRA_TEXT, sourceStr) - startActivity(Intent.createChooser(textIntent, "Source Share")) - } catch (e: Exception) { - toast(R.string.can_not_share) - } - } + R.id.menu_share_str -> GSON.toJson(getSource())?.let { sourceStr -> + shareText(getString(R.string.share_book_source), sourceStr) + } + R.id.menu_share_qr -> GSON.toJson(getSource())?.let { sourceStr -> + shareWithQr(getString(R.string.share_book_source), sourceStr) } R.id.menu_rule_summary -> { try { diff --git a/app/src/main/java/io/legado/app/ui/importbook/ImportBookActivity.kt b/app/src/main/java/io/legado/app/ui/importbook/ImportBookActivity.kt new file mode 100644 index 000000000..8cbce52f8 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/importbook/ImportBookActivity.kt @@ -0,0 +1,19 @@ +package io.legado.app.ui.importbook + +import android.os.Bundle +import io.legado.app.R +import io.legado.app.base.VMBaseActivity +import io.legado.app.utils.getViewModel + + +class ImportBookActivity : VMBaseActivity(R.layout.activity_import_book) { + + override val viewModel: ImportBookViewModel + get() = getViewModel(ImportBookViewModel::class.java) + + + override fun onActivityCreated(savedInstanceState: Bundle?) { + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt b/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt new file mode 100644 index 000000000..1865f1be8 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt @@ -0,0 +1,10 @@ +package io.legado.app.ui.importbook + +import android.app.Application +import io.legado.app.base.BaseViewModel + + +class ImportBookViewModel(application: Application) : BaseViewModel(application) { + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 037364296..24b49a5c6 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -1,7 +1,6 @@ package io.legado.app.ui.rss.read import android.annotation.SuppressLint -import android.content.Intent import android.os.Bundle import android.view.KeyEvent import android.view.Menu @@ -15,8 +14,8 @@ import io.legado.app.lib.theme.DrawableUtils import io.legado.app.lib.theme.primaryTextColor import io.legado.app.utils.NetworkUtils import io.legado.app.utils.getViewModel +import io.legado.app.utils.shareText import kotlinx.android.synthetic.main.activity_rss_read.* -import org.jetbrains.anko.toast class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read), ReadRssViewModel.CallBack { @@ -25,6 +24,7 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r get() = getViewModel(ReadRssViewModel::class.java) private var starMenuItem: MenuItem? = null + private var ttsMenuItem: MenuItem? = null override fun onActivityCreated(savedInstanceState: Bundle?) { viewModel.callBack = this @@ -37,6 +37,7 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.rss_read, menu) starMenuItem = menu.findItem(R.id.menu_rss_star) + ttsMenuItem = menu.findItem(R.id.menu_aloud) upStarMenu() return super.onCompatCreateOptionsMenu(menu) } @@ -47,6 +48,7 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r R.id.menu_share_it -> viewModel.rssArticle?.let { shareText("链接分享", it.link) } + R.id.menu_aloud -> readAloud() } return super.onCompatOptionsItemSelected(item) } @@ -97,6 +99,17 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r DrawableUtils.setTint(starMenuItem?.icon, primaryTextColor) } + override fun upTtsMenu(isPlaying: Boolean) { + if (isPlaying) { + ttsMenuItem?.setIcon(R.drawable.ic_stop_black_24dp) + ttsMenuItem?.setTitle(R.string.aloud_stop) + } else { + ttsMenuItem?.setIcon(R.drawable.ic_volume_up) + ttsMenuItem?.setTitle(R.string.read_aloud) + } + DrawableUtils.setTint(ttsMenuItem?.icon, primaryTextColor) + } + override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean { when (keyCode) { KeyEvent.KEYCODE_BACK -> { @@ -121,14 +134,10 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r return super.onKeyUp(keyCode, event) } - private fun shareText(title: String, text: String) { - try { - val textIntent = Intent(Intent.ACTION_SEND) - textIntent.type = "text/plain" - textIntent.putExtra(Intent.EXTRA_TEXT, text) - startActivity(Intent.createChooser(textIntent, title)) - } catch (e: Exception) { - toast(R.string.can_not_share) + private fun readAloud() { + webView.evaluateJavascript("document.documentElement.outerHTML") { + viewModel.readAloud(it) } } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index 98355d7d8..83a88091e 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt @@ -2,21 +2,28 @@ package io.legado.app.ui.rss.read import android.app.Application import android.content.Intent +import android.speech.tts.TextToSpeech +import android.speech.tts.UtteranceProgressListener import androidx.lifecycle.MutableLiveData import io.legado.app.App +import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssSource import io.legado.app.model.Rss import io.legado.app.model.analyzeRule.AnalyzeUrl +import kotlinx.coroutines.launch +import java.util.* -class ReadRssViewModel(application: Application) : BaseViewModel(application) { +class ReadRssViewModel(application: Application) : BaseViewModel(application), + TextToSpeech.OnInitListener { var callBack: CallBack? = null var rssSource: RssSource? = null var rssArticle: RssArticle? = null val contentLiveData = MutableLiveData() val urlLiveData = MutableLiveData() var star = false + var textToSpeech: TextToSpeech = TextToSpeech(context, this) fun initData(intent: Intent) { execute { @@ -91,7 +98,48 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application) { } } + override fun onInit(status: Int) { + launch { + if (status == TextToSpeech.SUCCESS) { + textToSpeech.language = Locale.CHINA + textToSpeech.setOnUtteranceProgressListener(TTSUtteranceListener()) + } else { + toast(R.string.tts_init_failed) + } + } + } + + fun readAloud(text: String) { + textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, "rss") + } + + override fun onCleared() { + super.onCleared() + textToSpeech.stop() + textToSpeech.shutdown() + } + + /** + * 朗读监听 + */ + private inner class TTSUtteranceListener : UtteranceProgressListener() { + + override fun onStart(s: String) { + callBack?.upTtsMenu(true) + } + + override fun onDone(s: String) { + callBack?.upTtsMenu(false) + } + + override fun onError(s: String) { + + } + + } + interface CallBack { fun upStarMenu() + fun upTtsMenu(isPlaying: Boolean) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt index d1704848e..1ae70df5d 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt @@ -22,9 +22,7 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.theme.ATH import io.legado.app.ui.rss.source.debug.RssSourceDebugActivity import io.legado.app.ui.widget.KeyboardToolPop -import io.legado.app.utils.GSON -import io.legado.app.utils.applyTint -import io.legado.app.utils.getViewModel +import io.legado.app.utils.* import kotlinx.android.synthetic.main.activity_rss_source_edit.* import org.jetbrains.anko.displayMetrics import org.jetbrains.anko.startActivity @@ -103,6 +101,12 @@ class RssSourceEditActivity : } } R.id.menu_paste_source -> viewModel.pasteSource { upRecyclerView(it) } + R.id.menu_share_str -> GSON.toJson(getRssSource())?.let { sourceStr -> + shareText(getString(R.string.share_rss_source), sourceStr) + } + R.id.menu_share_qr -> GSON.toJson(getRssSource())?.let { sourceStr -> + shareWithQr(getString(R.string.share_rss_source), sourceStr) + } } return super.onCompatOptionsItemSelected(item) } diff --git a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt index a0a4170d8..6ff51d379 100644 --- a/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt @@ -88,16 +88,14 @@ class FontSelectDialog : DialogFragment(), override fun onMenuItemClick(item: MenuItem?): Boolean { when (item?.itemId) { R.id.menu_default -> { - val pf = parentFragment - if (pf is CallBack) { - if ("" != pf.curFontPath) { - pf.selectFile("") + (parentFragment as? CallBack)?.let { + if (it.curFontPath != "") { + it.selectFile("") } } - val activity = activity - if (activity is CallBack) { - if ("" != activity.curFontPath) { - activity.selectFile("") + (activity as? CallBack)?.let { + if (it.curFontPath != "") { + it.selectFile("") } } dismiss() @@ -131,7 +129,7 @@ class FontSelectDialog : DialogFragment(), @SuppressLint("DefaultLocale") private fun getFontFiles(uri: Uri) { launch(IO) { - DocumentFile.fromTreeUri(requireContext(), uri)?.listFiles()?.forEach { file -> + DocumentFile.fromTreeUri(App.INSTANCE, uri)?.listFiles()?.forEach { file -> if (file.name?.toLowerCase()?.matches(".*\\.[ot]tf".toRegex()) == true) { DocumentUtils.readBytes(App.INSTANCE, file.uri)?.let { FileHelp.getFile(fontFolder + file.name).writeBytes(it) @@ -169,17 +167,15 @@ class FontSelectDialog : DialogFragment(), } override fun onClick(file: File) { - file.absolutePath.let { - val pf = parentFragment - if (pf is CallBack) { - if (it != pf.curFontPath) { - pf.selectFile(it) + file.absolutePath.let { path -> + (parentFragment as? CallBack)?.let { + if (it.curFontPath != path) { + it.selectFile(path) } } - val activity = activity - if (activity is CallBack) { - if (it != activity.curFontPath) { - activity.selectFile(it) + (activity as? CallBack)?.let { + if (it.curFontPath != path) { + it.selectFile(path) } } } @@ -187,15 +183,9 @@ class FontSelectDialog : DialogFragment(), } override fun curFilePath(): String { - val pf = parentFragment - if (pf is CallBack) { - return pf.curFontPath - } - val activity = activity - if (activity is CallBack) { - return activity.curFontPath - } - return "" + return (parentFragment as? CallBack)?.curFontPath + ?: (activity as? CallBack)?.curFontPath + ?: "" } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt index f47a0a2a8..2ab88b481 100644 --- a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt @@ -1,13 +1,25 @@ package io.legado.app.utils +import android.annotation.SuppressLint import android.content.Context +import android.content.Intent import android.content.res.ColorStateList +import android.graphics.Bitmap import android.graphics.drawable.Drawable import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider import androidx.core.content.edit +import cn.bingoogolapple.qrcode.zxing.QRCodeEncoder +import com.google.zxing.EncodeHintType +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import io.legado.app.BuildConfig +import io.legado.app.R import org.jetbrains.anko.defaultSharedPreferences +import org.jetbrains.anko.toast +import java.io.File +import java.io.FileOutputStream fun Context.getPrefBoolean(key: String, defValue: Boolean = false) = defaultSharedPreferences.getBoolean(key, defValue) @@ -48,7 +60,8 @@ fun Context.getCompatColor(@ColorRes id: Int): Int = ContextCompat.getColor(this fun Context.getCompatDrawable(@DrawableRes id: Int): Drawable? = ContextCompat.getDrawable(this, id) -fun Context.getCompatColorStateList(@ColorRes id: Int): ColorStateList? = ContextCompat.getColorStateList(this, id) +fun Context.getCompatColorStateList(@ColorRes id: Int): ColorStateList? = + ContextCompat.getColorStateList(this, id) fun Context.getStatusBarHeight(): Int { val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") @@ -60,6 +73,48 @@ fun Context.getNavigationBarHeight(): Int { return resources.getDimensionPixelSize(resourceId) } +fun Context.shareText(title: String, text: String) { + try { + val textIntent = Intent(Intent.ACTION_SEND) + textIntent.type = "text/plain" + textIntent.putExtra(Intent.EXTRA_TEXT, text) + startActivity(Intent.createChooser(textIntent, title)) + } catch (e: Exception) { + toast(R.string.can_not_share) + } +} + +@SuppressLint("SetWorldReadable") +fun Context.shareWithQr(title: String, text: String) { + QRCodeEncoder.HINTS[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.L + val bitmap = QRCodeEncoder.syncEncodeQRCode(text, 600) + QRCodeEncoder.HINTS[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H + if (bitmap == null) { + toast(R.string.text_too_long_qr_error) + } else { + try { + val file = File(externalCacheDir, "qr.png") + val fOut = FileOutputStream(file) + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut) + fOut.flush() + fOut.close() + file.setReadable(true, false) + val contentUri = FileProvider.getUriForFile( + this, + "${BuildConfig.APPLICATION_ID}.fileProvider", + file + ) + val intent = Intent(Intent.ACTION_SEND) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.putExtra(Intent.EXTRA_STREAM, contentUri) + intent.type = "image/png" + startActivity(Intent.createChooser(intent, title)) + } catch (e: Exception) { + toast(e.localizedMessage ?: "ERROR") + } + } +} + val Context.isNightTheme: Boolean get() = getPrefBoolean("isNightTheme") @@ -67,4 +122,4 @@ val Context.isTransparentStatusBar: Boolean get() = getPrefBoolean("transparentStatusBar", true) val Context.isShowRSS: Boolean - get() = getPrefBoolean("showRss", true) \ No newline at end of file + get() = getPrefBoolean("showRss", true) \ No newline at end of file diff --git a/app/src/main/res/layout/activity_import_book.xml b/app/src/main/res/layout/activity_import_book.xml new file mode 100644 index 000000000..d829e291c --- /dev/null +++ b/app/src/main/res/layout/activity_import_book.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/rss_read.xml b/app/src/main/res/menu/rss_read.xml index 15463f64d..f32d01d2e 100644 --- a/app/src/main/res/menu/rss_read.xml +++ b/app/src/main/res/menu/rss_read.xml @@ -14,4 +14,10 @@ android:icon="@drawable/ic_menu_share" app:showAsAction="ifRoom" /> + + \ No newline at end of file diff --git a/app/src/main/res/menu/source_edit.xml b/app/src/main/res/menu/source_edit.xml index ec8739c11..2fd9678c4 100644 --- a/app/src/main/res/menu/source_edit.xml +++ b/app/src/main/res/menu/source_edit.xml @@ -35,7 +35,7 @@ app:showAsAction="never" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5036723df..3ceda2af3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -579,5 +579,8 @@ 上一句 下一句 其它目录 + 文字太多,生成二维码失败 + 分享RSS源 + 分享书源