commit
179bcff88f
@ -1,69 +0,0 @@ |
|||||||
{ |
|
||||||
"project_info": { |
|
||||||
"project_number": "453392274790", |
|
||||||
"firebase_url": "https://legado-fca69.firebaseio.com", |
|
||||||
"project_id": "legado-fca69", |
|
||||||
"storage_bucket": "legado-fca69.appspot.com" |
|
||||||
}, |
|
||||||
"client": [ |
|
||||||
{ |
|
||||||
"client_info": { |
|
||||||
"mobilesdk_app_id": "1:453392274790:android:1d2b1eefbe0e78cff624a7", |
|
||||||
"android_client_info": { |
|
||||||
"package_name": "io.legado.app" |
|
||||||
} |
|
||||||
}, |
|
||||||
"oauth_client": [ |
|
||||||
{ |
|
||||||
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", |
|
||||||
"client_type": 3 |
|
||||||
} |
|
||||||
], |
|
||||||
"api_key": [ |
|
||||||
{ |
|
||||||
"current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA" |
|
||||||
} |
|
||||||
], |
|
||||||
"services": { |
|
||||||
"appinvite_service": { |
|
||||||
"other_platform_oauth_client": [ |
|
||||||
{ |
|
||||||
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", |
|
||||||
"client_type": 3 |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
} |
|
||||||
}, |
|
||||||
{ |
|
||||||
"client_info": { |
|
||||||
"mobilesdk_app_id": "1:453392274790:android:c4eac14b1410eec5f624a7", |
|
||||||
"android_client_info": { |
|
||||||
"package_name": "io.legado.app.debug" |
|
||||||
} |
|
||||||
}, |
|
||||||
"oauth_client": [ |
|
||||||
{ |
|
||||||
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", |
|
||||||
"client_type": 3 |
|
||||||
} |
|
||||||
], |
|
||||||
"api_key": [ |
|
||||||
{ |
|
||||||
"current_key": "AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA" |
|
||||||
} |
|
||||||
], |
|
||||||
"services": { |
|
||||||
"appinvite_service": { |
|
||||||
"other_platform_oauth_client": [ |
|
||||||
{ |
|
||||||
"client_id": "453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com", |
|
||||||
"client_type": 3 |
|
||||||
} |
|
||||||
] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
], |
|
||||||
"configuration_version": "1" |
|
||||||
} |
|
@ -1,56 +1,62 @@ |
|||||||
[ |
[ |
||||||
{ |
{ |
||||||
"enable": true, |
"enable": true, |
||||||
"name": "数字 分隔符 标题名称", |
"name": "数字 分隔符 标题名称", |
||||||
"rule": "^[ \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,30}$", |
"rule": "^[ \\t]{0,4}\\d{1,5}[\\,\\., 、\\-].{1,30}$", |
||||||
"serialNumber": 0 |
"serialNumber": 0 |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"enable": true, |
"enable": true, |
||||||
"name": "特殊符号 序号 标题", |
"name": "目录", |
||||||
"rule": "^[ \\t]{0,4}[\\[〈「『〖〔《(【\\(]?[第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[\\.:: \f\t].{0,30}$", |
"rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", |
||||||
"serialNumber": 1 |
"serialNumber": 1 |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"enable": true, |
"enable": false, |
||||||
"name": "特殊符号 标题", |
"name": "目录(不匹配行前空白)", |
||||||
"rule": "^[ \\t]{0,4}[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$", |
"rule": "^(?<=\\s)(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", |
||||||
"serialNumber": 2 |
"serialNumber": 2 |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"enable": false, |
"enable": false, |
||||||
"name": "特殊符号 标题(不匹配空白字符)", |
"name": "目录(去简介)", |
||||||
"rule": "(?<=\\s)[\\[〈「『〖〔《(【\\(☆★].{1,30}[\\)】)》〕〗』」〉\\]]?\\s{0,4}$", |
"rule": "^(?<=\\s)(?:前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", |
||||||
"serialNumber": 3 |
"serialNumber": 3 |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"enable": true, |
"enable": false, |
||||||
"name": "Chapter/Section/Part 序号 标题", |
"name": "目录(古典小说备用)", |
||||||
"rule": "^[ \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art)\\s{0,4}\\d{1,4}.{0,30}$", |
"rule": "^[ \\t]{0,4}(?:前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|场(?![和合比电是])|篇(?!张))).{0,30}$", |
||||||
"serialNumber": 4 |
"serialNumber": 4 |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"enable": true, |
"enable": true, |
||||||
"name": "正文 标题/序号", |
"name": "Chapter/Section/Part 序号 标题", |
||||||
"rule": "^[ \\t]{0,4}正文\\s{1,4}.{0,20}$", |
"rule": "^[ \\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art)\\s{0,4}\\d{1,4}.{0,30}$", |
||||||
"serialNumber": 5 |
"serialNumber": 5 |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"enable": true, |
"enable": true, |
||||||
"name": "目录", |
"name": "正文 标题/序号", |
||||||
"rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", |
"rule": "^[ \\t]{0,4}正文\\s{1,4}.{0,20}$", |
||||||
"serialNumber": 6 |
"serialNumber": 6 |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"enable": false, |
"enable": true, |
||||||
"name": "目录(不匹配行前空白,正文不出现双标题)", |
"name": "特殊符号 序号 标题", |
||||||
"rule": "^(?<=\\s)(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|篇(?!张))).{0,30}$", |
"rule": "^[ \\t]{0,4}[〈〖〔【][第卷][\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节][\\.:: \f\t].{0,30}$", |
||||||
"serialNumber": 7 |
"serialNumber": 7 |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"enable": false, |
"enable": true, |
||||||
"name": "目录(古典小说备用)", |
"name": "特殊符号 标题", |
||||||
"rule": "^[ \\t]{0,4}(?:(?:内容|文章)?简介|前言|序章|楔子|正文(?!完)|[Cc]hapter|[Ss]ection|终章|后记|尾声|番外|第?\\s{0,4}[\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?!分)|场(?![和合比电是])|篇(?!张))).{0,30}$", |
"rule": "^[ \\t]{0,4}[〈〖〔【☆★].{1,30}[】〕〗〉]?\\s{0,4}$", |
||||||
"serialNumber": 8 |
"serialNumber": 8 |
||||||
|
}, |
||||||
|
{ |
||||||
|
"enable":false, |
||||||
|
"name": "特殊符号 标题(不匹配空白字符)", |
||||||
|
"rule": "(?<=\\s)[〈〖〔【☆★].{1,30}[】〕〗〉]?\\s{0,4}$", |
||||||
|
"serialNumber": 9 |
||||||
} |
} |
||||||
] |
] |
||||||
|
@ -1,34 +0,0 @@ |
|||||||
package io.legado.app.help |
|
||||||
|
|
||||||
import androidx.recyclerview.widget.ListUpdateCallback |
|
||||||
import androidx.recyclerview.widget.RecyclerView |
|
||||||
import io.legado.app.base.adapter.ItemViewHolder |
|
||||||
|
|
||||||
class FirstTopListUpCallback : ListUpdateCallback { |
|
||||||
var firstInsert = -1 |
|
||||||
lateinit var adapter: RecyclerView.Adapter<ItemViewHolder> |
|
||||||
|
|
||||||
override fun onChanged(position: Int, count: Int, payload: Any?) { |
|
||||||
adapter.notifyItemRangeChanged(position, count, payload) |
|
||||||
} |
|
||||||
|
|
||||||
override fun onMoved(fromPosition: Int, toPosition: Int) { |
|
||||||
if (toPosition == 0) { |
|
||||||
firstInsert = 0 |
|
||||||
} |
|
||||||
adapter.notifyItemMoved(fromPosition, toPosition) |
|
||||||
} |
|
||||||
|
|
||||||
override fun onInserted(position: Int, count: Int) { |
|
||||||
if (firstInsert == -1 || firstInsert > position) { |
|
||||||
firstInsert = position |
|
||||||
} |
|
||||||
adapter.notifyItemRangeInserted(position, count) |
|
||||||
} |
|
||||||
|
|
||||||
override fun onRemoved(position: Int, count: Int) { |
|
||||||
adapter.notifyItemRangeRemoved(position, count) |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,173 @@ |
|||||||
|
package io.legado.app.ui.book.read |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.app.SearchManager |
||||||
|
import android.content.Context |
||||||
|
import android.content.Intent |
||||||
|
import android.content.pm.ResolveInfo |
||||||
|
import android.net.Uri |
||||||
|
import android.os.Build |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.Menu |
||||||
|
import android.view.ViewGroup |
||||||
|
import android.widget.PopupWindow |
||||||
|
import androidx.annotation.RequiresApi |
||||||
|
import androidx.appcompat.view.SupportMenuInflater |
||||||
|
import androidx.appcompat.view.menu.MenuBuilder |
||||||
|
import androidx.appcompat.view.menu.MenuItemImpl |
||||||
|
import androidx.appcompat.widget.PopupMenu |
||||||
|
import androidx.core.view.size |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import io.legado.app.R |
||||||
|
import io.legado.app.base.adapter.ItemViewHolder |
||||||
|
import io.legado.app.base.adapter.SimpleRecyclerAdapter |
||||||
|
import io.legado.app.utils.isAbsUrl |
||||||
|
import io.legado.app.utils.sendToClip |
||||||
|
import io.legado.app.utils.visible |
||||||
|
import kotlinx.android.synthetic.main.item_fillet_text.view.* |
||||||
|
import kotlinx.android.synthetic.main.popup_action_menu.view.* |
||||||
|
import org.jetbrains.anko.sdk27.listeners.onClick |
||||||
|
import org.jetbrains.anko.share |
||||||
|
import org.jetbrains.anko.toast |
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi") |
||||||
|
class TextActionMenu(private val context: Context, private val callBack: CallBack) : |
||||||
|
PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) { |
||||||
|
|
||||||
|
init { |
||||||
|
@SuppressLint("InflateParams") |
||||||
|
contentView = LayoutInflater.from(context).inflate(R.layout.popup_action_menu, null) |
||||||
|
|
||||||
|
isTouchable = true |
||||||
|
isOutsideTouchable = false |
||||||
|
isFocusable = false |
||||||
|
|
||||||
|
initRecyclerView() |
||||||
|
} |
||||||
|
|
||||||
|
private fun initRecyclerView() = with(contentView) { |
||||||
|
val adapter = Adapter(context) |
||||||
|
recycler_view.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) |
||||||
|
recycler_view.adapter = adapter |
||||||
|
val menu = MenuBuilder(context) |
||||||
|
SupportMenuInflater(context).inflate(R.menu.content_select_action, menu) |
||||||
|
adapter.setItems(menu.visibleItems) |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||||
|
val popupMenu = PopupMenu(context, iv_menu_more) |
||||||
|
onInitializeMenu(popupMenu.menu) |
||||||
|
if (popupMenu.menu.size > 0) { |
||||||
|
iv_menu_more.visible() |
||||||
|
popupMenu.setOnMenuItemClickListener { item -> |
||||||
|
item.intent?.let { |
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||||
|
it.putExtra(Intent.EXTRA_PROCESS_TEXT, callBack.selectedText) |
||||||
|
context.startActivity(it) |
||||||
|
} |
||||||
|
} |
||||||
|
this@TextActionMenu.dismiss() |
||||||
|
true |
||||||
|
} |
||||||
|
} |
||||||
|
iv_menu_more.onClick { |
||||||
|
popupMenu.show() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
inner class Adapter(context: Context) : |
||||||
|
SimpleRecyclerAdapter<MenuItemImpl>(context, R.layout.item_text) { |
||||||
|
|
||||||
|
override fun convert( |
||||||
|
holder: ItemViewHolder, |
||||||
|
item: MenuItemImpl, |
||||||
|
payloads: MutableList<Any> |
||||||
|
) { |
||||||
|
with(holder.itemView) { |
||||||
|
text_view.text = item.title |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun registerListener(holder: ItemViewHolder) { |
||||||
|
holder.itemView.onClick { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
if (!callBack.onMenuItemSelected(it.itemId)) { |
||||||
|
onMenuItemSelected(it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun onMenuItemSelected(item: MenuItemImpl) { |
||||||
|
when (item.itemId) { |
||||||
|
R.id.menu_copy -> context.sendToClip(callBack.selectedText) |
||||||
|
R.id.menu_share_str -> context.share(callBack.selectedText) |
||||||
|
R.id.menu_browser -> { |
||||||
|
try { |
||||||
|
val intent = if (callBack.selectedText.isAbsUrl()) { |
||||||
|
Intent(Intent.ACTION_VIEW).apply { |
||||||
|
data = Uri.parse(callBack.selectedText) |
||||||
|
} |
||||||
|
} else { |
||||||
|
Intent(Intent.ACTION_WEB_SEARCH).apply { |
||||||
|
putExtra(SearchManager.QUERY, callBack.selectedText) |
||||||
|
} |
||||||
|
} |
||||||
|
context.startActivity(intent) |
||||||
|
} catch (e: Exception) { |
||||||
|
e.printStackTrace() |
||||||
|
context.toast(e.localizedMessage ?: "ERROR") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
callBack.onMenuActionFinally() |
||||||
|
} |
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.M) |
||||||
|
private fun createProcessTextIntent(): Intent { |
||||||
|
return Intent() |
||||||
|
.setAction(Intent.ACTION_PROCESS_TEXT) |
||||||
|
.setType("text/plain") |
||||||
|
} |
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.M) |
||||||
|
private fun getSupportedActivities(): List<ResolveInfo> { |
||||||
|
return context.packageManager |
||||||
|
.queryIntentActivities(createProcessTextIntent(), 0) |
||||||
|
} |
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.M) |
||||||
|
private fun createProcessTextIntentForResolveInfo(info: ResolveInfo): Intent { |
||||||
|
return createProcessTextIntent() |
||||||
|
.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false) |
||||||
|
.setClassName(info.activityInfo.packageName, info.activityInfo.name) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Start with a menu Item order value that is high enough |
||||||
|
* so that your "PROCESS_TEXT" menu items appear after the |
||||||
|
* standard selection menu items like Cut, Copy, Paste. |
||||||
|
*/ |
||||||
|
@RequiresApi(Build.VERSION_CODES.M) |
||||||
|
private fun onInitializeMenu(menu: Menu) { |
||||||
|
// Start with a menu Item order value that is high enough |
||||||
|
// so that your "PROCESS_TEXT" menu items appear after the |
||||||
|
// standard selection menu items like Cut, Copy, Paste. |
||||||
|
var menuItemOrder = 100 |
||||||
|
for (resolveInfo in getSupportedActivities()) { |
||||||
|
menu.add( |
||||||
|
Menu.NONE, Menu.NONE, |
||||||
|
menuItemOrder++, resolveInfo.loadLabel(context.packageManager) |
||||||
|
).intent = createProcessTextIntentForResolveInfo(resolveInfo) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface CallBack { |
||||||
|
val selectedText: String |
||||||
|
|
||||||
|
fun onMenuItemSelected(itemId: Int): Boolean |
||||||
|
|
||||||
|
fun onMenuActionFinally() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,259 @@ |
|||||||
|
package io.legado.app.ui.book.read.config |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.content.Context |
||||||
|
import android.os.Bundle |
||||||
|
import android.util.DisplayMetrics |
||||||
|
import android.view.LayoutInflater |
||||||
|
import android.view.MenuItem |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import androidx.appcompat.widget.Toolbar |
||||||
|
import androidx.fragment.app.FragmentManager |
||||||
|
import androidx.lifecycle.LiveData |
||||||
|
import androidx.lifecycle.Observer |
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper |
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager |
||||||
|
import androidx.recyclerview.widget.RecyclerView |
||||||
|
import io.legado.app.App |
||||||
|
import io.legado.app.R |
||||||
|
import io.legado.app.base.BaseDialogFragment |
||||||
|
import io.legado.app.base.adapter.ItemViewHolder |
||||||
|
import io.legado.app.base.adapter.SimpleRecyclerAdapter |
||||||
|
import io.legado.app.constant.Theme |
||||||
|
import io.legado.app.data.entities.TxtTocRule |
||||||
|
import io.legado.app.help.ItemTouchCallback |
||||||
|
import io.legado.app.lib.dialogs.alert |
||||||
|
import io.legado.app.lib.dialogs.cancelButton |
||||||
|
import io.legado.app.lib.dialogs.customView |
||||||
|
import io.legado.app.lib.dialogs.okButton |
||||||
|
import io.legado.app.model.localBook.AnalyzeTxtFile |
||||||
|
import io.legado.app.ui.widget.recycler.VerticalDivider |
||||||
|
import io.legado.app.utils.applyTint |
||||||
|
import kotlinx.android.synthetic.main.dialog_toc_regex.* |
||||||
|
import kotlinx.android.synthetic.main.dialog_toc_regex_edit.view.* |
||||||
|
import kotlinx.android.synthetic.main.item_toc_regex.view.* |
||||||
|
import kotlinx.coroutines.Dispatchers.IO |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
import org.jetbrains.anko.sdk27.listeners.onClick |
||||||
|
import java.util.* |
||||||
|
|
||||||
|
|
||||||
|
class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener { |
||||||
|
|
||||||
|
private lateinit var adapter: TocRegexAdapter |
||||||
|
private var tocRegexLiveData: LiveData<List<TxtTocRule>>? = null |
||||||
|
var selectedName: String? = null |
||||||
|
private var durRegex: String? = null |
||||||
|
|
||||||
|
override fun onStart() { |
||||||
|
super.onStart() |
||||||
|
val dm = DisplayMetrics() |
||||||
|
activity?.windowManager?.defaultDisplay?.getMetrics(dm) |
||||||
|
dialog?.window?.setLayout((dm.widthPixels * 0.9).toInt(), (dm.heightPixels * 0.8).toInt()) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onCreateView( |
||||||
|
inflater: LayoutInflater, |
||||||
|
container: ViewGroup?, |
||||||
|
savedInstanceState: Bundle? |
||||||
|
): View? { |
||||||
|
return inflater.inflate(R.layout.dialog_toc_regex, container) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||||
|
super.onViewCreated(view, savedInstanceState) |
||||||
|
durRegex = arguments?.getString("tocRegex") |
||||||
|
tool_bar.setTitle(R.string.txt_toc_regex) |
||||||
|
tool_bar.inflateMenu(R.menu.txt_toc_regex) |
||||||
|
tool_bar.menu.applyTint(requireContext(), Theme.getTheme()) |
||||||
|
tool_bar.setOnMenuItemClickListener(this) |
||||||
|
initView() |
||||||
|
initData() |
||||||
|
} |
||||||
|
|
||||||
|
private fun initView() { |
||||||
|
adapter = TocRegexAdapter(requireContext()) |
||||||
|
recycler_view.layoutManager = LinearLayoutManager(requireContext()) |
||||||
|
recycler_view.addItemDecoration(VerticalDivider(requireContext())) |
||||||
|
recycler_view.adapter = adapter |
||||||
|
val itemTouchCallback = ItemTouchCallback() |
||||||
|
itemTouchCallback.onItemTouchCallbackListener = adapter |
||||||
|
itemTouchCallback.isCanDrag = true |
||||||
|
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) |
||||||
|
tv_cancel.onClick { |
||||||
|
dismiss() |
||||||
|
} |
||||||
|
tv_ok.onClick { |
||||||
|
adapter.getItems().forEach { tocRule -> |
||||||
|
if (selectedName == tocRule.name) { |
||||||
|
val callBack = activity as? CallBack |
||||||
|
callBack?.onTocRegexDialogResult(tocRule.rule) |
||||||
|
dismiss() |
||||||
|
return@onClick |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun initData() { |
||||||
|
tocRegexLiveData?.removeObservers(viewLifecycleOwner) |
||||||
|
tocRegexLiveData = App.db.txtTocRule().observeAll() |
||||||
|
tocRegexLiveData?.observe(viewLifecycleOwner, Observer { tocRules -> |
||||||
|
initSelectedName(tocRules) |
||||||
|
adapter.setItems(tocRules) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
private fun initSelectedName(tocRules: List<TxtTocRule>) { |
||||||
|
if (selectedName == null && durRegex != null) { |
||||||
|
tocRules.forEach { |
||||||
|
if (durRegex == it.rule) { |
||||||
|
selectedName = it.name |
||||||
|
return@forEach |
||||||
|
} |
||||||
|
} |
||||||
|
if (selectedName == null) { |
||||||
|
selectedName = "" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem?): Boolean { |
||||||
|
when (item?.itemId) { |
||||||
|
R.id.menu_add -> editRule() |
||||||
|
R.id.menu_default -> importDefault() |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
private fun importDefault() { |
||||||
|
launch(IO) { |
||||||
|
AnalyzeTxtFile.getDefaultRules().let { |
||||||
|
App.db.txtTocRule().insert(*it.toTypedArray()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressLint("InflateParams") |
||||||
|
private fun editRule(rule: TxtTocRule? = null) { |
||||||
|
val tocRule = rule?.copy() ?: TxtTocRule() |
||||||
|
requireContext().alert(titleResource = R.string.txt_toc_regex) { |
||||||
|
var rootView: View? = null |
||||||
|
customView { |
||||||
|
LayoutInflater.from(requireContext()) |
||||||
|
.inflate(R.layout.dialog_toc_regex_edit, null).apply { |
||||||
|
rootView = this |
||||||
|
tv_rule_name.setText(tocRule.name) |
||||||
|
tv_rule_regex.setText(tocRule.rule) |
||||||
|
} |
||||||
|
} |
||||||
|
okButton { |
||||||
|
rootView?.apply { |
||||||
|
tocRule.name = tv_rule_name.text.toString() |
||||||
|
tocRule.rule = tv_rule_regex.text.toString() |
||||||
|
saveRule(tocRule, rule) |
||||||
|
} |
||||||
|
} |
||||||
|
cancelButton() |
||||||
|
}.show().applyTint() |
||||||
|
} |
||||||
|
|
||||||
|
private fun saveRule(rule: TxtTocRule, oldRule: TxtTocRule? = null) { |
||||||
|
launch(IO) { |
||||||
|
if (rule.serialNumber < 0) { |
||||||
|
rule.serialNumber = adapter.getItems().lastOrNull()?.serialNumber ?: 0 + 1 |
||||||
|
} |
||||||
|
oldRule?.let { |
||||||
|
App.db.txtTocRule().delete(oldRule) |
||||||
|
} |
||||||
|
App.db.txtTocRule().insert(rule) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
inner class TocRegexAdapter(context: Context) : |
||||||
|
SimpleRecyclerAdapter<TxtTocRule>(context, R.layout.item_toc_regex), |
||||||
|
ItemTouchCallback.OnItemTouchCallbackListener { |
||||||
|
|
||||||
|
override fun convert(holder: ItemViewHolder, item: TxtTocRule, payloads: MutableList<Any>) { |
||||||
|
holder.itemView.apply { |
||||||
|
if (payloads.isEmpty()) { |
||||||
|
rb_regex_name.text = item.name |
||||||
|
rb_regex_name.isChecked = item.name == selectedName |
||||||
|
swt_enabled.isChecked = item.enable |
||||||
|
} else { |
||||||
|
rb_regex_name.isChecked = item.name == selectedName |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
override fun registerListener(holder: ItemViewHolder) { |
||||||
|
holder.itemView.apply { |
||||||
|
rb_regex_name.setOnCheckedChangeListener { buttonView, isChecked -> |
||||||
|
if (buttonView.isPressed && isChecked) { |
||||||
|
selectedName = getItem(holder.layoutPosition)?.name |
||||||
|
updateItems(0, itemCount - 1, true) |
||||||
|
} |
||||||
|
} |
||||||
|
swt_enabled.setOnCheckedChangeListener { buttonView, isChecked -> |
||||||
|
if (buttonView.isPressed) { |
||||||
|
getItem(holder.layoutPosition)?.let { |
||||||
|
it.enable = isChecked |
||||||
|
launch(IO) { |
||||||
|
App.db.txtTocRule().update(it) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
iv_edit.onClick { |
||||||
|
editRule(getItem(holder.layoutPosition)) |
||||||
|
} |
||||||
|
iv_delete.onClick { |
||||||
|
getItem(holder.layoutPosition)?.let { item -> |
||||||
|
launch(IO) { |
||||||
|
App.db.txtTocRule().delete(item) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private var isMoved = false |
||||||
|
|
||||||
|
override fun onMove(srcPosition: Int, targetPosition: Int): Boolean { |
||||||
|
Collections.swap(getItems(), srcPosition, targetPosition) |
||||||
|
notifyItemMoved(srcPosition, targetPosition) |
||||||
|
isMoved = true |
||||||
|
return super.onMove(srcPosition, targetPosition) |
||||||
|
} |
||||||
|
|
||||||
|
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { |
||||||
|
super.clearView(recyclerView, viewHolder) |
||||||
|
if (isMoved) { |
||||||
|
for ((index, item) in getItems().withIndex()) { |
||||||
|
item.serialNumber = index + 1 |
||||||
|
} |
||||||
|
launch(IO) { |
||||||
|
App.db.txtTocRule().update(*getItems().toTypedArray()) |
||||||
|
} |
||||||
|
} |
||||||
|
isMoved = false |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
companion object { |
||||||
|
fun show(fragmentManager: FragmentManager, tocRegex: String? = null) { |
||||||
|
val dialog = TocRegexDialog() |
||||||
|
val bundle = Bundle() |
||||||
|
bundle.putString("tocRegex", tocRegex) |
||||||
|
dialog.arguments = bundle |
||||||
|
dialog.show(fragmentManager, "tocRegexDialog") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface CallBack { |
||||||
|
fun onTocRegexDialogResult(tocRegex: String) {} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,39 +0,0 @@ |
|||||||
package io.legado.app.ui.book.read.page |
|
||||||
|
|
||||||
import android.view.ActionMode |
|
||||||
import android.view.Menu |
|
||||||
import android.view.MenuItem |
|
||||||
import android.widget.TextView |
|
||||||
|
|
||||||
import io.legado.app.R |
|
||||||
import io.legado.app.constant.EventBus |
|
||||||
import io.legado.app.utils.postEvent |
|
||||||
|
|
||||||
class ContentSelectActionCallback(private val textView: TextView) : ActionMode.Callback { |
|
||||||
|
|
||||||
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { |
|
||||||
when (item?.itemId) { |
|
||||||
R.id.menu_replace -> { |
|
||||||
val text = textView.text.substring(textView.selectionStart, textView.selectionEnd) |
|
||||||
postEvent(EventBus.REPLACE, text) |
|
||||||
mode?.finish() |
|
||||||
return true |
|
||||||
} |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { |
|
||||||
mode?.menuInflater?.inflate(R.menu.content_select_action, menu) |
|
||||||
return true |
|
||||||
} |
|
||||||
|
|
||||||
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) { |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,124 +1,591 @@ |
|||||||
package io.legado.app.ui.book.read.page |
package io.legado.app.ui.book.read.page |
||||||
|
|
||||||
import android.annotation.SuppressLint |
|
||||||
import android.content.Context |
import android.content.Context |
||||||
import android.graphics.Canvas |
import android.graphics.Canvas |
||||||
import android.graphics.Paint |
import android.graphics.Paint |
||||||
|
import android.graphics.RectF |
||||||
import android.util.AttributeSet |
import android.util.AttributeSet |
||||||
import android.view.MotionEvent |
|
||||||
import android.view.View |
import android.view.View |
||||||
import io.legado.app.R |
import io.legado.app.R |
||||||
import io.legado.app.constant.PreferKey |
import io.legado.app.constant.PreferKey |
||||||
import io.legado.app.help.ReadBookConfig |
import io.legado.app.help.ReadBookConfig |
||||||
import io.legado.app.lib.theme.accentColor |
import io.legado.app.lib.theme.accentColor |
||||||
import io.legado.app.ui.book.read.page.entities.SelectPoint |
import io.legado.app.ui.book.read.page.entities.TextChar |
||||||
import io.legado.app.ui.book.read.page.entities.TextPage |
import io.legado.app.ui.book.read.page.entities.TextPage |
||||||
|
import io.legado.app.utils.activity |
||||||
import io.legado.app.utils.getCompatColor |
import io.legado.app.utils.getCompatColor |
||||||
import io.legado.app.utils.getPrefBoolean |
import io.legado.app.utils.getPrefBoolean |
||||||
|
|
||||||
|
|
||||||
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { |
class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) { |
||||||
|
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble) |
||||||
|
var upView: ((TextPage) -> Unit)? = null |
||||||
private val selectedPaint by lazy { |
private val selectedPaint by lazy { |
||||||
Paint().apply { |
Paint().apply { |
||||||
color = context.getCompatColor(R.color.btn_bg_press_2) |
color = context.getCompatColor(R.color.btn_bg_press_2) |
||||||
style = Paint.Style.FILL |
style = Paint.Style.FILL |
||||||
} |
} |
||||||
} |
} |
||||||
var selectAble = context.getPrefBoolean(PreferKey.textSelectAble) |
private var callBack: CallBack |
||||||
var selectStartLine = 0 |
private val visibleRect = RectF() |
||||||
var selectStartChar = 0 |
private val selectStart = arrayOf(0, 0, 0) |
||||||
var selectEndLine = 0 |
private val selectEnd = arrayOf(0, 0, 0) |
||||||
var selectEndChar = 0 |
private var textPage: TextPage = TextPage() |
||||||
private var textPage: TextPage? = null |
//滚动参数 |
||||||
|
private val pageFactory: TextPageFactory get() = callBack.pageFactory |
||||||
|
private val maxScrollOffset = 100f |
||||||
|
private var pageOffset = 0f |
||||||
|
|
||||||
|
init { |
||||||
|
callBack = activity as CallBack |
||||||
|
contentDescription = textPage.text |
||||||
|
} |
||||||
|
|
||||||
fun setContent(textPage: TextPage?) { |
fun setContent(textPage: TextPage) { |
||||||
this.textPage = textPage |
this.textPage = textPage |
||||||
|
contentDescription = textPage.text |
||||||
invalidate() |
invalidate() |
||||||
} |
} |
||||||
|
|
||||||
|
fun upVisibleRect() { |
||||||
|
visibleRect.set( |
||||||
|
ChapterProvider.paddingLeft.toFloat(), |
||||||
|
ChapterProvider.paddingTop.toFloat(), |
||||||
|
ChapterProvider.visibleRight.toFloat(), |
||||||
|
ChapterProvider.visibleBottom.toFloat() |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { |
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { |
||||||
super.onSizeChanged(w, h, oldw, oldh) |
super.onSizeChanged(w, h, oldw, oldh) |
||||||
ReadBookConfig.durConfig.let { |
ChapterProvider.viewWidth = w |
||||||
ChapterProvider.viewWidth = w |
ChapterProvider.viewHeight = h |
||||||
ChapterProvider.viewHeight = h |
ChapterProvider.upSize() |
||||||
ChapterProvider.upSize(ReadBookConfig.durConfig) |
upVisibleRect() |
||||||
|
textPage.format() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) { |
||||||
|
super.onDraw(canvas) |
||||||
|
canvas.clipRect(visibleRect) |
||||||
|
drawPage(canvas) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 绘制页面 |
||||||
|
*/ |
||||||
|
private fun drawPage(canvas: Canvas) { |
||||||
|
var relativeOffset = relativeOffset(0) |
||||||
|
textPage.textLines.forEach { textLine -> |
||||||
|
val lineTop = textLine.lineTop + relativeOffset |
||||||
|
val lineBase = textLine.lineBase + relativeOffset |
||||||
|
val lineBottom = textLine.lineBottom + relativeOffset |
||||||
|
drawChars( |
||||||
|
canvas, |
||||||
|
textLine.textChars, |
||||||
|
lineTop, |
||||||
|
lineBase, |
||||||
|
lineBottom, |
||||||
|
textLine.isTitle, |
||||||
|
textLine.isReadAloud |
||||||
|
) |
||||||
|
} |
||||||
|
if (!ReadBookConfig.isScroll) return |
||||||
|
//滚动翻页 |
||||||
|
val nextPage = relativePage(1) |
||||||
|
relativeOffset = relativeOffset(1) |
||||||
|
nextPage.textLines.forEach { textLine -> |
||||||
|
val lineTop = textLine.lineTop + relativeOffset |
||||||
|
val lineBase = textLine.lineBase + relativeOffset |
||||||
|
val lineBottom = textLine.lineBottom + relativeOffset |
||||||
|
drawChars( |
||||||
|
canvas, |
||||||
|
textLine.textChars, |
||||||
|
lineTop, |
||||||
|
lineBase, |
||||||
|
lineBottom, |
||||||
|
textLine.isTitle, |
||||||
|
textLine.isReadAloud |
||||||
|
) |
||||||
|
} |
||||||
|
relativeOffset = relativeOffset(2) |
||||||
|
if (relativeOffset < ChapterProvider.visibleHeight) { |
||||||
|
relativePage(2).textLines.forEach { textLine -> |
||||||
|
val lineTop = textLine.lineTop + relativeOffset |
||||||
|
val lineBase = textLine.lineBase + relativeOffset |
||||||
|
val lineBottom = textLine.lineBottom + relativeOffset |
||||||
|
drawChars( |
||||||
|
canvas, |
||||||
|
textLine.textChars, |
||||||
|
lineTop, |
||||||
|
lineBase, |
||||||
|
lineBottom, |
||||||
|
textLine.isTitle, |
||||||
|
textLine.isReadAloud |
||||||
|
) |
||||||
|
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility") |
/** |
||||||
override fun onTouchEvent(event: MotionEvent?): Boolean { |
* 绘制文字 |
||||||
|
*/ |
||||||
|
private fun drawChars( |
||||||
|
canvas: Canvas, |
||||||
|
textChars: List<TextChar>, |
||||||
|
lineTop: Float, |
||||||
|
lineBase: Float, |
||||||
|
lineBottom: Float, |
||||||
|
isTitle: Boolean, |
||||||
|
isReadAloud: Boolean |
||||||
|
) { |
||||||
|
val textPaint = if (isTitle) ChapterProvider.titlePaint else ChapterProvider.contentPaint |
||||||
|
textPaint.color = |
||||||
|
if (isReadAloud) context.accentColor else ReadBookConfig.durConfig.textColor() |
||||||
|
textChars.forEach { |
||||||
|
canvas.drawText(it.charData, it.start, lineBase, textPaint) |
||||||
|
if (it.selected) { |
||||||
|
canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 滚动事件 |
||||||
|
*/ |
||||||
|
fun onScroll(mOffset: Float) { |
||||||
|
if (mOffset == 0f) return |
||||||
|
var offset = mOffset |
||||||
|
if (offset > maxScrollOffset) { |
||||||
|
offset = maxScrollOffset |
||||||
|
} else if (offset < -maxScrollOffset) { |
||||||
|
offset = -maxScrollOffset |
||||||
|
} |
||||||
|
|
||||||
|
pageOffset += offset |
||||||
|
if (pageOffset > 0) { |
||||||
|
if (!pageFactory.hasPrev()) { |
||||||
|
pageOffset = 0f |
||||||
|
} else { |
||||||
|
pageFactory.moveToPrev() |
||||||
|
textPage = pageFactory.currentPage |
||||||
|
pageOffset -= textPage.height |
||||||
|
upView?.invoke(textPage) |
||||||
|
} |
||||||
|
} else if (pageOffset < -textPage.height) { |
||||||
|
if (!pageFactory.hasNext()) { |
||||||
|
pageOffset = -textPage.height.toFloat() |
||||||
|
} else { |
||||||
|
pageOffset += textPage.height |
||||||
|
pageFactory.moveToNext() |
||||||
|
textPage = pageFactory.currentPage |
||||||
|
upView?.invoke(textPage) |
||||||
|
} |
||||||
|
} |
||||||
|
invalidate() |
||||||
|
} |
||||||
|
|
||||||
return true |
fun resetPageOffset() { |
||||||
|
pageOffset = 0f |
||||||
} |
} |
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) { |
/** |
||||||
super.onDraw(canvas) |
* 选择初始文字 |
||||||
textPage?.let { textPage -> |
*/ |
||||||
textPage.textLines.forEach { textLine -> |
fun selectText( |
||||||
val textPaint = if (textLine.isTitle) { |
x: Float, |
||||||
ChapterProvider.titlePaint |
y: Float, |
||||||
} else { |
select: (relativePage: Int, lineIndex: Int, charIndex: Int) -> Unit |
||||||
ChapterProvider.contentPaint |
) { |
||||||
|
if (!selectAble) return |
||||||
|
if (!visibleRect.contains(x, y)) return |
||||||
|
var relativeOffset = relativeOffset(0) |
||||||
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
||||||
|
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
|
if (x > textChar.start && x < textChar.end) { |
||||||
|
textChar.selected = true |
||||||
|
invalidate() |
||||||
|
selectStart[0] = 0 |
||||||
|
selectStart[1] = lineIndex |
||||||
|
selectStart[2] = charIndex |
||||||
|
selectEnd[0] = 0 |
||||||
|
selectEnd[1] = lineIndex |
||||||
|
selectEnd[2] = charIndex |
||||||
|
upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) |
||||||
|
select(0, lineIndex, charIndex) |
||||||
|
return |
||||||
|
} |
||||||
} |
} |
||||||
textPaint.color = if (textLine.isReadAloud) { |
return |
||||||
context.accentColor |
} |
||||||
} else { |
} |
||||||
ReadBookConfig.durConfig.textColor() |
if (!ReadBookConfig.isScroll) return |
||||||
|
//滚动翻页 |
||||||
|
relativeOffset = relativeOffset(1) |
||||||
|
if (relativeOffset >= ChapterProvider.visibleHeight) return |
||||||
|
val nextPage = relativePage(1) |
||||||
|
for ((lineIndex, textLine) in nextPage.textLines.withIndex()) { |
||||||
|
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
|
if (x > textChar.start && x < textChar.end) { |
||||||
|
textChar.selected = true |
||||||
|
invalidate() |
||||||
|
selectStart[0] = 1 |
||||||
|
selectStart[1] = lineIndex |
||||||
|
selectStart[2] = charIndex |
||||||
|
selectEnd[0] = 1 |
||||||
|
selectEnd[1] = lineIndex |
||||||
|
selectEnd[2] = charIndex |
||||||
|
upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) |
||||||
|
select(1, lineIndex, charIndex) |
||||||
|
return |
||||||
|
} |
||||||
} |
} |
||||||
textLine.textChars.forEach { |
return |
||||||
canvas.drawText( |
} |
||||||
it.charData, |
} |
||||||
it.leftBottomPosition.x, |
relativeOffset = relativeOffset(2) |
||||||
it.leftBottomPosition.y.toFloat(), |
if (relativeOffset >= ChapterProvider.visibleHeight) return |
||||||
textPaint |
for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { |
||||||
) |
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
if (it.selected) { |
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
canvas.drawRect( |
if (x > textChar.start && x < textChar.end) { |
||||||
it.leftBottomPosition.x, |
textChar.selected = true |
||||||
it.rightTopPosition.y.toFloat(), |
invalidate() |
||||||
it.rightTopPosition.x, |
selectStart[0] = 2 |
||||||
it.leftBottomPosition.y.toFloat(), |
selectStart[1] = lineIndex |
||||||
selectedPaint |
selectStart[2] = charIndex |
||||||
) |
selectEnd[0] = 2 |
||||||
|
selectEnd[1] = lineIndex |
||||||
|
selectEnd[2] = charIndex |
||||||
|
upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) |
||||||
|
select(2, lineIndex, charIndex) |
||||||
|
return |
||||||
} |
} |
||||||
} |
} |
||||||
|
return |
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
fun selectText(x: Float, y: Float): SelectPoint? { |
/** |
||||||
textPage?.let { textPage -> |
* 开始选择符移动 |
||||||
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
*/ |
||||||
if (y > textLine.lineTop && y < textLine.lineBottom) { |
fun selectStartMove(x: Float, y: Float) { |
||||||
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
if (!visibleRect.contains(x, y)) return |
||||||
if (x > textChar.leftBottomPosition.x && x < textChar.rightTopPosition.x) { |
var relativeOffset = relativeOffset(0) |
||||||
textChar.selected = true |
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
||||||
invalidate() |
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
selectStartLine = lineIndex |
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
selectStartChar = charIndex |
if (x > textChar.start && x < textChar.end) { |
||||||
selectEndLine = lineIndex |
if (selectStart[0] != 0 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { |
||||||
selectEndChar = charIndex |
if (selectToInt(0, lineIndex, charIndex) > selectToInt(selectEnd)) { |
||||||
return SelectPoint( |
return |
||||||
textChar.leftBottomPosition.x, |
} |
||||||
textChar.leftBottomPosition.y.toFloat(), |
selectStart[0] = 0 |
||||||
textChar.rightTopPosition.x, |
selectStart[1] = lineIndex |
||||||
textChar.leftBottomPosition.y.toFloat() |
selectStart[2] = charIndex |
||||||
) |
upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectChars() |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
if (!ReadBookConfig.isScroll) return |
||||||
|
//滚动翻页 |
||||||
|
relativeOffset = relativeOffset(1) |
||||||
|
if (relativeOffset >= ChapterProvider.visibleHeight) return |
||||||
|
for ((lineIndex, textLine) in relativePage(1).textLines.withIndex()) { |
||||||
|
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
|
if (x > textChar.start && x < textChar.end) { |
||||||
|
if (selectStart[0] != 1 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { |
||||||
|
if (selectToInt(1, lineIndex, charIndex) > selectToInt(selectEnd)) { |
||||||
|
return |
||||||
|
} |
||||||
|
selectStart[0] = 1 |
||||||
|
selectStart[1] = lineIndex |
||||||
|
selectStart[2] = charIndex |
||||||
|
upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectChars() |
||||||
} |
} |
||||||
|
return |
||||||
} |
} |
||||||
break |
|
||||||
} |
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
relativeOffset = relativeOffset(2) |
||||||
|
if (relativeOffset >= ChapterProvider.visibleHeight) return |
||||||
|
for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { |
||||||
|
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
|
if (x > textChar.start && x < textChar.end) { |
||||||
|
if (selectStart[0] != 2 || selectStart[1] != lineIndex || selectStart[2] != charIndex) { |
||||||
|
if (selectToInt(2, lineIndex, charIndex) > selectToInt(selectEnd)) { |
||||||
|
return |
||||||
|
} |
||||||
|
selectStart[0] = 2 |
||||||
|
selectStart[1] = lineIndex |
||||||
|
selectStart[2] = charIndex |
||||||
|
upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectChars() |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
} |
} |
||||||
} |
} |
||||||
return null |
|
||||||
} |
} |
||||||
|
|
||||||
fun selectStartMove(x: Float, y: Float) { |
/** |
||||||
|
* 结束选择符移动 |
||||||
|
*/ |
||||||
|
fun selectEndMove(x: Float, y: Float) { |
||||||
|
if (!visibleRect.contains(x, y)) return |
||||||
|
var relativeOffset = relativeOffset(0) |
||||||
|
for ((lineIndex, textLine) in textPage.textLines.withIndex()) { |
||||||
|
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
|
if (x > textChar.start && x < textChar.end) { |
||||||
|
if (selectEnd[0] != 0 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { |
||||||
|
if (selectToInt(0, lineIndex, charIndex) < selectToInt(selectStart)) { |
||||||
|
return |
||||||
|
} |
||||||
|
selectEnd[0] = 0 |
||||||
|
selectEnd[1] = lineIndex |
||||||
|
selectEnd[2] = charIndex |
||||||
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectChars() |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
if (!ReadBookConfig.isScroll) return |
||||||
|
//滚动翻页 |
||||||
|
relativeOffset = relativeOffset(1) |
||||||
|
if (relativeOffset >= ChapterProvider.visibleHeight) return |
||||||
|
for ((lineIndex, textLine) in relativePage(1).textLines.withIndex()) { |
||||||
|
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
|
if (x > textChar.start && x < textChar.end) { |
||||||
|
if (selectEnd[0] != 1 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { |
||||||
|
if (selectToInt(1, lineIndex, charIndex) < selectToInt(selectStart)) { |
||||||
|
return |
||||||
|
} |
||||||
|
selectEnd[0] = 1 |
||||||
|
selectEnd[1] = lineIndex |
||||||
|
selectEnd[2] = charIndex |
||||||
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectChars() |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
relativeOffset = relativeOffset(2) |
||||||
|
if (relativeOffset >= ChapterProvider.visibleHeight) return |
||||||
|
for ((lineIndex, textLine) in relativePage(2).textLines.withIndex()) { |
||||||
|
if (y > textLine.lineTop + relativeOffset && y < textLine.lineBottom + relativeOffset) { |
||||||
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
|
if (x > textChar.start && x < textChar.end) { |
||||||
|
if (selectEnd[0] != 2 || selectEnd[1] != lineIndex || selectEnd[2] != charIndex) { |
||||||
|
if (selectToInt(2, lineIndex, charIndex) < selectToInt(selectStart)) { |
||||||
|
return |
||||||
|
} |
||||||
|
selectEnd[0] = 2 |
||||||
|
selectEnd[1] = lineIndex |
||||||
|
selectEnd[2] = charIndex |
||||||
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset) |
||||||
|
upSelectChars() |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 选择开始文字 |
||||||
|
*/ |
||||||
|
fun selectStartMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) { |
||||||
|
selectStart[0] = relativePage |
||||||
|
selectStart[1] = lineIndex |
||||||
|
selectStart[2] = charIndex |
||||||
|
val textLine = relativePage(relativePage).textLines[lineIndex] |
||||||
|
val textChar = textLine.textChars[charIndex] |
||||||
|
upSelectedStart(textChar.start, textLine.lineBottom + relativeOffset(relativePage)) |
||||||
|
upSelectChars() |
||||||
} |
} |
||||||
|
|
||||||
fun selectEndMove(x: Float, y: Float) { |
/** |
||||||
|
* 选择结束文字 |
||||||
|
*/ |
||||||
|
fun selectEndMoveIndex(relativePage: Int, lineIndex: Int, charIndex: Int) { |
||||||
|
selectEnd[0] = relativePage |
||||||
|
selectEnd[1] = lineIndex |
||||||
|
selectEnd[2] = charIndex |
||||||
|
val textLine = relativePage(relativePage).textLines[lineIndex] |
||||||
|
val textChar = textLine.textChars[charIndex] |
||||||
|
upSelectedEnd(textChar.end, textLine.lineBottom + relativeOffset(relativePage)) |
||||||
|
upSelectChars() |
||||||
|
} |
||||||
|
|
||||||
|
private fun upSelectChars() { |
||||||
|
val last = if (ReadBookConfig.isScroll) 2 else 0 |
||||||
|
for (relativePos in 0..last) { |
||||||
|
for ((lineIndex, textLine) in relativePage(relativePos).textLines.withIndex()) { |
||||||
|
for ((charIndex, textChar) in textLine.textChars.withIndex()) { |
||||||
|
textChar.selected = |
||||||
|
if (relativePos == selectStart[0] |
||||||
|
&& relativePos == selectEnd[0] |
||||||
|
&& lineIndex == selectStart[1] |
||||||
|
&& lineIndex == selectEnd[1] |
||||||
|
) { |
||||||
|
charIndex in selectStart[2]..selectEnd[2] |
||||||
|
} else if (relativePos == selectStart[0] && lineIndex == selectStart[1]) { |
||||||
|
charIndex >= selectStart[2] |
||||||
|
} else if (relativePos == selectEnd[0] && lineIndex == selectEnd[1]) { |
||||||
|
charIndex <= selectEnd[2] |
||||||
|
} else if (relativePos == selectStart[0] && relativePos == selectEnd[0]) { |
||||||
|
lineIndex in (selectStart[1] + 1) until selectEnd[1] |
||||||
|
} else if (relativePos == selectStart[0]) { |
||||||
|
lineIndex > selectStart[1] |
||||||
|
} else if (relativePos == selectEnd[0]) { |
||||||
|
lineIndex < selectEnd[1] |
||||||
|
} else { |
||||||
|
relativePos in selectStart[0] + 1 until selectEnd[0] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
invalidate() |
||||||
|
} |
||||||
|
|
||||||
|
private fun upSelectedStart(x: Float, y: Float) { |
||||||
|
callBack.upSelectedStart(x, y + callBack.headerHeight) |
||||||
|
} |
||||||
|
|
||||||
|
private fun upSelectedEnd(x: Float, y: Float) { |
||||||
|
callBack.upSelectedEnd(x, y + callBack.headerHeight) |
||||||
|
} |
||||||
|
|
||||||
|
fun cancelSelect() { |
||||||
|
val last = if (ReadBookConfig.isScroll) 2 else 0 |
||||||
|
for (relativePos in 0..last) { |
||||||
|
relativePage(relativePos).textLines.forEach { textLine -> |
||||||
|
textLine.textChars.forEach { |
||||||
|
it.selected = false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
invalidate() |
||||||
|
callBack.onCancelSelect() |
||||||
} |
} |
||||||
|
|
||||||
|
val selectedText: String |
||||||
|
get() { |
||||||
|
val stringBuilder = StringBuilder() |
||||||
|
for (relativePos in selectStart[0]..selectEnd[0]) { |
||||||
|
val textPage = relativePage(relativePos) |
||||||
|
if (relativePos == selectStart[0] && relativePos == selectEnd[0]) { |
||||||
|
for (lineIndex in selectStart[1]..selectEnd[1]) { |
||||||
|
if (lineIndex == selectStart[1] && lineIndex == selectEnd[1]) { |
||||||
|
stringBuilder.append( |
||||||
|
textPage.textLines[lineIndex].text.substring( |
||||||
|
selectStart[2], |
||||||
|
selectEnd[2] + 1 |
||||||
|
) |
||||||
|
) |
||||||
|
} else if (lineIndex == selectStart[1]) { |
||||||
|
stringBuilder.append( |
||||||
|
textPage.textLines[lineIndex].text.substring( |
||||||
|
selectStart[2] |
||||||
|
) |
||||||
|
) |
||||||
|
} else if (lineIndex == selectEnd[1]) { |
||||||
|
stringBuilder.append( |
||||||
|
textPage.textLines[lineIndex].text.substring(0, selectEnd[2] + 1) |
||||||
|
) |
||||||
|
} else { |
||||||
|
stringBuilder.append(textPage.textLines[lineIndex].text) |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (relativePos == selectStart[0]) { |
||||||
|
for (lineIndex in selectStart[1] until relativePage(relativePos).textLines.size) { |
||||||
|
if (lineIndex == selectStart[1]) { |
||||||
|
stringBuilder.append( |
||||||
|
textPage.textLines[lineIndex].text.substring( |
||||||
|
selectStart[2] |
||||||
|
) |
||||||
|
) |
||||||
|
} else { |
||||||
|
stringBuilder.append(textPage.textLines[lineIndex].text) |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (relativePos == selectEnd[0]) { |
||||||
|
for (lineIndex in 0..selectEnd[1]) { |
||||||
|
if (lineIndex == selectEnd[1]) { |
||||||
|
stringBuilder.append( |
||||||
|
textPage.textLines[lineIndex].text.substring(0, selectEnd[2] + 1) |
||||||
|
) |
||||||
|
} else { |
||||||
|
stringBuilder.append(textPage.textLines[lineIndex].text) |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (relativePos in selectStart[0] + 1 until selectEnd[0]) { |
||||||
|
for (lineIndex in selectStart[1]..selectEnd[1]) { |
||||||
|
stringBuilder.append(textPage.textLines[lineIndex].text) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return stringBuilder.toString() |
||||||
|
} |
||||||
|
|
||||||
|
private fun selectToInt(page: Int, line: Int, char: Int): Int { |
||||||
|
return page * 1000000 + line * 100000 + char |
||||||
|
} |
||||||
|
|
||||||
|
private fun selectToInt(select: Array<Int>): Int { |
||||||
|
return select[0] * 1000000 + select[1] * 100000 + select[2] |
||||||
|
} |
||||||
|
|
||||||
|
private fun relativeOffset(relativePos: Int): Float { |
||||||
|
return when (relativePos) { |
||||||
|
0 -> pageOffset |
||||||
|
1 -> pageOffset + textPage.height |
||||||
|
else -> pageOffset + textPage.height + pageFactory.nextPage.height |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun relativePage(relativePos: Int): TextPage { |
||||||
|
return when (relativePos) { |
||||||
|
0 -> textPage |
||||||
|
1 -> pageFactory.nextPage |
||||||
|
else -> pageFactory.nextPagePlus |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
interface CallBack { |
||||||
|
fun upSelectedStart(x: Float, y: Float) |
||||||
|
fun upSelectedEnd(x: Float, y: Float) |
||||||
|
fun onCancelSelect() |
||||||
|
val headerHeight: Int |
||||||
|
val pageFactory: TextPageFactory |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -1,24 +1,19 @@ |
|||||||
package io.legado.app.ui.book.read.page |
package io.legado.app.ui.book.read.page |
||||||
|
|
||||||
|
import io.legado.app.service.help.ReadBook |
||||||
import io.legado.app.ui.book.read.page.entities.TextChapter |
import io.legado.app.ui.book.read.page.entities.TextChapter |
||||||
|
|
||||||
interface DataSource { |
interface DataSource { |
||||||
val isScrollDelegate: Boolean |
|
||||||
|
|
||||||
val pageIndex: Int |
val pageIndex: Int get() = ReadBook.durChapterPos() |
||||||
|
|
||||||
fun setPageIndex(pageIndex: Int) |
val currentChapter: TextChapter? |
||||||
|
|
||||||
fun getChapterPosition(): Int |
val nextChapter: TextChapter? |
||||||
|
|
||||||
fun getCurrentChapter(): TextChapter? |
val prevChapter: TextChapter? |
||||||
|
|
||||||
fun getNextChapter(): TextChapter? |
|
||||||
|
|
||||||
fun getPreviousChapter(): TextChapter? |
|
||||||
|
|
||||||
fun hasNextChapter(): Boolean |
fun hasNextChapter(): Boolean |
||||||
|
|
||||||
fun hasPrevChapter(): Boolean |
fun hasPrevChapter(): Boolean |
||||||
|
|
||||||
} |
} |
@ -1,119 +1,61 @@ |
|||||||
package io.legado.app.ui.book.read.page.delegate |
package io.legado.app.ui.book.read.page.delegate |
||||||
|
|
||||||
import android.graphics.Canvas |
|
||||||
import android.graphics.Matrix |
|
||||||
import android.view.MotionEvent |
import android.view.MotionEvent |
||||||
|
import android.view.VelocityTracker |
||||||
import io.legado.app.ui.book.read.page.PageView |
import io.legado.app.ui.book.read.page.PageView |
||||||
import kotlin.math.abs |
import kotlin.math.abs |
||||||
|
|
||||||
class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { |
class ScrollPageDelegate(pageView: PageView) : PageDelegate(pageView) { |
||||||
|
|
||||||
private val bitmapMatrix = Matrix() |
// 滑动追踪的时间 |
||||||
|
private val velocityDuration = 1000 |
||||||
|
//速度追踪器 |
||||||
|
private val mVelocity: VelocityTracker = VelocityTracker.obtain() |
||||||
|
|
||||||
override fun onAnimStart() { |
override fun onAnimStart() { |
||||||
if (!atTop && !atBottom) { |
//惯性滚动 |
||||||
stopScroll() |
fling( |
||||||
return |
0, touchY.toInt(), 0, mVelocity.yVelocity.toInt(), |
||||||
} |
0, 0, -10 * viewHeight, 10 * viewHeight |
||||||
val distanceY: Float |
) |
||||||
when (mDirection) { |
|
||||||
Direction.NEXT -> distanceY = |
|
||||||
if (isCancel) { |
|
||||||
var dis = viewHeight - startY + touchY |
|
||||||
if (dis > viewHeight) { |
|
||||||
dis = viewHeight.toFloat() |
|
||||||
} |
|
||||||
viewHeight - dis |
|
||||||
} else { |
|
||||||
-(touchY + (viewHeight - startY)) |
|
||||||
} |
|
||||||
else -> distanceY = |
|
||||||
if (isCancel) { |
|
||||||
-(touchY - startY) |
|
||||||
} else { |
|
||||||
viewHeight - (touchY - startY) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
startScroll(0, touchY.toInt(), 0, distanceY.toInt()) |
|
||||||
} |
} |
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) { |
override fun onScroll() { |
||||||
if (atTop || atBottom) { |
curPage.onScroll(touchY - lastY) |
||||||
val offsetY = touchY - startY |
} |
||||||
|
|
||||||
if ((mDirection == Direction.NEXT && offsetY > 0) |
|
||||||
|| (mDirection == Direction.PREV && offsetY < 0) |
|
||||||
) return |
|
||||||
|
|
||||||
val distanceY = if (offsetY > 0) offsetY - viewHeight else offsetY + viewHeight |
override fun onTouch(event: MotionEvent) { |
||||||
if (atTop && mDirection == Direction.PREV) { |
when (event.action) { |
||||||
bitmap?.let { |
MotionEvent.ACTION_DOWN -> { |
||||||
bitmapMatrix.setTranslate(0.toFloat(), distanceY) |
setStartPoint(event.x, event.y) |
||||||
canvas.drawBitmap(it, bitmapMatrix, null) |
abort() |
||||||
} |
mVelocity.clear() |
||||||
} else if (atBottom && mDirection == Direction.NEXT) { |
} |
||||||
bitmap?.let { |
MotionEvent.ACTION_MOVE -> { |
||||||
bitmapMatrix.setTranslate(0.toFloat(), distanceY) |
if (isTextSelected) { |
||||||
canvas.drawBitmap(it, bitmapMatrix, null) |
selectText(event) |
||||||
|
} else { |
||||||
|
onScroll(event) |
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
super.onTouch(event) |
||||||
} |
} |
||||||
|
|
||||||
override fun onAnimStop() { |
private fun onScroll(event: MotionEvent) { |
||||||
if (!isCancel) { |
mVelocity.addMovement(event) |
||||||
pageView.fillPage(mDirection) |
mVelocity.computeCurrentVelocity(velocityDuration) |
||||||
} |
setTouchPoint(event.x, event.y) |
||||||
} |
if (!isMoved) { |
||||||
|
isMoved = abs(startX - event.x) > slop || abs(startY - event.y) > slop |
||||||
override fun onScroll( |
|
||||||
e1: MotionEvent, |
|
||||||
e2: MotionEvent, |
|
||||||
distanceX: Float, |
|
||||||
distanceY: Float |
|
||||||
): Boolean { |
|
||||||
if (!isMoved && abs(distanceX) < abs(distanceY)) { |
|
||||||
if (distanceY < 0) { |
|
||||||
if (atTop) { |
|
||||||
val event = e1.toAction(MotionEvent.ACTION_UP) |
|
||||||
curPage.dispatchTouchEvent(event) |
|
||||||
event.recycle() |
|
||||||
//如果上一页不存在 |
|
||||||
if (!hasPrev()) { |
|
||||||
noNext = true |
|
||||||
return true |
|
||||||
} |
|
||||||
setDirection(Direction.PREV) |
|
||||||
setBitmap() |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (atBottom) { |
|
||||||
val event = e1.toAction(MotionEvent.ACTION_UP) |
|
||||||
curPage.dispatchTouchEvent(event) |
|
||||||
event.recycle() |
|
||||||
//如果不存在表示没有下一页了 |
|
||||||
if (!hasNext()) { |
|
||||||
noNext = true |
|
||||||
return true |
|
||||||
} |
|
||||||
setDirection(Direction.NEXT) |
|
||||||
setBitmap() |
|
||||||
} |
|
||||||
} |
|
||||||
isMoved = true |
|
||||||
} |
|
||||||
if ((atTop && mDirection != Direction.PREV) || (atBottom && mDirection != Direction.NEXT) || mDirection == Direction.NONE) { |
|
||||||
//传递触摸事件到textView |
|
||||||
curPage.dispatchTouchEvent(e2) |
|
||||||
} |
} |
||||||
if (isMoved) { |
if (isMoved) { |
||||||
isCancel = if (mDirection == Direction.NEXT) distanceY < 0 else distanceY > 0 |
|
||||||
isRunning = true |
isRunning = true |
||||||
//设置触摸点 |
|
||||||
setTouchPoint(e2.x, e2.y) |
|
||||||
} |
} |
||||||
return isMoved |
|
||||||
} |
} |
||||||
|
|
||||||
|
override fun onDestroy() { |
||||||
|
super.onDestroy() |
||||||
|
mVelocity.recycle() |
||||||
|
} |
||||||
} |
} |
@ -1,8 +0,0 @@ |
|||||||
package io.legado.app.ui.book.read.page.entities |
|
||||||
|
|
||||||
data class SelectPoint( |
|
||||||
var startX: Float, |
|
||||||
var startY: Float, |
|
||||||
var endX: Float, |
|
||||||
var endY: Float |
|
||||||
) |
|
@ -1,6 +0,0 @@ |
|||||||
package io.legado.app.ui.book.read.page.entities |
|
||||||
|
|
||||||
data class TextPoint( |
|
||||||
val x: Float, |
|
||||||
val y: Int |
|
||||||
) |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue