diff --git a/app/build.gradle b/app/build.gradle index 0b7ba28a4..c84674ddb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ static def releaseTime() { def name = "legado" def version = "3." + releaseTime() -def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], project.rootDir).text.trim()) +// def gitCommits = Integer.parseInt('git rev-list --count HEAD'.execute([], project.rootDir).text.trim()) android { compileSdkVersion 30 @@ -30,7 +30,7 @@ android { applicationId "io.legado.app" minSdkVersion 21 targetSdkVersion 30 - versionCode gitCommits + versionCode 1 versionName version testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" project.ext.set("archivesBaseName", name + "_" + version) diff --git a/app/src/main/java/io/legado/app/data/dao/BookIdealDao.kt b/app/src/main/java/io/legado/app/data/dao/BookIdealDao.kt new file mode 100644 index 000000000..847f54de0 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/BookIdealDao.kt @@ -0,0 +1,16 @@ +package io.legado.app.data.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Query +import io.legado.app.data.entities.IdealEntity + +@Dao +interface BookIdealDao { + + /** + * 根据书名查找想法列表 + */ + @Query("SELECT * FROM book_ideals WHERE `bookName` = bookName") + fun getIdealList(bookName: String): LiveData> +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/BookIdealDetailDao.kt b/app/src/main/java/io/legado/app/data/dao/BookIdealDetailDao.kt new file mode 100644 index 000000000..d452cbfe9 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/BookIdealDetailDao.kt @@ -0,0 +1,23 @@ +package io.legado.app.data.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.legado.app.data.entities.IdealDetailEntity + +@Dao +interface BookIdealDetailDao { + + + /** + * 根据想法id查询想法内容列表 + */ + @Query("SELECT * FROM book_ideal_list WHERE `idealId` = idealId") + fun getIdealListById(idealId: Long): LiveData> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertIDeal(vararg idealDetail: IdealDetailEntity) + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/BookLineDao.kt b/app/src/main/java/io/legado/app/data/dao/BookLineDao.kt new file mode 100644 index 000000000..0d2c2d3b2 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/BookLineDao.kt @@ -0,0 +1,4 @@ +package io.legado.app.data.dao + +class BookLineDao { +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/DrawLineEntity.kt b/app/src/main/java/io/legado/app/data/entities/DrawLineEntity.kt new file mode 100644 index 000000000..7f25a0dec --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/DrawLineEntity.kt @@ -0,0 +1,42 @@ +package io.legado.app.data.entities + +import io.legado.app.R + +class DrawLineEntity { + + // 第几章的划线 + var chapterIndex: Int = 0 + + // 章节的第几个字符开始 + var startIndex: Int = 0 + + // 本章的第几个字符结束 + var endIndex: Int = 0 + + // 划线的样式 + var lineStyle: LineStyle = LineStyle.SHAPE + + // 划线颜色 + var lineColor: LineColor = LineColor.BLUE + + enum class LineColor { + BLUE, + ORANGE, + YELLOW, + GREEN + } + + enum class LineStyle { + STEEP, // 直线 + WAVE, // 波浪线 + SHAPE, // 选中样式 + } + + + fun getLineColor() = when (lineColor) { + LineColor.BLUE -> R.color.md_blue_200 + LineColor.GREEN -> R.color.md_green_600 + LineColor.ORANGE -> R.color.md_orange_600 + LineColor.YELLOW -> R.color.md_yellow_600 + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/IdealDetailEntity.kt b/app/src/main/java/io/legado/app/data/entities/IdealDetailEntity.kt new file mode 100644 index 000000000..39a20e6d5 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/IdealDetailEntity.kt @@ -0,0 +1,31 @@ +package io.legado.app.data.entities + +import android.os.Parcelable +import androidx.room.Entity +import kotlinx.parcelize.Parcelize + +@Entity(primaryKeys = ["id", "idealId"], tableName = "book_ideal_list") +@Parcelize +data class IdealDetailEntity( + var id: Long = 0b1, + var idealId: Long, // 整个想法的id + var idealContent: String? = "", // 想法内容 + var userAvatar: String? = "", // 用户头像 + var assistCount: Int = 0, // 赞数量 + var replyCount: Int = 0 // 回复数量 +) : Parcelable { + + override fun hashCode(): Int { + return id.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other !is IdealDetailEntity) return false + return id == other.id && + idealId == other.idealId && + idealContent == other.idealContent && + userAvatar == other.userAvatar && + assistCount == other.assistCount && + replyCount == other.replyCount + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/IdealEntity.kt b/app/src/main/java/io/legado/app/data/entities/IdealEntity.kt new file mode 100644 index 000000000..083df77c5 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/IdealEntity.kt @@ -0,0 +1,53 @@ +package io.legado.app.data.entities + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.PrimaryKey +import io.legado.app.R +import kotlinx.parcelize.Parcelize + +/** + * 想法 + */ +@Parcelize +@Entity(tableName = "book_ideals") +data class IdealEntity( + + @PrimaryKey + var idealId: Long = 0, + + // 在本章里面的第几个想法 + var inChapterIndex: Int = 0, + + // 书id + var bookName: String = "", + + // 第几章的想法 + var chapterIndex: Int = 0, + + // 章节的第几个字符开始 + var startIndex: Int = 0, + + // 本章的第几个字符结束 + var endIndex: Int = 0 + +) : Parcelable { + + + override fun hashCode(): Int { + return idealId.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other is IdealEntity) { + return idealId == other.idealId && + chapterIndex == other.chapterIndex && + startIndex == other.startIndex && + endIndex == other.endIndex + } + + return false + } + + fun getLineColor() = R.color.md_red_300 +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/IdealRelation.kt b/app/src/main/java/io/legado/app/data/entities/IdealRelation.kt new file mode 100644 index 000000000..63cbf7694 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/IdealRelation.kt @@ -0,0 +1,15 @@ +package io.legado.app.data.entities + +import android.os.Parcelable +import androidx.room.Entity +import kotlinx.parcelize.Parcelize + +@Entity( + tableName = "ideal_detail_list", + primaryKeys = ["id", "idealId"] +) +@Parcelize +data class IdealRelation( + var id: Long = 0b1, + var idealId: Long = 0b1 +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/IdealWithDetail.kt b/app/src/main/java/io/legado/app/data/entities/IdealWithDetail.kt new file mode 100644 index 000000000..fcc2395ca --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/IdealWithDetail.kt @@ -0,0 +1,22 @@ +package io.legado.app.data.entities + +import android.os.Parcelable +import androidx.room.Embedded +import androidx.room.Junction +import androidx.room.Relation +import kotlinx.parcelize.Parcelize + +@Parcelize +data class IdealWithDetail( + + @Embedded + var idealEntity: IdealEntity, + + @Relation( + parentColumn = "idealId", + entityColumn = "id", + entity = IdealDetailEntity::class, + associateBy = Junction(IdealRelation::class) + ) + var idealList: List +) : Parcelable \ No newline at end of file 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 dba76f97f..1cee5ec8e 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -192,9 +192,9 @@ object BookHelp { fun getContent(book: Book, bookChapter: BookChapter): String? { if (book.isLocalTxt()) { - return LocalBook.getContext(book, bookChapter) + return LocalBook.getContent(book, bookChapter) } else if (book.isEpub() && !hasContent(book, bookChapter)) { - val string = LocalBook.getContext(book, bookChapter) + val string = LocalBook.getContent(book, bookChapter) string?.let { FileUtils.createFileIfNotExist( downloadDir, diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index 001cc93b5..b205105e1 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -29,7 +29,7 @@ object LocalBook { } } - fun getContext(book: Book, chapter: BookChapter): String? { + fun getContent(book: Book, chapter: BookChapter): String? { return if (book.isEpub()) { EpubFile.getContent(book, chapter) } else { diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 4726949cb..e0e6fb89d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -420,14 +420,33 @@ class ReadBookActivity : ReadBookBaseActivity(), MotionEvent.ACTION_DOWN -> textActionMenu.dismiss() MotionEvent.ACTION_MOVE -> { when (v.id) { - R.id.cursor_left -> readView.curPage.selectStartMove( - event.rawX + cursorLeft.width, - event.rawY - cursorLeft.height - ) - R.id.cursor_right -> readView.curPage.selectEndMove( - event.rawX - cursorRight.width, - event.rawY - cursorRight.height - ) + R.id.cursor_left -> { + with(readView) { + curPage.selectStartMoveIndex( + firstRelativePage, + firstLineIndex, + firstCharIndex + ) + + curPage.selectStartMove( + event.rawX + cursorLeft.width, + event.rawY - cursorLeft.height + ) + } + } + R.id.cursor_right -> { + with(readView) { + curPage.selectEndMoveIndex( + firstRelativePage, + firstLineIndex, + firstCharIndex + ) + curPage.selectEndMove( + event.rawX + cursorRight.width, + event.rawY - cursorRight.height + ) + } + } } } MotionEvent.ACTION_UP -> showTextActionMenu() @@ -540,7 +559,7 @@ class ReadBookActivity : ReadBookBaseActivity(), */ override fun onMenuActionFinally() = with(binding) { textActionMenu.dismiss() - readView.curPage.cancelSelect() + readView.cancelSelect() readView.isTextSelected = false } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt index 970ec18ed..6d77751ac 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.book.read.page import android.content.Context import android.graphics.Canvas +import android.graphics.DashPathEffect import android.graphics.Paint import android.graphics.RectF import android.util.AttributeSet @@ -10,6 +11,8 @@ import android.view.View import io.legado.app.R import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Bookmark +import io.legado.app.data.entities.DrawLineEntity +import io.legado.app.data.entities.IdealEntity import io.legado.app.help.ReadBookConfig import io.legado.app.lib.theme.accentColor import io.legado.app.service.help.ReadBook @@ -27,6 +30,7 @@ import io.legado.app.utils.toastOnUi import kotlinx.coroutines.CoroutineScope import kotlin.math.min + /** * 阅读内容界面 */ @@ -39,6 +43,16 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at style = Paint.Style.FILL } } + + private val drawLinePaint by lazy { + Paint().apply { + color = context.getCompatColor(R.color.btn_bg_press_2) + style = Paint.Style.FILL + strokeWidth = 4f + isAntiAlias = true + isDither = true + } + } private var callBack: CallBack private val visibleRect = RectF() private val selectStart = arrayOf(0, 0, 0) @@ -152,9 +166,59 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at } else { canvas.drawText(it.charData, it.start, lineBase, textPaint) } - if (it.selected) { - canvas.drawRect(it.start, lineTop, it.end, lineBottom, selectedPaint) + + drawLine(canvas, it, lineTop, lineBottom) + + } + } + + + /** + * 选中,划线,画想法 + */ + private fun drawLine(canvas: Canvas, textChar: TextChar, lineTop: Float, lineBottom: Float) { + + // 缩进字符就不选中了 + if (textChar.isIndent) return + + // todo 划线 + val drawLineEntity = textChar.drawLineEntity + val idealEntity = textChar.idealEntity + + if (drawLineEntity != null) { + drawLinePaint.pathEffect = null + drawLinePaint.color = context.getCompatColor(drawLineEntity.getLineColor()) + when (drawLineEntity.lineStyle) { + DrawLineEntity.LineStyle.SHAPE -> { // 矩形线 + canvas.drawRect( + textChar.start, + lineTop, + textChar.end, + lineBottom, + drawLinePaint + ) + } + DrawLineEntity.LineStyle.STEEP -> { // 直线 + canvas.drawLine( + textChar.start, + lineBottom, + textChar.end, + lineBottom, + drawLinePaint + ) + } + else -> { // 波浪线 + } } + } else if (idealEntity != null) { + drawLinePaint.pathEffect = DashPathEffect(floatArrayOf(4f, 4f), 0f) + drawLinePaint.color = context.getCompatColor(idealEntity.getLineColor()) + canvas.drawLine(textChar.start, lineBottom, textChar.end, lineBottom, drawLinePaint) + } + + // 选中 + if (textChar.selected) { + canvas.drawRect(textChar.start, lineTop, textChar.end, lineBottom, selectedPaint) } } @@ -260,7 +324,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at return } } - } } @@ -282,10 +345,7 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at for ((charIndex, textChar) in textLine.textChars.withIndex()) { if (x > textChar.start && x < textChar.end) { if (selectStart[0] != relativePos || selectStart[1] != lineIndex || selectStart[2] != charIndex) { - if (selectToInt(relativePos, lineIndex, charIndex) > selectToInt( - selectEnd - ) - ) { + if (selectToInt(relativePos, lineIndex, charIndex) > selectToInt(selectEnd)) { return } selectStart[0] = relativePos diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 42886de3c..59943dc6c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -72,9 +72,9 @@ class ReadView(context: Context, attrs: AttributeSet) : } var isTextSelected = false private var pressOnTextSelected = false - private var firstRelativePage = 0 - private var firstLineIndex: Int = 0 - private var firstCharIndex: Int = 0 + var firstRelativePage = 0 + var firstLineIndex: Int = 0 + var firstCharIndex: Int = 0 val slopSquare by lazy { ViewConfiguration.get(context).scaledTouchSlop } private val tlRect = RectF() @@ -155,11 +155,19 @@ class ReadView(context: Context, attrs: AttributeSet) : */ @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { + + if (event.pointerCount > 1) { + isSelected = false + longPressed = false + removeCallbacks(longPressRunnable) + //cancelSelect() + return super.onTouchEvent(event) + } callBack.screenOffTimerStart() when (event.action) { MotionEvent.ACTION_DOWN -> { if (isTextSelected) { - curPage.cancelSelect() + cancelSelect() isTextSelected = false pressOnTextSelected = true } else { @@ -245,6 +253,14 @@ class ReadView(context: Context, attrs: AttributeSet) : pageDelegate?.onScroll() } + + /** + * 取消选中 + */ + fun cancelSelect() { + curPage.cancelSelect() + } + /** * 长按选择 */ @@ -357,6 +373,7 @@ class ReadView(context: Context, attrs: AttributeSet) : * 单击 */ private fun onSingleTapUp() { + cancelSelect() when { isTextSelected -> isTextSelected = false mcRect.contains(startX, startY) -> if (!isAbortAnim) { @@ -402,7 +419,7 @@ class ReadView(context: Context, attrs: AttributeSet) : /** * 选择文本 */ - private fun selectText(x: Float, y: Float) { + fun selectText(x: Float, y: Float) { curPage.selectText(x, y) { relativePage, lineIndex, charIndex -> when { relativePage > firstRelativePage -> { @@ -435,7 +452,7 @@ class ReadView(context: Context, attrs: AttributeSet) : fun onDestroy() { pageDelegate?.onDestroy() - curPage.cancelSelect() + cancelSelect() } fun fillPage(direction: PageDirection): Boolean { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt index 6a0e9b0f4..2b5aff8e9 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChar.kt @@ -1,9 +1,18 @@ package io.legado.app.ui.book.read.page.entities +import io.legado.app.data.entities.DrawLineEntity +import io.legado.app.data.entities.IdealEntity + data class TextChar( val charData: String, var start: Float, var end: Float, var selected: Boolean = false, - var isImage: Boolean = false + var isImage: Boolean = false, + // 是否是首行缩进字符 + var isIndent: Boolean = false, + // 划线 + var drawLineEntity: DrawLineEntity? = null, + // 想法 + var idealEntity: IdealEntity? = null ) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index dfeb4cb6d..57c3b591e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -119,10 +119,10 @@ object ChapterProvider { if (matcher.find()) { matcher.group(1)?.let { src -> //if (!book.isEpub()) { - durY = setTypeImage( - book, bookChapter, src, - durY, textPages, book.getImageStyle() - ) + durY = setTypeImage( + book, bookChapter, src, + durY, textPages, book.getImageStyle() + ) //} } } else { @@ -324,13 +324,14 @@ object ChapterProvider { srcList.removeFirst(), start = paddingLeft + x, end = paddingLeft + x1, - isImage = true + isImage = true, + isIndent = true ) ) } else { textLine.textChars.add( TextChar( - it, start = paddingLeft + x, end = paddingLeft + x1 + it, start = paddingLeft + x, end = paddingLeft + x1, isIndent = true ) ) }