diff --git a/.idea/gradle.xml b/.idea/gradle.xml index a2d7c21..2cbbe00 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -11,6 +11,7 @@ diff --git a/app/build.gradle b/app/build.gradle index bc9196e..7fe70a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,14 +9,13 @@ android { defaultConfig { applicationId "com.zjgsu.jianshu" - minSdk 21 - targetSdk 31 + minSdk 19 + targetSdkVersion 31 versionCode 1 versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true } - buildTypes { release { minifyEnabled false @@ -57,4 +56,6 @@ dependencies { implementation 'junit:junit:4.12' implementation 'com.jakewharton:butterknife:7.0.1' implementation 'com.facebook.fresco:fresco:0.9.0+' + implementation project(':hwtxtreaderlib') + implementation 'androidx.multidex:multidex:2.0.1' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cb193e6..2decb61 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,13 +12,9 @@ - - - - - - + + - - - - - + + + + + - - + + @@ -50,23 +46,23 @@ - - + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/zjgsu/jianshu/Activity/BookInformationActivity.kt b/app/src/main/java/com/zjgsu/jianshu/Activity/BookInformationActivity.kt index cb1c4d6..37fe275 100644 --- a/app/src/main/java/com/zjgsu/jianshu/Activity/BookInformationActivity.kt +++ b/app/src/main/java/com/zjgsu/jianshu/Activity/BookInformationActivity.kt @@ -18,14 +18,21 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.recyclerview.widget.StaggeredGridLayoutManager import cn.bmob.v3.listener.SaveListener +import com.bifan.txtreaderlib.ui.HwTxtPlayActivity import com.bumptech.glide.Glide import com.zjgsu.jianshu.Adapter.GoodperceptionAdapter import com.zjgsu.jianshu.Bmob.BookShelf import com.zjgsu.jianshu.Bmob.Book_info_bmob +import com.zjgsu.jianshu.Bmob.Bookcontent_bmob import com.zjgsu.jianshu.Bmob.Perception_bmob import kotlinx.android.synthetic.main.activity_book_info.* import kotlinx.android.synthetic.main.activity_sendspecific.* import kotlinx.android.synthetic.main.bookinfo_title.* +import okhttp3.* +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream class BookInformationActivity : AppCompatActivity() { @@ -38,6 +45,7 @@ class BookInformationActivity : AppCompatActivity() { private lateinit var picurl:String private var sourceActivityClass: Class<*>? = null private lateinit var bookInfoTextView: TextView + private lateinit var bookPath:String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_book_info) @@ -102,7 +110,6 @@ class BookInformationActivity : AppCompatActivity() { val str: String = String.format("%.2f", tuijian_res) textView10.text = "简书推荐值 " + str + "%" } - updateBookRankings(book) bookinfo_bookname.text = book.name bookinfo_authorname.text = book.author_name bookinfo_introduce.text=book.introduce @@ -181,9 +188,8 @@ class BookInformationActivity : AppCompatActivity() { showBookIntroDialog(bookInfoTextView.text.toString()) } startToRead.setOnClickListener { - val intent = Intent(this, BookIntroActivity::class.java) - intent.putExtra("Book_name", bookName) - startActivity(intent) + downloadFile(bookName) + } } //TODO:以后可以考虑再次点击这个“已加入的按钮”后,弹出一个对话框:是否将该书移除,然后将该书从书架中移除。而不是弹:该书已在您的书架中 @@ -268,21 +274,6 @@ class BookInformationActivity : AppCompatActivity() { // } // } - private fun updateBookRankings(book: Book_info_bmob, isRecommended: Boolean = false) { - if (isRecommended) { - book.settuijian(book.tuijian + 1) - } else { - book.setyiban(book.yiban + 1) - book.setbuxing(book.buxing + 1) - } - book.update(book.objectId, object : UpdateListener() { - override fun done(e: BmobException?) { - if (e != null) { - Log.e("BookInfo", "Error updating book rankings: ${e.message}") - } - } - }) - } private fun judgeComment(){ val query=BmobQuery() query.addWhereEqualTo("userid",userId) @@ -313,4 +304,59 @@ class BookInformationActivity : AppCompatActivity() { .error(R.drawable.fail_load) // 设置加载失败时显示的图片 .into(bookinfo_img) // 将图片加载到指定的 ImageView 中 } + private fun downloadFile(bookName: String) { + val localPath = getExternalFilesDir(null)?.absolutePath + "/$bookName.txt" + val file = File(localPath) + + // 检查本地是否已经存在该书籍的 TXT 文件 + if (file.exists()) { + // 如果存在,则直接打开该文件 + runOnUiThread { + HwTxtPlayActivity.loadTxtFile(this@BookInformationActivity, file.absolutePath) + } + } else { + // 如果不存在,则从网上下载 + val querybook = BmobQuery() + querybook.addWhereEqualTo("bookname", bookName) + querybook.findObjects(object : FindListener() { + override fun done(books: MutableList?, e: BmobException?) { + if (e == null && books != null && books.isNotEmpty()) { + val book = books[0] + bookPath = book.bookpath.url + downloadFromUrl(bookPath, localPath) + } else { + Log.e("BookInfo", "Error loading book info: ${e?.message}") + } + } + }) + } + } + + private fun downloadFromUrl(bookPath: String, localPath: String) { + val client = OkHttpClient() + val request = Request.Builder().url(bookPath).build() + client.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + Log.e("Download", "Download failed: ${e.message}") + runOnUiThread { + Toast.makeText(this@BookInformationActivity, "Download failed", Toast.LENGTH_SHORT).show() + } + } + override fun onResponse(call: Call, response: Response) { + response.body?.let { body -> + val inputStream: InputStream = body.byteStream() + val file = File(localPath) + val outputStream = FileOutputStream(file) + inputStream.use { input -> + outputStream.use { output -> + input.copyTo(output) + runOnUiThread { + HwTxtPlayActivity.loadTxtFile(this@BookInformationActivity, file.absolutePath) + } + } + } + } + } + }) + } } \ No newline at end of file diff --git a/app/src/main/java/com/zjgsu/jianshu/Activity/BookShelfActivity.kt b/app/src/main/java/com/zjgsu/jianshu/Activity/BookShelfActivity.kt index 79aeab9..36425ba 100644 --- a/app/src/main/java/com/zjgsu/jianshu/Activity/BookShelfActivity.kt +++ b/app/src/main/java/com/zjgsu/jianshu/Activity/BookShelfActivity.kt @@ -86,9 +86,6 @@ class BookShelfActivity : AppCompatActivity() { bookcity.setOnClickListener { NavigationHelper.navigateTo(this, MainActivity::class.java) } - bookshelf.setOnClickListener { - NavigationHelper.navigateTo(this, BookShelfActivity::class.java) - } reading_comprehension.setOnClickListener { NavigationHelper.navigateTo(this, PerceptionActivity::class.java) } diff --git a/app/src/main/java/com/zjgsu/jianshu/Activity/MyActivity.kt b/app/src/main/java/com/zjgsu/jianshu/Activity/MyActivity.kt index 538e491..ead7356 100644 --- a/app/src/main/java/com/zjgsu/jianshu/Activity/MyActivity.kt +++ b/app/src/main/java/com/zjgsu/jianshu/Activity/MyActivity.kt @@ -107,9 +107,6 @@ class MyActivity : AppCompatActivity() { reading_comprehension.setOnClickListener { NavigationHelper.navigateTo(this, PerceptionActivity::class.java) } - homepage.setOnClickListener { - NavigationHelper.navigateTo(this, MyActivity::class.java) - } } } diff --git a/app/src/main/java/com/zjgsu/jianshu/Activity/PerceptionActivity.kt b/app/src/main/java/com/zjgsu/jianshu/Activity/PerceptionActivity.kt index bf02d27..4f9f6bd 100644 --- a/app/src/main/java/com/zjgsu/jianshu/Activity/PerceptionActivity.kt +++ b/app/src/main/java/com/zjgsu/jianshu/Activity/PerceptionActivity.kt @@ -67,9 +67,6 @@ class PerceptionActivity : AppCompatActivity() { bookshelf.setOnClickListener { NavigationHelper.navigateTo(this, BookShelfActivity::class.java) } - reading_comprehension.setOnClickListener { - NavigationHelper.navigateTo(this, PerceptionActivity::class.java) - } homepage.setOnClickListener { NavigationHelper.navigateTo(this, MyActivity::class.java) } diff --git a/app/src/main/java/com/zjgsu/jianshu/Adapter/BillboardAdapter.kt b/app/src/main/java/com/zjgsu/jianshu/Adapter/BillboardAdapter.kt index 9005118..9495959 100644 --- a/app/src/main/java/com/zjgsu/jianshu/Adapter/BillboardAdapter.kt +++ b/app/src/main/java/com/zjgsu/jianshu/Adapter/BillboardAdapter.kt @@ -42,7 +42,6 @@ class BillboardAdapter(var listOfbookRankList: List>) : Recycler override fun onBindViewHolder(holder: ViewHolder, position: Int) { val bookRankList = listOfbookRankList[position] - Log.d("BillboardAdapter", "Size of bookRankList at position $position") if(position==0) { holder.bookRank_title.text="热门图书榜" holder.bookRank_decr1.text = "在读人数:"+bookRankList[0].courtOfReaders.toString() diff --git a/app/src/main/java/com/zjgsu/jianshu/Adapter/PerceptionAdapter.kt b/app/src/main/java/com/zjgsu/jianshu/Adapter/PerceptionAdapter.kt index 13a1d92..09b2483 100644 --- a/app/src/main/java/com/zjgsu/jianshu/Adapter/PerceptionAdapter.kt +++ b/app/src/main/java/com/zjgsu/jianshu/Adapter/PerceptionAdapter.kt @@ -71,8 +71,6 @@ class PerceptionAdapter(val perceptionList: List) : for (i in list) { if (p.userid.equals(i.objectId)) { holder.perception_posetPerson.text = i.nickName -// flag = true //找到 - Log.d("perception", "find sucessflly") //加载图片 Glide.with(holder.itemView.context) .load(i.face.url) // 确保你的Book对象有正确的图片URL @@ -110,55 +108,6 @@ class PerceptionAdapter(val perceptionList: List) : } } }) - //加载书籍封面 -// query.findObjects(object : FindListener() { -// override fun done(p0: MutableList?, p1: BmobException?) { -// if (p1 == null) { -// if (p0 != null && p0.size > 0) { -// for (p in p0) { -// Log.d("myLog", p.b_name) -// bookName=p.b_name -// holder.perceptionList_bookName.text=bookName -// val queryBook = BmobQuery() -// queryBook.findObjects(object : FindListener() { -// override fun done(list: List, e: BmobException?) { -// if (e == null) { -// for (i in list) { -// if (p.b_name.equals(i.name)) { -// holder.perceptionList_bookAuthor.text=i.author_name -// //加载图片 -// object : Thread() { -// override fun run() { -// try { -// val url = URL(i!!.picture?.url) -// val connection: HttpURLConnection = -// url.openConnection() as HttpURLConnection -// connection.setRequestMethod("GET") -// connection.setConnectTimeout(3000) -// val `in`: InputStream = connection.getInputStream() -// val bitmap: Bitmap = -// BitmapFactory.decodeStream(`in`) -// holder.perceptionList_bookPic.setImageBitmap(bitmap) -// } catch (e: Exception) { -// e.printStackTrace() -// } -// } -// }.start() -// -// -// } -// } -// } else { -// Log.d("myLog", "error") -// } -// } -// }) -// } -// } -// } -// } -// }) - holder.perception_bookInf.setOnClickListener() { val intent = Intent(holder.itemView.context, BookInformationActivity::class.java) intent.putExtra("Book_name", bookName) diff --git a/app/src/main/java/com/zjgsu/jianshu/Bmob/Bookcontent_bmob.kt b/app/src/main/java/com/zjgsu/jianshu/Bmob/Bookcontent_bmob.kt new file mode 100644 index 0000000..95baf8f --- /dev/null +++ b/app/src/main/java/com/zjgsu/jianshu/Bmob/Bookcontent_bmob.kt @@ -0,0 +1,9 @@ +package com.zjgsu.jianshu.Bmob + +import cn.bmob.v3.BmobObject +import cn.bmob.v3.datatype.BmobFile + +class Bookcontent_bmob:BmobObject() { + val bookpath:BmobFile=BmobFile() + val bookname:String="" +} \ No newline at end of file diff --git a/app/src/main/java/com/zjgsu/jianshu/Fragment/ContentFragment.kt b/app/src/main/java/com/zjgsu/jianshu/Fragment/ContentFragment.kt new file mode 100644 index 0000000..7048bff --- /dev/null +++ b/app/src/main/java/com/zjgsu/jianshu/Fragment/ContentFragment.kt @@ -0,0 +1,50 @@ +package com.zjgsu.jianshu.Fragment + +import android.graphics.Typeface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import com.zjgsu.jianshu.R + +class BookPageFragment : Fragment() { + private var bookContent: String? = null + private var pageIndex: Int = 0 + + companion object { + private const val ARG_BOOK_CONTENT = "book_content" + private const val ARG_PAGE_INDEX = "page_index" + + fun newInstance(bookContent: String, pageIndex: Int): BookPageFragment { + return BookPageFragment().apply { + arguments = Bundle().apply { + putString(ARG_BOOK_CONTENT, bookContent) + putInt(ARG_PAGE_INDEX, pageIndex) + } + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + bookContent = it.getString(ARG_BOOK_CONTENT) + pageIndex = it.getInt(ARG_PAGE_INDEX) + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_content, container, false) + val textViewContent: TextView = view.findViewById(R.id.textViewContent) + + // 计算当前页的内容 + val startIndex = pageIndex * (10 * 100) + val endIndex = minOf((pageIndex + 1) * (10 * 100), bookContent?.length ?: 0) + val currentPageContent = bookContent?.substring(startIndex, endIndex) ?: "" + textViewContent.text = currentPageContent + + return view + } +} diff --git a/app/src/main/res/drawable/chatbot.png b/app/src/main/res/drawable/chatbot.png new file mode 100644 index 0000000..92f77f6 Binary files /dev/null and b/app/src/main/res/drawable/chatbot.png differ diff --git a/app/src/main/res/drawable/ic_copy.png b/app/src/main/res/drawable/ic_copy.png new file mode 100644 index 0000000..bbe1f5d Binary files /dev/null and b/app/src/main/res/drawable/ic_copy.png differ diff --git a/app/src/main/res/drawable/ic_regenerate.png b/app/src/main/res/drawable/ic_regenerate.png new file mode 100644 index 0000000..bb76d4f Binary files /dev/null and b/app/src/main/res/drawable/ic_regenerate.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 723b403..a1af26d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -29,8 +29,10 @@ + + android:layout_height="match_parent" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + diff --git a/app/src/main/res/layout/fragment_content.xml b/app/src/main/res/layout/fragment_content.xml new file mode 100644 index 0000000..b8ad181 --- /dev/null +++ b/app/src/main/res/layout/fragment_content.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..a6b3dae --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 22dc47a..f19806a 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ buildscript { repositories { google() mavenCentral() + maven { url 'https://jitpack.io' } } dependencies { classpath "com.android.tools.build:gradle:7.0.2" @@ -12,8 +13,6 @@ buildscript { } } - - task clean(type: Delete) { delete rootProject.buildDir } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2f839a1..e2819d1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,3 +4,4 @@ distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME + diff --git a/hwtxtreaderlib/.gitignore b/hwtxtreaderlib/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/hwtxtreaderlib/.gitignore @@ -0,0 +1 @@ +/build diff --git a/hwtxtreaderlib/build.gradle b/hwtxtreaderlib/build.gradle new file mode 100644 index 0000000..5c16d85 --- /dev/null +++ b/hwtxtreaderlib/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.library' + + +android { + compileSdkVersion 31 + defaultConfig { + minSdkVersion 15 + targetSdkVersion 30 + versionCode 6 + versionName "4.5" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation files('libs/chardet.jar') + implementation 'com.google.android.material:material:1.3.0' + implementation 'io.github.bmob:android-sdk:3.9.4' + implementation 'io.reactivex.rxjava2:rxjava:2.2.8' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation 'com.squareup.okhttp3:okhttp:4.8.1' + implementation 'com.squareup.okio:okio:2.2.2' + implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + +} diff --git a/hwtxtreaderlib/libs/chardet.jar b/hwtxtreaderlib/libs/chardet.jar new file mode 100644 index 0000000..69f6503 Binary files /dev/null and b/hwtxtreaderlib/libs/chardet.jar differ diff --git a/hwtxtreaderlib/proguard-rules.pro b/hwtxtreaderlib/proguard-rules.pro new file mode 100644 index 0000000..f66c05e --- /dev/null +++ b/hwtxtreaderlib/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\developtool\sdk\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/hwtxtreaderlib/src/main/AndroidManifest.xml b/hwtxtreaderlib/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4db255e --- /dev/null +++ b/hwtxtreaderlib/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/hwtxtreaderlib/src/main/assets/config.properties b/hwtxtreaderlib/src/main/assets/config.properties new file mode 100644 index 0000000..4b2ebb9 --- /dev/null +++ b/hwtxtreaderlib/src/main/assets/config.properties @@ -0,0 +1 @@ +auth_key=sk-No44eOPA2V17ZQmuD897C61b905743B9Aa2d83B6EeF2C38a diff --git a/hwtxtreaderlib/src/main/assets/fonts/text_style.TTF b/hwtxtreaderlib/src/main/assets/fonts/text_style.TTF new file mode 100644 index 0000000..6b87c1b Binary files /dev/null and b/hwtxtreaderlib/src/main/assets/fonts/text_style.TTF differ diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/adapter/MessageAdapter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/adapter/MessageAdapter.java new file mode 100644 index 0000000..ecde23b --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/adapter/MessageAdapter.java @@ -0,0 +1,69 @@ +package com.bifan.txtreaderlib.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bifan.txtreaderlib.R; +import com.bifan.txtreaderlib.bean.Message; + +import java.util.List; + +public class MessageAdapter extends RecyclerView.Adapter { + private List messages; + + public MessageAdapter(List messages) { + this.messages = messages; + } + + @Override + public int getItemViewType(int position) { + Message message = messages.get(position); + return message.getIsuser(); + } + + @NonNull + @Override + public MessageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + View view; + if (viewType == 1) { + view = inflater.inflate(R.layout.item_message_user, parent, false); + } else { + view = inflater.inflate(R.layout.item_message_bot, parent, false); + } + return new MessageViewHolder(view, viewType); + } + + + @Override + public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) { + Message message = messages.get(position); + if (message.getIsuser() == 1) { + holder.textViewMessage.setText(message.getContent()); + } else { + holder.textViewMessage.setText(message.getContent()); + } + } + + @Override + public int getItemCount() { + return messages.size(); + } + + static class MessageViewHolder extends RecyclerView.ViewHolder { + TextView textViewMessage; + + MessageViewHolder(View itemView, int viewType) { + super(itemView); + if (viewType == 1) { // 假设 1 是用户消息 + textViewMessage = itemView.findViewById(R.id.textViewMessageUser); + } else { // 非 1 即机器人消息 + textViewMessage = itemView.findViewById(R.id.textViewMessageBot); + } + } + } +} \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Chapter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Chapter.java new file mode 100644 index 0000000..4182507 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Chapter.java @@ -0,0 +1,99 @@ +package com.bifan.txtreaderlib.bean; + +import com.bifan.txtreaderlib.interfaces.IChapter; + +/** + * @description + * @author bifan-wei + * @time 2021/11/13 16:05 + */ + +public class Chapter implements IChapter { + public String Title; + public int Index;//章节位置 + public int StartParagraphIndex; + public int EndParagraphIndex; + public int StartCharIndex;//字符开始位置 + public int EndCharIndex;//字符结束位置 + public int StartIndex;//第一个字符在全文字符的位置 + + public Chapter(String title, int startParagraphIndex, int endParagraphIndex) { + Title = title; + StartParagraphIndex = startParagraphIndex; + EndParagraphIndex = endParagraphIndex; + } + + public Chapter(int startIndex, int index, String title, int startParagraphIndex, int endParagraphIndex, int startCharIndex, int endCharIndex) { + StartIndex = startIndex; + Title = title; + Index = index; + StartParagraphIndex = startParagraphIndex; + EndParagraphIndex = endParagraphIndex; + StartCharIndex = startCharIndex; + EndCharIndex = endCharIndex; + } + + public Chapter() { + } + + public void setTitle(String title) { + Title = title; + } + + @Override + public int getStartIndex() { + return StartIndex; + } + + @Override + public int getStartCharIndex() { + return StartCharIndex; + } + + @Override + public int getEndCharIndex() { + return EndCharIndex; + } + + @Override + public int getStartParagraphIndex() { + return StartParagraphIndex; + } + + @Override + public int getEndParagraphIndex() { + return EndParagraphIndex; + } + + @Override + public String getTitle() { + return Title; + } + + @Override + public void setStartParagraphIndex(int index) { + StartParagraphIndex = index; + } + + @Override + public void setEndParagraphIndex(int index) { + EndParagraphIndex = index; + } + + @Override + public int getIndex() { + return Index; + } + + @Override + public String toString() { + return "Chapter{" + + "Title='" + Title + '\'' + + ", Index=" + Index + + ", StartParagraphIndex=" + StartParagraphIndex + + ", EndParagraphIndex=" + EndParagraphIndex + + ", StartCharIndex=" + StartCharIndex + + ", EndCharIndex=" + EndCharIndex + + '}'; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatMessage_bmob.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatMessage_bmob.java new file mode 100644 index 0000000..9111466 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatMessage_bmob.java @@ -0,0 +1,49 @@ +package com.bifan.txtreaderlib.bean; + +import cn.bmob.v3.BmobObject; + +public class ChatMessage_bmob extends BmobObject { + private String content; + private Integer isuser; + private String relatedBook; + private String userid; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getIsuser() { + return isuser; + } + + public void setIsuser(Integer isuser) { + this.isuser = isuser; + } + + public String getRelatedBook() { + return relatedBook; + } + + public void setRelatedBook(String relatedBook) { + this.relatedBook = relatedBook; + } + + public String getUserid() { + return userid; + } + + public void setUserid(String userid) { + this.userid = userid; + } + + public ChatMessage_bmob(String content, Integer isuser, String relatedBook, String userid) { + this.content = content; + this.isuser = isuser; + this.relatedBook = relatedBook; + this.userid = userid; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatRequest.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatRequest.java new file mode 100644 index 0000000..86e20da --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatRequest.java @@ -0,0 +1,71 @@ +package com.bifan.txtreaderlib.bean; + +import java.util.List; + + +//curl https://api.openai.com/v1/chat/completions \ +// -H "Content-Type: application/json" \ +// -H "Authorization: Bearer $OPENAI_API_KEY" \ +// -d '{ +// "model": "gpt-3.5-turbo", +// "messages": [ +// { +// "role": "system", +// "content": "You are a helpful assistant." +// }, +// { +// "role": "user", +// "content": "Hello!" +// } +// ] +// }' +public class ChatRequest { + private String model; + private List messages; + private Integer maxTokens; + private Double temperature; + + public ChatRequest(List messages) { + this.messages = messages; + this.model = "gpt-3.5-turbo"; // 默认模型 + this.maxTokens = 150; // 默认的最大 token 数 + this.temperature = 0.7; + } + public void setMaxTokens(Integer maxTokens) { + this.maxTokens = maxTokens; + } + public Integer getMaxTokens() { + return maxTokens; + } + public void setTemperature(Double temperature) { + this.temperature = temperature; + } + + public Double getTemperature() { + return temperature; + } + + public void setModel(String model) { + this.model = model; + } + + public List getMessages() { + return messages; + } + + public String getModel() { + return model; + } + + // Message 类 + public static class Message { + String role; + String content; + + public Message(String role, String content) { + this.role = role; + this.content = content; + } + } +} + diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatResponse.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatResponse.java new file mode 100644 index 0000000..22a2800 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/ChatResponse.java @@ -0,0 +1,47 @@ +package com.bifan.txtreaderlib.bean; + +import java.util.List; + +//{ +// "id": "chatcmpl-123", +// "object": "chat.completion", +// "created": 1677652288, +// "model": "gpt-3.5-turbo-0125", +// "system_fingerprint": "fp_44709d6fcb", +// "choices": [{ +// "index": 0, +// "message": { +// "role": "assistant", +// "content": "\n\nHello there, how may I assist you today?", +// }, +// "logprobs": null, +// "finish_reason": "stop" +// }], +// "usage": { +// "prompt_tokens": 9, +// "completion_tokens": 12, +// "total_tokens": 21 +// } +// } + + +public class ChatResponse { + String id; + String object; + long created; + String model; + public List choices; + + public static class Choice { + int index; + public Message message; + // 你可以根据需要继续添加其他字段如 logprobs, finish_reason 等 + } + + public static class Message { + String role; + public String content; + } + + // 假设你已经有 getter 方法或者你可以自行添加 +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultLeftSlider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultLeftSlider.java new file mode 100644 index 0000000..81cb8d4 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultLeftSlider.java @@ -0,0 +1,37 @@ +package com.bifan.txtreaderlib.bean; + +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * created by : bifan-wei + */ + +public class DefaultLeftSlider extends Slider{ + @Override + public float getX(float dx) { + return Right + dx +5; + } + + @Override + public float getY(float dy) { + return Top + dy - 5; + } + + @Override + public Path getPath(TxtChar txtChar,Path path) { + if (txtChar != null) { + Path p = path; + p.reset(); + p.moveTo(txtChar.Left, txtChar.Bottom); + p.lineTo(txtChar.Left, txtChar.Bottom + SliderWidth); + Rect rect = new Rect(txtChar.Left - SliderWidth * 2, txtChar.Bottom, txtChar.Left, txtChar.Bottom + SliderWidth * 2); + p.addArc(new RectF(rect), 0, 270); + p.lineTo(txtChar.Left, txtChar.Bottom); + return p; + } else { + return null; + } + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultRightSlider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultRightSlider.java new file mode 100644 index 0000000..08caec0 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/DefaultRightSlider.java @@ -0,0 +1,37 @@ +package com.bifan.txtreaderlib.bean; + +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * created by : bifan-wei + */ + +public class DefaultRightSlider extends Slider{ + @Override + public float getX(float dx) { + return Left + dx - 5; + } + + @Override + public float getY(float dy) { + return Top + dy - 5; + } + + @Override + public Path getPath(TxtChar txtChar,Path path) { + if (txtChar != null) { + Path p = path; + p.reset(); + p.moveTo(txtChar.Right, txtChar.Bottom + SliderWidth); + p.lineTo(txtChar.Right, txtChar.Bottom); + p.lineTo(txtChar.Right + SliderWidth, txtChar.Bottom); + Rect rect = new Rect(txtChar.Right, txtChar.Bottom, txtChar.Right + SliderWidth * 2, txtChar.Bottom + SliderWidth * 2); + p.addArc(new RectF(rect), -90, 270); + return p; + } else { + return null; + } + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/EnChar.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/EnChar.java new file mode 100644 index 0000000..553f6d1 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/EnChar.java @@ -0,0 +1,23 @@ +package com.bifan.txtreaderlib.bean; + +import android.graphics.Color; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class EnChar extends TxtChar { + public static int DefaultTextColor = Color.parseColor("#45a1cd"); + public EnChar(char aChar) { + super(aChar); + } + @Override + public int getTextColor() { + return DefaultTextColor; + } + + @Override + public int getCharType() { + return Char_En; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/FileReadRecordBean.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/FileReadRecordBean.java new file mode 100644 index 0000000..e0a5f10 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/FileReadRecordBean.java @@ -0,0 +1,26 @@ +package com.bifan.txtreaderlib.bean; + +/** + * created by : bifan-wei + */ + +public class FileReadRecordBean { + public String fileHashName; + public int id; + public String fileName; + public String filePath; + public int paragraphIndex; + public int chartIndex; + + @Override + public String toString() { + return "FileReadRecordBean{" + + "fileHashName='" + fileHashName + '\'' + + ", id=" + id + + ", fileName='" + fileName + '\'' + + ", filePath='" + filePath + '\'' + + ", paragraphIndex='" + paragraphIndex + '\'' + + ", chartIndex='" + chartIndex + '\'' + + '}'; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Message.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Message.java new file mode 100644 index 0000000..00ccf38 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Message.java @@ -0,0 +1,27 @@ +package com.bifan.txtreaderlib.bean; + +public class Message { + private String content; + private Integer isuser; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Integer getIsuser() { + return isuser; + } + + public void setIsuser(Integer isuser) { + this.isuser = isuser; + } + + public Message(String content, Integer isuser) { + this.content = content; + this.isuser = isuser; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiLeftSlider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiLeftSlider.java new file mode 100644 index 0000000..ac10ce8 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiLeftSlider.java @@ -0,0 +1,47 @@ +package com.bifan.txtreaderlib.bean; + +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * created by : bifan-wei + */ + +public class MuiLeftSlider extends Slider { + @Override + public float getX(float dx) { + return Right + dx - 5; + } + + @Override + public float getY(float dy) { + return Top + dy - 5; + } + + @Override + public Path getPath(TxtChar txtChar, Path path) { + if (txtChar != null) { + + int r = SliderWidth; + int leftWidth = (int) (Math.cos(30) * r); + int heightWidth = r * 3 / 2; + Path p = path; + p.reset(); + p.moveTo(txtChar.Left, txtChar.Top); + p.lineTo(txtChar.Left - leftWidth, txtChar.Top - heightWidth); + + Rect rect = new Rect( + txtChar.Left - r, + txtChar.Top - 3 * r, + txtChar.Left + r, + txtChar.Top - r); + + p.addArc(new RectF(rect), 150, 240); + p.lineTo(txtChar.Left, txtChar.Top); + return p; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiRightSlider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiRightSlider.java new file mode 100644 index 0000000..9a0f355 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/MuiRightSlider.java @@ -0,0 +1,46 @@ +package com.bifan.txtreaderlib.bean; + +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; + +/** + * created by : bifan-wei + */ + +public class MuiRightSlider extends Slider { + @Override + public float getX(float dx) { + return Left + dx + 5; + } + + @Override + public float getY(float dy) { + return Bottom + dy + 5; + } + + @Override + public Path getPath(TxtChar txtChar, Path path) { + if (txtChar != null) { + int r = SliderWidth; + int leftWidth = (int) (Math.cos(30) * r); + int heightWidth = r * 3 / 2; + Path p = path; + p.reset(); + p.moveTo(txtChar.Right, txtChar.Bottom); + p.lineTo(txtChar.Right + leftWidth, txtChar.Bottom + heightWidth); + + Rect rect = new Rect( + txtChar.Right - r, + txtChar.Bottom + r, + txtChar.Right + r, + txtChar.Bottom +3*r); + + p.addArc(new RectF(rect), -30, 240); + p.lineTo(txtChar.Right, txtChar.Bottom); + return p; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/NumChar.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/NumChar.java new file mode 100644 index 0000000..7586556 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/NumChar.java @@ -0,0 +1,23 @@ +package com.bifan.txtreaderlib.bean; + +import android.graphics.Color; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class NumChar extends TxtChar { + public static int DefaultTextColor = Color.parseColor("#45a1cf"); + public NumChar(char aChar) { + super(aChar); + } + @Override + public int getTextColor() { + return DefaultTextColor; + } + + @Override + public int getCharType() { + return Char_Num; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Page.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Page.java new file mode 100644 index 0000000..56b06cc --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Page.java @@ -0,0 +1,236 @@ +package com.bifan.txtreaderlib.bean; + +import com.bifan.txtreaderlib.interfaces.ICursor; +import com.bifan.txtreaderlib.interfaces.IPage; +import com.bifan.txtreaderlib.interfaces.ITxtLine; + +import java.util.ArrayList; +import java.util.List; + + +/* + * create by bifan-wei + * 2017-11-13 + */ +public class Page implements IPage, ICursor { + private int CurrentIndex; + private List lines = null; + private boolean fullPage = false; + + @Override + public int CurrentIndex() { + return CurrentIndex; + } + + @Override + public void addLineTo(ITxtLine line, int index) { + if (line == null) { + throw new NullPointerException("line == null on addLine form Page"); + } + if (lines == null) { + lines = new ArrayList<>(); + } + lines.add(index, line); + } + + @Override + public void addLine(ITxtLine line) { + if (line == null) { + throw new NullPointerException("line == null on addLine form Page"); + } + if (lines == null) { + lines = new ArrayList<>(); + } + lines.add(line); + } + + @Override + public void setLines(List lines) { + this.lines = lines; + } + + @Override + public int getCount() { + return lines == null ? 0 : lines.size(); + } + + @Override + public void moveToPosition(int index) { + if (HasData()) { + if (index < 0 || index >= getCount()) { + throw new ArrayIndexOutOfBoundsException + (" moveToPosition index OutOfBoundsException from page"); + } + CurrentIndex = index; + Current(); + } + + } + + @Override + public TxtChar getFirstChar() { + ITxtLine firstLine = getFirstLine(); + if (!firstLine.HasData()) { + return null; + } else { + return firstLine.getFirstChar(); + } + } + + @Override + public void moveToFirst() { + moveToPosition(0); + } + + @Override + public TxtChar getLastChar() { + ITxtLine lastLine = getLastLine(); + if (!lastLine.HasData()) { + return null; + } else { + return lastLine.getLastChar(); + } + } + + @Override + public void moveToLast() { + CurrentIndex = getCount() - 1; + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + } + + @Override + public ITxtLine getFirstLine() { + moveToFirst(); + return Current(); + } + + @Override + public void moveToNext() { + CurrentIndex++; + if (CurrentIndex >= getCount()) { + CurrentIndex = getCount() - 1; + } + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + } + + @Override + public ITxtLine getLastLine() { + moveToLast(); + return Current(); + } + + @Override + public void moveToPrevious() { + CurrentIndex--; + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + } + + @Override + public boolean isFirst() { + return CurrentIndex == 0; + } + + @Override + public ICursor getLineCursor() { + return this; + } + + @Override + public boolean isLast() { + return CurrentIndex == getCount() - 1; + } + + @Override + public boolean isFullPage() { + return fullPage; + } + + @Override + public void setFullPage(boolean fullPage) { + this.fullPage = fullPage; + } + + @Override + public int getLineNum() { + return getCount(); + } + + private Boolean AfterLast = false; + private Boolean BeforeFirst = false; + + @Override + public boolean isBeforeFirst() { + return BeforeFirst; + } + + + @Override + public boolean isAfterLast() { + return AfterLast; + } + + @Override + public Boolean HasData() { + return getCount() > 0; + } + + @Override + public ITxtLine Pre() { + CurrentIndex--; + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + return Current(); + } + + @Override + public ITxtLine Next() { + CurrentIndex++; + if (CurrentIndex >= getCount()) { + CurrentIndex = getCount() - 1; + } + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + return Current(); + } + + @Override + public ITxtLine Current() { + AfterLast = isLast(); + BeforeFirst = isFirst(); + return lines == null ? null : getLine(CurrentIndex); + } + + @Override + public ITxtLine getLine(int index) { + return lines == null ? null : lines.get(index); + } + + @Override + public String toString() { + String str = ""; + if (HasData()) { + for (ITxtLine l : lines) { + str = str + l.getLineStr() + "\r\n"; + } + } + return str; + } + + @Override + public List getLines() { + return lines; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Slider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Slider.java new file mode 100644 index 0000000..85e2a9d --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/Slider.java @@ -0,0 +1,31 @@ +package com.bifan.txtreaderlib.bean; + +import android.graphics.Path; + +/** + * Created by bifan-wei + * on 2017/12/5. + */ + +public abstract class Slider { + public Boolean ShowBellow= true; + public int Left; + public int Right; + public int Top; + public int Bottom; + public int SliderWidth; + + public abstract float getX(float dx);//手势判断位置x坐标 + public abstract float getY(float dy);//手势判断位置y坐标 + public abstract Path getPath(TxtChar txtChar,Path path);//获取当前滑动条Path + @Override + public String toString() { + return "Slider{" + + "ShowBellow=" + ShowBellow + + ", Left=" + Left + + ", Right=" + Right + + ", Top=" + Top + + ", Bottom=" + Bottom + + '}'; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtChar.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtChar.java new file mode 100644 index 0000000..0526095 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtChar.java @@ -0,0 +1,72 @@ +package com.bifan.txtreaderlib.bean; + + +import android.graphics.Color; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class TxtChar { + public static final int Char_text = 0x01; + public static final int Char_Num = 0x02; + public static final int Char_En = 0x03; + + public char Char; + public int ParagraphIndex; + public float CharWidth = 0; + public int CharIndex; + public int TextColor = Color.BLACK; + public int PositionX; + public int PositionY; + public int Left; + public int Right; + public int Bottom; + public int Top; + + public TxtChar(char aChar) { + Char = aChar; + } + + public int getTextColor() { + return TextColor; + } + + public int getCharType() { + return Char_text; + } + + public char getValue() { + return Char; + } + + public String getValueStr() { + return String.valueOf(getValue()); + } + + @Override + public String toString() { + return "TxtChar{" + + "Char=" + Char + + ", ParagraphIndex=" + ParagraphIndex + + ", CharWidth=" + CharWidth + + ", CharIndex=" + CharIndex + + ", TextColor=" + TextColor + + ", PositionX=" + PositionX + + ", PositionY=" + PositionY + + ", Left=" + Left + + ", Right=" + Right + + ", Bottom=" + Bottom + + ", Top=" + Top + + '}'; + } + + @Override + public boolean equals(Object obj) { + TxtChar to = (TxtChar) obj; + if (to != null) { + return ParagraphIndex == to.ParagraphIndex && CharIndex == to.CharIndex && Char == to.Char && Top == to.Top; + } + return false; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtFileMsg.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtFileMsg.java new file mode 100644 index 0000000..d001e22 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtFileMsg.java @@ -0,0 +1,18 @@ +package com.bifan.txtreaderlib.bean; + +/** + * created by : bifan-wei + */ + +public class TxtFileMsg { + public String FilePath; + public String FileName; + public String HexName; + public long FileSize; + public int CurrentParagraphIndex; + public int CurrentCharIndex; + public int PreParagraphIndex =-1; + public int PreCharIndex = -1; + public String FileCode; + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtLine.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtLine.java new file mode 100644 index 0000000..4a835a1 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtLine.java @@ -0,0 +1,224 @@ +package com.bifan.txtreaderlib.bean; + +import com.bifan.txtreaderlib.interfaces.ICursor; +import com.bifan.txtreaderlib.interfaces.ITxtLine; + +import java.util.ArrayList; +import java.util.List; + + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class TxtLine implements ITxtLine, ICursor { + private int CurrentIndex; + private List chars = null; + private boolean isParagraphEndLine = false; + + public TxtLine() { + } + @Override + public int CurrentIndex() { + return CurrentIndex; + } + + @Override + public Boolean HasData() { + return getCount() != 0; + } + + @Override + public List getTxtChars() { + return chars; + } + + @Override + public void addChar(TxtChar txtChar) { + if (chars == null) { + chars = new ArrayList<>(); + } + chars.add(txtChar); + + } + + + @Override + public int getCount() { + return chars == null ? 0 : chars.size(); + } + + @Override + public void moveToPosition(int index) { + if (index < 0 || index >= getCount()) { + throw new ArrayIndexOutOfBoundsException(" moveToPosition index OutOfBoundsException"); + } + CurrentIndex = index; + } + + @Override + public int getCharNum() { + return getCount(); + } + + @Override + public void moveToFirst() { + CurrentIndex = 0; + Current(); + } + + @Override + public TxtChar getFirstChar() { + CurrentIndex = 0; + return Current(); + } + + @Override + public void moveToLast() { + CurrentIndex = getCount() - 1; + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + Current(); + } + + @Override + public TxtChar getLastChar() { + CurrentIndex = getCount() - 1; + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + return Current(); + } + + @Override + public void moveToNext() { + CurrentIndex++; + if (CurrentIndex >= getCount()) { + CurrentIndex = getCount() - 1; + } + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + } + + @Override + public TxtChar getChar(int index) { + if (index < 0 || index >= getCount()) { + throw new ArrayIndexOutOfBoundsException(" moveToPosition index OutOfBoundsException"); + } + return chars == null ? null : chars.get(index); + } + + @Override + public void moveToPrevious() { + CurrentIndex--; + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + } + + @Override + public boolean isFirst() { + return CurrentIndex == 0; + } + + @Override + public ICursor getCharCursor() { + return this; + } + + @Override + public boolean isLast() { + return CurrentIndex == getCount() - 1; + } + + @Override + public String getLineStr() { + String str = ""; + for (TxtChar txtChar : chars) { + str = str + txtChar.getValue(); + } + return str; + } + + + @Override + public boolean isBeforeFirst() { + return BeforeFirst; + } + + @Override + public char[] getLineChar() { + return (getLineStr() + "").toCharArray(); + } + + @Override + public boolean isAfterLast() { + return AfterLast; + } + + private Boolean AfterLast = false; + private Boolean BeforeFirst = false; + + @Override + public TxtChar Pre() { + CurrentIndex--; + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + return Current(); + } + + @Override + public TxtChar Next() { + CurrentIndex++; + if (CurrentIndex >= getCount()) { + CurrentIndex = getCount() - 1; + } + if (CurrentIndex < 0) { + CurrentIndex = 0; + } + moveToPosition(CurrentIndex); + return Current(); + } + + @Override + public TxtChar Current() { + if (isLast()) { + AfterLast = true; + } else { + AfterLast = false; + } + if (isFirst()) { + BeforeFirst = true; + } else { + BeforeFirst = false; + } + return chars == null ? null : getChar(CurrentIndex); + } + + @Override + public boolean isParagraphEndLine() { + return isParagraphEndLine; + } + + public void setParagraphEndLine(boolean paragraphEndLine) { + isParagraphEndLine = paragraphEndLine; + } + + @Override + public void Clear() { + if (chars != null) { + chars.clear(); + } + } + + @Override + public String toString() { + return "" + getLineStr(); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtMsg.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtMsg.java new file mode 100644 index 0000000..c367ba6 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/bean/TxtMsg.java @@ -0,0 +1,10 @@ +package com.bifan.txtreaderlib.bean; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public enum TxtMsg { + InitError, + FileNoExist, +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/constant/Constants.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/constant/Constants.java new file mode 100644 index 0000000..90e9c91 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/constant/Constants.java @@ -0,0 +1,11 @@ +package com.bifan.txtreaderlib.constant; + +import com.bifan.txtreaderlib.utils.ConfigUtils; + +public class Constants { + public static final String AUTH_KEY; + + static { + AUTH_KEY = ConfigUtils.getAuthKey(); + } +} \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ChatGPTService.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ChatGPTService.java new file mode 100644 index 0000000..807779b --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ChatGPTService.java @@ -0,0 +1,15 @@ +package com.bifan.txtreaderlib.interfaces; + +import com.bifan.txtreaderlib.bean.ChatRequest; +import com.bifan.txtreaderlib.bean.ChatResponse; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.Headers; +import retrofit2.http.POST; + +public interface ChatGPTService { + @Headers("Authorization: Bearer sk-uucSf3pIkBZSYHzQ6028C9FcCe4e4d61B457497b4d72C894") + @POST("v1/chat/completions") + Call postMessage(@Body ChatRequest request); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICenterAreaClickListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICenterAreaClickListener.java new file mode 100644 index 0000000..88bc334 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICenterAreaClickListener.java @@ -0,0 +1,19 @@ +package com.bifan.txtreaderlib.interfaces; + +/** + * created by : bifan-wei + */ + +public interface ICenterAreaClickListener { + /** + * @param widthPercentInView + * @return 返回是否处理了这个事件,如果已经处理了,将可能会执行翻页事件 + */ + boolean onCenterClick(float widthPercentInView); + + /** + * @param widthPercentInView + * @return 返回是否处理了这个事件,如果已经处理了,将可能会执行翻页事件 + */ + boolean onOutSideCenterClick(float widthPercentInView); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapter.java new file mode 100644 index 0000000..33b519e --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapter.java @@ -0,0 +1,27 @@ +package com.bifan.txtreaderlib.interfaces; + +/** + * Created by HP on 2017/11/15. + */ + +public interface IChapter { + + int getIndex(); + + int getStartParagraphIndex(); + + int getEndParagraphIndex(); + + int getStartCharIndex(); + + int getEndCharIndex(); + + int getStartIndex(); + + String getTitle(); + + void setStartParagraphIndex(int index); + + void setEndParagraphIndex(int index); + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapterMatcher.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapterMatcher.java new file mode 100644 index 0000000..943b3fe --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IChapterMatcher.java @@ -0,0 +1,14 @@ +package com.bifan.txtreaderlib.interfaces; + +/** + * created by : bifan-wei + */ + +public interface IChapterMatcher { + /** + * @param paragraphData 段落数据 + * @param ParagraphIndex 当前段落位置 + * @return + */ + IChapter match(String paragraphData, int ParagraphIndex); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICharpter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICharpter.java new file mode 100644 index 0000000..9754894 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICharpter.java @@ -0,0 +1,14 @@ +package com.bifan.txtreaderlib.interfaces; + +/** + * Created by HP on 2017/11/15. + */ + +public interface ICharpter { + int getParagraphNum(); + int getStartParagraphIndex(); + int getEndParagraphIndex(); + String getTitle(); + void setStartParagraphIndex(int index); + void setEndParagraphIndex(int index); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICursor.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICursor.java new file mode 100644 index 0000000..c30d2b4 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ICursor.java @@ -0,0 +1,21 @@ +package com.bifan.txtreaderlib.interfaces; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public interface ICursor { + int getCount(); + void moveToPosition(int var1); + void moveToFirst(); + void moveToLast(); + void moveToNext(); + void moveToPrevious(); + boolean isFirst(); + boolean isLast(); + boolean isBeforeFirst(); + boolean isAfterLast(); + T Pre(); + T Next(); + T Current(); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ILoadListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ILoadListener.java new file mode 100644 index 0000000..1ca6ff8 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ILoadListener.java @@ -0,0 +1,14 @@ +package com.bifan.txtreaderlib.interfaces; + + +import com.bifan.txtreaderlib.bean.TxtMsg; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public interface ILoadListener { + void onSuccess(); + void onFail(TxtMsg txtMsg); + void onMessage(String message); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPage.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPage.java new file mode 100644 index 0000000..33a1d76 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPage.java @@ -0,0 +1,28 @@ +package com.bifan.txtreaderlib.interfaces; + +import com.bifan.txtreaderlib.bean.TxtChar; + +import java.util.List; + + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public interface IPage { + ITxtLine getLine(int index); + void addLine(ITxtLine line); + void addLineTo(ITxtLine line, int index); + void setLines(List lines); + TxtChar getFirstChar(); + TxtChar getLastChar(); + ITxtLine getFirstLine(); + ITxtLine getLastLine(); + List getLines(); + ICursor getLineCursor(); + int getLineNum(); + boolean isFullPage();//是否满页了 + void setFullPage(boolean fullPage); + int CurrentIndex(); + Boolean HasData(); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageChangeListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageChangeListener.java new file mode 100644 index 0000000..fa445a2 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageChangeListener.java @@ -0,0 +1,10 @@ +package com.bifan.txtreaderlib.interfaces; + +/** + * Created by bifan-wei + * on 2017/12/8. + */ + +public interface IPageChangeListener { + void onCurrentPage(float progress); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageDataPipeline.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageDataPipeline.java new file mode 100644 index 0000000..f2aa5d4 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageDataPipeline.java @@ -0,0 +1,10 @@ +package com.bifan.txtreaderlib.interfaces; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public interface IPageDataPipeline { + IPage getPageStartFromProgress(int paragraphIndex, int charIndex); + IPage getPageEndToProgress(int paragraphIndex, int charIndex); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageEdgeListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageEdgeListener.java new file mode 100644 index 0000000..02dee5c --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IPageEdgeListener.java @@ -0,0 +1,10 @@ +package com.bifan.txtreaderlib.interfaces; + +/** + * created by : bifan-wei + */ + +public interface IPageEdgeListener { + void onCurrentFirstPage(); + void onCurrentLastPage(); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IParagraphData.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IParagraphData.java new file mode 100644 index 0000000..8764d45 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IParagraphData.java @@ -0,0 +1,31 @@ +package com.bifan.txtreaderlib.interfaces; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public interface IParagraphData { + void addParagraph(int ParaIndex, String p); + void addParagraph(String p); + int getParagraphNum(); + int getCharNum(); + int findParagraphIndexByCharIndex(int CharIndex); + int getParaStartCharIndex(int ParaIndex); + String getParagraphStr(int ParaIndex); + /** + * @param ParaIndex 段落位置 + * @param CharIndex 字符位置 + * @return 如果段落位置超出范围,报越界异常,如果字符位置超出段落范围,返回空字符 + */ + String getParagraphStrFromStart(int ParaIndex,int CharIndex); + + /** + * @param ParaIndex + * @param CharIndex + * @return 如果段落位置超出范围,报越界异常,如果字符位置超出段落范围,返回空字符 + */ + String getParagraphStrToEnd(int ParaIndex,int CharIndex); + + void Clear(); + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IReaderViewDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IReaderViewDrawer.java new file mode 100644 index 0000000..57fcd32 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/IReaderViewDrawer.java @@ -0,0 +1,61 @@ +package com.bifan.txtreaderlib.interfaces; + +import android.graphics.Canvas; +import android.view.MotionEvent; + +/** + * Created by bifan-wei + * on 2017/12/1. + */ + +public interface IReaderViewDrawer { + void drawPageNextPageShadow(Canvas canvas); + //绘制执行下一页的页边阴影 + + void drawPageNextBottomPage(Canvas canvas); + //绘制执行下一页时的下面页部分 + + + void drawPageNextTopPage(Canvas canvas); + //绘制执行下一页时的上面页部分 + + + void drawPagePrePageShadow(Canvas canvas); + //绘制执行上一页的页边阴影 + + + void drawPagePreBottomPage(Canvas canvas); + //绘制执行上一页时的下面页部分 + + + void drawPagePreTopPage(Canvas canvas); + //绘制执行上一页时的上面页部分 + + + void startPageStateBackAnimation(); + //状态恢复动画 + + void startPageNextAnimation(); + //执行下一页动画 + + void startPagePreAnimation(); + //执行上一页动画 + + void onTextSelectMoveForward(MotionEvent event); + // 文字向前选中 + + void onTextSelectMoveBack(MotionEvent event); + //文字向后选中 + + void onPageMove(MotionEvent event); + //页面移动 + + void drawNote(Canvas canvas); + //绘制笔记 + + void drawSelectedText(Canvas canvas); + //文字选中 + + void computeScroll(); + //完成移动 +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ISliderListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ISliderListener.java new file mode 100644 index 0000000..b73aad4 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ISliderListener.java @@ -0,0 +1,13 @@ +package com.bifan.txtreaderlib.interfaces; + +import com.bifan.txtreaderlib.bean.TxtChar; + +/** + * created by : bifan-wei + */ + +public interface ISliderListener { + void onShowSlider(TxtChar txtChar); + void onShowSlider(String CurrentSelectedText); + void onReleaseSlider(); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectDrawer.java new file mode 100644 index 0000000..3ed3767 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectDrawer.java @@ -0,0 +1,19 @@ +package com.bifan.txtreaderlib.interfaces; + +import android.graphics.Canvas; +import android.graphics.Paint; + +import com.bifan.txtreaderlib.bean.TxtChar; + +import java.util.List; + +/** + * Created by bifan-wei + * on 2017/12/4. + */ + +public interface ITextSelectDrawer { + void drawSelectedChar(TxtChar selectedChar, Canvas canvas, Paint paint); + + void drawSelectedLines(List selectedLines, Canvas canvas, Paint paint); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectListener.java new file mode 100644 index 0000000..951dad1 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITextSelectListener.java @@ -0,0 +1,14 @@ +package com.bifan.txtreaderlib.interfaces; + +import com.bifan.txtreaderlib.bean.TxtChar; + +/** + * Created by bifan-wei + * on 2017/12/5. + */ + +public interface ITextSelectListener { + void onTextChanging(TxtChar firstSelectedChar,TxtChar lastSelectedChar); + void onTextChanging(String selectText); + void onTextSelected(String selectText); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtLine.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtLine.java new file mode 100644 index 0000000..819516b --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtLine.java @@ -0,0 +1,27 @@ +package com.bifan.txtreaderlib.interfaces; + +import com.bifan.txtreaderlib.bean.TxtChar; + +import java.util.List; + + +/* +* create by bifan-wei +* 2017-11-14 +*/ +public interface ITxtLine { + List getTxtChars(); + int getCharNum(); + TxtChar getFirstChar(); + TxtChar getLastChar(); + TxtChar getChar(int index); + ICursor getCharCursor(); + String getLineStr(); + char[] getLineChar(); + Boolean HasData(); + void addChar(TxtChar txtChar); + void Clear(); + int CurrentIndex(); + boolean isParagraphEndLine();//判断是否是段落最后一句 + void setParagraphEndLine(boolean isParagraphEndLine); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtReaderLoggerListener.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtReaderLoggerListener.java new file mode 100644 index 0000000..0caa756 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtReaderLoggerListener.java @@ -0,0 +1,9 @@ +package com.bifan.txtreaderlib.interfaces; + +/** + * Created by HP on 2017/11/15. + */ + +public interface ITxtReaderLoggerListener { + void onLog(String tag, String msg); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtTask.java new file mode 100644 index 0000000..1ca7b97 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/interfaces/ITxtTask.java @@ -0,0 +1,11 @@ +package com.bifan.txtreaderlib.interfaces; + +import com.bifan.txtreaderlib.main.TxtReaderContext; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public interface ITxtTask { + void Run(ILoadListener callBack, TxtReaderContext readerContext); +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/BitmapData.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/BitmapData.java new file mode 100644 index 0000000..e387123 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/BitmapData.java @@ -0,0 +1,62 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Bitmap; + +/** + * Created by bifa-wei + * on 2017/11/27. + */ + +public class BitmapData { + private final Bitmap[] pages = new Bitmap[3]; + private Bitmap BgBitmap; + + public void setFirstPage(Bitmap page) { + pages[0] = page; + } + + public void setMidPage(Bitmap page) { + pages[1] = page; + } + + public void setLastPage(Bitmap page) { + pages[2] = page; + } + + public Bitmap FirstPage() { + return pages[0]; + } + + public Bitmap MidPage() { + return pages[1]; + } + + public Bitmap LastPage() { + return pages[2]; + } + + public void setBgBitmap(Bitmap bgBitmap) { + BgBitmap = bgBitmap; + } + + public Bitmap getBgBitmap() { + return BgBitmap; + } + + public Bitmap[] getPages() { + return pages; + } + + public void onDestroy() { + recycle(getBgBitmap()); + recycle(FirstPage()); + recycle(MidPage()); + recycle(LastPage()); + } + + private void recycle(Bitmap bitmap) { + if (bitmap != null) { + bitmap.recycle(); + } + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/FileReadRecordDB.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/FileReadRecordDB.java new file mode 100644 index 0000000..8d63ffc --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/FileReadRecordDB.java @@ -0,0 +1,222 @@ +package com.bifan.txtreaderlib.main; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import androidx.annotation.NonNull; +import android.text.TextUtils; + +import com.bifan.txtreaderlib.bean.FileReadRecordBean; + +import java.util.ArrayList; +import java.util.List; + +public class FileReadRecordDB extends SQLiteOpenHelper { + private static final int Version_1 = 1; + private static final String DB_NAME = "hwTxtReader"; + private String TABLE_NAME = "FileReadRecord"; + + private final String FileHashName = "fileHashName"; + private final String SearchId = "searchId"; + private final String FilePath = "filePath"; + private final String FileName = "fileName"; + private final String ParagraphIndex = "paragraphIndex"; + private final String ChartIndex = "chartIndex"; + + private String sql = "create table if not exists " + TABLE_NAME + " (" + SearchId + + " integer primary key autoincrement," + FileHashName + " varchar(50)," + + FilePath + " varchar(100), " + FileName + " varchar(100)," + + ParagraphIndex + " integer, " + ChartIndex + " integer)"; + + public FileReadRecordDB(Context context) { + this(context, DB_NAME, null, Version_1); + } + + private FileReadRecordDB(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { + super(context, name, factory, version); + } + + public void createTable() { + getWritableDatabase().execSQL(sql); + } + + @Override + public void onCreate(SQLiteDatabase db) { + + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } + + /** + * 关闭数据表 + */ + public void closeTable() { + close(); + } + + /** + * 删除数据表 + */ + public void delectTable() { + getWritableDatabase().execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); + } + + public void insertData(String fileHashName, String path, String fileName, int paragraphIndex, int chartIndex) { + if (!TextUtils.isEmpty(FileHashName)) { + if (IsHasData(FileHashName, fileHashName)) {//已经存在了,先删除 + deleteRecordsByFileHashName(fileHashName); + } + String sql = " insert into " + TABLE_NAME + " (" + FileHashName + "," + FilePath + "," + FileName + "," + ParagraphIndex + "," + ChartIndex + ") values ('" + fileHashName + + "','" + path + "','" + fileName + "','" + paragraphIndex + "','" + chartIndex + "')"; + getWritableDatabase().execSQL(sql); + } + } + + public void insertData(@NonNull FileReadRecordBean recordBean) { + insertData( + recordBean.fileHashName + , recordBean.filePath + , recordBean.fileName + , recordBean.paragraphIndex + , recordBean.chartIndex); + } + + /** + * @param fileHashName 文件hash值 + * @return 如果没有记录,返回null + */ + public FileReadRecordBean getRecordByHashName(String fileHashName) { + if (!TextUtils.isEmpty(fileHashName) && IsHasData(FileHashName, fileHashName)) { + Cursor cursor = getCursorByKeyValue(FileHashName, fileHashName); + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + FileReadRecordBean bean = getRecordBeanFromCursor(cursor); + cursor.close(); + return bean; + } + cursor.close(); + } + } + return null; + } + + /** + * @param userId + * @return 如果userId 为空,不分用户返回的是所有下载的数据,否则返回指定用户的下载记录,不会返回空 + */ + public List getRecord(String userId) { + List searchBeans = new ArrayList<>(); + Cursor cursor; + if (TextUtils.isEmpty(userId)) { + cursor = getWritableDatabase().rawQuery("select * from " + TABLE_NAME, null); + } else { + cursor = getCursorByKeyValue(FileHashName, userId); + } + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + searchBeans.add(getRecordBeanFromCursor(cursor)); + cursor.moveToNext(); + } + } + cursor.close(); + } + return searchBeans; + + } + + /** + * @return 获取所有记录 + */ + public List getAllFileRecord() { + return getRecord(null); + } + + + private FileReadRecordBean getRecordBeanFromCursor(Cursor cursor) { + FileReadRecordBean bean = new FileReadRecordBean(); + int userIdIndex = cursor.getColumnIndex(FileHashName); + bean.fileHashName = cursor.getString(userIdIndex); + + int searchIdIndex = cursor.getColumnIndex(SearchId); + bean.id = cursor.getInt(searchIdIndex); + + int CachePathIndex = cursor.getColumnIndex(FilePath); + bean.filePath = cursor.getString(CachePathIndex); + + int fileNameIndex = cursor.getColumnIndex(FileName); + bean.fileName = cursor.getString(fileNameIndex); + + int paragraphIndex = cursor.getColumnIndex(ParagraphIndex); + bean.paragraphIndex = cursor.getInt(paragraphIndex); + + int chartIndex = cursor.getColumnIndex(ChartIndex); + bean.chartIndex = cursor.getInt(chartIndex); + + return bean; + } + + + /** + * @param recordBean 要删除的记录 + */ + public void deleteRecord(FileReadRecordBean recordBean) { + if (IsHasData(SearchId, recordBean.id + "")) { + delete(SearchId, recordBean.id + ""); + } + } + + /** + * @param fileHashName fileHashName + */ + public void deleteRecordsByFileHashName(String fileHashName) { + if (IsHasData(FileHashName, fileHashName + "")) { + delete(FileHashName, fileHashName + ""); + } + + } + + /** + * @param key + * @param value + * @return 是否有数据 + */ + private Boolean IsHasData(String key, String value) { + Cursor cursor = getCursorByKeyValue(key, value); + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.close(); + return true; + } + cursor.close(); + } + return false; + } + + /** + * @param key + * @param value + */ + private void delete(String key, String value) { + String sql = "delete from " + TABLE_NAME + " where " + key + " = " + "'" + value + "'"; + getWritableDatabase().execSQL(sql); + } + + /** + * @param key + * @param value + * @return + */ + private Cursor getCursorByKeyValue(String key, String value) { + Cursor cursor = getWritableDatabase() + .rawQuery("select * from " + TABLE_NAME + " where " + key + " = " + "'" + value + "'", null); + return cursor; + } + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/LoadListenerAdapter.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/LoadListenerAdapter.java new file mode 100644 index 0000000..7f8e30a --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/LoadListenerAdapter.java @@ -0,0 +1,23 @@ +package com.bifan.txtreaderlib.main; + +import com.bifan.txtreaderlib.bean.TxtMsg; +import com.bifan.txtreaderlib.interfaces.ILoadListener; + +/** + * Created by bifan-wei + * on 2017/12/11. + */ + +public class LoadListenerAdapter implements ILoadListener{ + @Override + public void onSuccess() { + } + + @Override + public void onFail(TxtMsg txtMsg) { + } + + @Override + public void onMessage(String message) { + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalPageDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalPageDrawer.java new file mode 100644 index 0000000..c68c1c5 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalPageDrawer.java @@ -0,0 +1,258 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Canvas; +import android.graphics.Region; +import android.graphics.drawable.GradientDrawable; +import android.view.MotionEvent; +import android.widget.Scroller; + +import com.bifan.txtreaderlib.interfaces.IReaderViewDrawer; + + +/** + * Created by bifan-wei + * on 2017/12/1. + */ + +public class NormalPageDrawer extends PageDrawerBase implements IReaderViewDrawer { + private String tag = "NormalReaderViewDrawer"; + private final static int BorderShadowWith = 20; + + public NormalPageDrawer(TxtReaderView readerView, TxtReaderContext readerContext, Scroller scroller) { + super(readerView, readerContext, scroller); + } + + @Override + public void drawPageNextPageShadow(Canvas canvas) { + //绘制执行下一页的页边阴影 + mPath.reset(); + int left = (int) getMoveDistance() + getWidth(); + int top = 0; + int right = left + BorderShadowWith; + int bottom = getHeight(); + if (left > BorderShadowWith) { + getPageNextBorderDrawable().setBounds(left, top, right, bottom); + getPageNextBorderDrawable().draw(canvas); + } + } + + @Override + public void drawPageNextBottomPage(Canvas canvas) { + //绘制执行下一页时的下面页部分 + /* float x =getWidth()+getMoveDistance(); + mPath.reset(); + mPath.moveTo(x, 0); + mPath.lineTo(getWidth(), 0); + mPath.lineTo(getWidth(), getHeight()); + mPath.lineTo(x, getHeight()); + mPath.lineTo(x, 0); + canvas.clipPath(mPath, Region.Op.INTERSECT); + canvas.drawBitmap(getBottomPage(), x, x, null);*/ + + float x =getWidth()+getMoveDistance(); + mPath.reset(); + mPath.moveTo(x, 0); + mPath.lineTo(getWidth(), 0); + mPath.lineTo(getWidth(), getHeight()); + mPath.lineTo(x, getHeight()); + mPath.lineTo(x, 0); + canvas.clipPath(mPath, Region.Op.INTERSECT); + canvas.drawBitmap(getBottomPage(), 0, 0, null); + + } + + @Override + public void drawPageNextTopPage(Canvas canvas) { + //绘制执行下一页时的上面页部分 + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(getWidth() , 0); + mPath.lineTo(getWidth() , getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath,Region.Op.INTERSECT); + canvas.drawBitmap(getTopPage(), getMoveDistance()+1, 0, null); + } + + @Override + public void drawPagePrePageShadow(Canvas canvas) { + //绘制执行上一页的页边阴影 + mPath.reset(); + int left = (int) getMoveDistance() - BorderShadowWith; + int top = 0; + int right = (int) getMoveDistance(); + int bottom = getHeight(); + if (right < getWidth() - BorderShadowWith) { + getPagePreBorderDrawable().setBounds(left, top, right, bottom); + getPagePreBorderDrawable().draw(canvas); + } + + } + + @Override + public void drawPagePreBottomPage(Canvas canvas) { + //绘制执行上一页时的下面页部分 + float x = getMoveDistance(); + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(x, 0); + mPath.lineTo(x, getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath, Region.Op.INTERSECT); + canvas.drawBitmap(getBottomPage(), 0, 0, null); + } + + @Override + public void drawPagePreTopPage(Canvas canvas) { + //绘制执行上一页时的上面页部分 + float distance = getMoveDistance(); + float x = getWidth(); + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(x , 0); + mPath.lineTo(x , getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath, Region.Op.INTERSECT); + canvas.drawBitmap(getTopPage(), distance, 0, null); + } + + private Boolean onPageStateBackAnimation = false; + + @Override + public void startPageStateBackAnimation() { + if (readerView.isPagePre() || readerView.isPageNext()) { + onPageStateBackAnimation = true; + scroller.startScroll((int) readerView.mTouch.x, 0, -(int) getMoveDistance(), 0, PageSwitchTime); + postInvalidate(); + } + } + + + @Override + public void startPageNextAnimation() { + scroller.startScroll(getWidth() + (int) getMoveDistance(), 0, -(getWidth() + (int) getMoveDistance()), 0, PageSwitchTime); + readerView.mDown.x = getWidth();//从getWidth()开始 + postInvalidate(); + } + + @Override + public void startPagePreAnimation() { + scroller.startScroll((int) getMoveDistance(), 0, getWidth() - (int) getMoveDistance(), 0, PageSwitchTime); + readerView.mDown.x = 0; + postInvalidate(); + } + + @Override + public void onTextSelectMoveForward(MotionEvent event) { + + } + + @Override + public void onTextSelectMoveBack(MotionEvent event) { + + } + + + @Override + public void onPageMove(MotionEvent event) { + readerView.mTouch.x = event.getX(); + readerView.mTouch.y = event.getY(); + postInvalidate(); + } + + private void postInvalidate() { + readerView.postInvalidate(); + } + + @Override + public void drawNote(Canvas canvas) { + + } + + + @Override + public void drawSelectedText(Canvas canvas) { + if (readerView.CurrentMode == TxtReaderBaseView.Mode.PressSelectText) { + drawPressSelectedText(canvas); + } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveBack) { + drawSelectedLinesText(canvas); + } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveForward) { + drawSelectedLinesText(canvas); + } + + } + + private void drawSelectedLinesText(Canvas canvas) { + getTextSelectDrawer().drawSelectedLines(readerView.getCurrentSelectTextLine(), canvas, readerContext.getPaintContext().selectTextPaint); + //draw slider + drawSlider(canvas); + } + + private void drawPressSelectedText(Canvas canvas) { + getTextSelectDrawer().drawSelectedChar(readerView.FirstSelectedChar, canvas, readerContext.getPaintContext().selectTextPaint); + //draw slider + drawSlider(canvas); + } + + private void drawSlider(Canvas canvas) { + if (readerView.getLeftSliderPath() != null && readerView.getRightSliderPath() != null) { + canvas.drawPath(readerView.getLeftSliderPath(), readerContext.getPaintContext().sliderPaint); + canvas.drawPath(readerView.getRightSliderPath(), readerContext.getPaintContext().sliderPaint); + } + } + + @Override + public void computeScroll() { + if (scroller.computeScrollOffset()) { + readerView.mTouch.x = scroller.getCurrX(); + readerView.invalidate(); + checkPageData(); + } + } + + private synchronized void checkPageData() { + if (onPageStateBackAnimation) { + if ((getMoveDistance() > 0 && getMoveDistance() <= 3) || (getMoveDistance() < 0 && getMoveDistance() >= -3)) { + scroller.abortAnimation(); + readerView.releaseTouch(); + readerView.invalidate(); + onPageStateBackAnimation = false; + } + } else { + if (readerView.mTouch.x == 0) {//执行下一页数据获取 + readerView.doPageNextDone(); + scroller.abortAnimation(); + + } else if (readerView.mTouch.x == getWidth()) {//执行上一页数据获取 + readerView.doPagePreDone(); + scroller.abortAnimation(); + + } + } + + } + + + private GradientDrawable mPagePreBorderDrawable; + private GradientDrawable mPageNextBorderDrawable; + + private GradientDrawable getPagePreBorderDrawable() { + if (mPagePreBorderDrawable == null) { + int[] color = new int[]{0xaa666666, 0x00666666}; + mPagePreBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, color); + } + return mPagePreBorderDrawable; + } + + private GradientDrawable getPageNextBorderDrawable() { + if (mPageNextBorderDrawable == null) { + int[] color = new int[]{0xaa666666, 0x00666666}; + mPageNextBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, color); + } + return mPageNextBorderDrawable; + } + + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalTextSelectDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalTextSelectDrawer.java new file mode 100644 index 0000000..5a37fd2 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/NormalTextSelectDrawer.java @@ -0,0 +1,61 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; + +import com.bifan.txtreaderlib.bean.TxtChar; +import com.bifan.txtreaderlib.interfaces.ITextSelectDrawer; +import com.bifan.txtreaderlib.interfaces.ITxtLine; +import com.bifan.txtreaderlib.utils.ELogger; + +import java.util.List; + +/** + * Created by bifan-wei + * on 2017/12/4. + */ + +public class NormalTextSelectDrawer implements ITextSelectDrawer { + private final Path path = new Path(); + + @Override + public void drawSelectedChar(TxtChar selectedChar, Canvas canvas, Paint paint) { + if (selectedChar != null) { + ELogger.log("onPressSelectText","drawSelectedChar"); + path.reset(); + path.moveTo(selectedChar.Left, selectedChar.Top); + path.lineTo(selectedChar.Right, selectedChar.Top); + path.lineTo(selectedChar.Right, selectedChar.Bottom); + path.lineTo(selectedChar.Left, selectedChar.Bottom); + path.lineTo(selectedChar.Left, selectedChar.Top); + canvas.drawPath(path, paint); + } + } + + @Override + public void drawSelectedLines(List selectedLines, Canvas canvas, Paint paint) { + + for (ITxtLine line : selectedLines) { + ELogger.log("onPressSelectText",line.getLineStr()); + if (line.getTxtChars() != null && line.getTxtChars().size() > 0) { + + TxtChar fistChar = line.getTxtChars().get(0); + TxtChar lastChar = line.getTxtChars().get(line.getTxtChars().size() - 1); + + float fw = fistChar.CharWidth; + float lw = lastChar.CharWidth; + + RectF rect = new RectF(fistChar.Left, fistChar.Top, + lastChar.Right, lastChar.Bottom); + + canvas.drawRoundRect(rect, fw / 2, + paint.getTextSize() / 2, paint); + + } + } + } + + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageData.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageData.java new file mode 100644 index 0000000..8477e60 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageData.java @@ -0,0 +1,49 @@ +package com.bifan.txtreaderlib.main; + +import com.bifan.txtreaderlib.interfaces.IPage; + +/** + * Created by bifa-wei + * on 2017/11/27. + */ + +public class PageData { + private final IPage[] pages = new IPage[3]; + public final int[] refreshTag = new int[]{1, 1, 1};//1:需要刷新,0不需要刷新 + + public void setFirstPage(IPage page) { + pages[0] = page; + } + + public void setMidPage(IPage page) { + pages[1] = page; + } + + public void setLastPage(IPage page) { + pages[2] = page; + } + + public IPage FirstPage() { + return pages[0]; + } + + public IPage MidPage() { + return pages[1]; + } + + public IPage LastPage() { + return pages[2]; + } + + public IPage[] getPages() { + return pages; + } + + public void onDestroy() { + pages[0]=null; + pages[1]=null; + pages[2]=null; + } + + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDataPipeline.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDataPipeline.java new file mode 100644 index 0000000..bac3e26 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDataPipeline.java @@ -0,0 +1,330 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Paint; + +import com.bifan.txtreaderlib.bean.EnChar; +import com.bifan.txtreaderlib.bean.NumChar; +import com.bifan.txtreaderlib.bean.Page; +import com.bifan.txtreaderlib.bean.TxtChar; +import com.bifan.txtreaderlib.bean.TxtLine; +import com.bifan.txtreaderlib.interfaces.IPage; +import com.bifan.txtreaderlib.interfaces.IPageDataPipeline; +import com.bifan.txtreaderlib.interfaces.IParagraphData; +import com.bifan.txtreaderlib.interfaces.ITxtLine; +import com.bifan.txtreaderlib.utils.FormatUtil; +import com.bifan.txtreaderlib.utils.TextBreakUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +public class PageDataPipeline implements IPageDataPipeline { + private TxtReaderContext readerContext; + + public PageDataPipeline(TxtReaderContext readerContext) { + this.readerContext = readerContext; + } + + @Override + public IPage getPageStartFromProgress(int paragraphIndex, int charIndex) { + IParagraphData data = readerContext.getParagraphData(); + PageParam param = readerContext.getPageParam(); + + if(data==null) return null; + + int PageLineNum = param.PageLineNum; + int PageHeight = param.PageHeight; + int LineHeight = (int) param.LineHeight; + + + int CurrentPaIndex = paragraphIndex; + int startIndex = charIndex; + int ParagraphNum = data.getParagraphNum(); + float lineWidth = readerContext.getPageParam().LineWidth; + float textPadding = readerContext.getPageParam().TextPadding; + int paragraphMargin = param.ParagraphMargin; + + if (CurrentPaIndex >= ParagraphNum || CurrentPaIndex < 0) return null;//超过返回null + + IPage page = new Page(); + + //获取页面数据 + while (page.getLineNum() < PageLineNum && CurrentPaIndex < ParagraphNum) { + String paragraphStr = data.getParagraphStr(CurrentPaIndex); + if (paragraphStr != null && paragraphStr.length() > 0) { + List lines = getLineFromParagraphStartOnBeginning(paragraphStr, CurrentPaIndex, startIndex, lineWidth, textPadding, readerContext.getPaintContext().textPaint); + for (ITxtLine line : lines) { + page.addLine(line); + if (page.getLineNum() >= PageLineNum) { + page.setFullPage(true); + break; + } + } + + startIndex = 0; + } + CurrentPaIndex++; + } + + //尝试识别是否需要加段落间距数据 + PageHeight = PageHeight - param.PaddingTop - param.PaddingBottom; + int textHeight = readerContext.getTxtConfig().textSize; + if (paragraphMargin > 0 && LineHeight > 0 && PageHeight > LineHeight && page.getLineNum() > 0) { + int height = param.PaddingTop; + List lines = new ArrayList<>(); + for (int i = 0; i < page.getLineNum(); i++) { + ITxtLine line = page.getLine(i); + height = height + LineHeight; + if (height > PageHeight) { + page.setFullPage(true); + if (height - LineHeight + textHeight <= PageHeight) { + lines.add(line); + } + break; + } else { + lines.add(line); + } + + if (line.isParagraphEndLine()) { + // if (height + paragraphMargin < PageHeight) { + height = height + paragraphMargin;//说明还有行数据 + // } + } + + } + if (height >= PageHeight) { + page.setFullPage(true); + } + page.setLines(lines); + } + + return page; + } + + + @Override + public IPage getPageEndToProgress(int paragraphIndex, int charIndex) { + IParagraphData data = readerContext.getParagraphData(); + PageParam param = readerContext.getPageParam(); + + if(data==null) return null; + + int PageLineNum = param.PageLineNum; + int PageHeight = param.PageHeight; + int LineHeight = (int) param.LineHeight; + + + int CurrentPaIndex = paragraphIndex; + int startIndex = charIndex; + int ParagraphNum = data.getParagraphNum(); + float lineWidth = readerContext.getPageParam().LineWidth; + float textPadding = readerContext.getPageParam().TextPadding; + + + if (charIndex == 0) {//说明上页开始是段落开始位置,段落左移 + CurrentPaIndex--;// + startIndex = 0; + } + if (CurrentPaIndex >= ParagraphNum || CurrentPaIndex < 0) return null;//超过返回null + + IPage page = new Page(); + PageHeight = PageHeight - param.PaddingTop - param.PaddingBottom; + int paragraphMargin = param.ParagraphMargin; + + + while (page.getLineNum() < PageLineNum && CurrentPaIndex >= 0) { + String paragraphStr = data.getParagraphStr(CurrentPaIndex); + + if (paragraphStr != null && paragraphStr.length() > 0) { + if (startIndex == 0) {//说明上页开始是段落开始位置 + startIndex = paragraphStr.length(); + } + List lines = getLineFromParagraphOnEnd(paragraphStr, CurrentPaIndex, startIndex, lineWidth, textPadding, readerContext.getPaintContext().textPaint); + if (lines.size() > 0) { + for (int i = lines.size() - 1; i >= 0; i--) { + ITxtLine line = lines.get(i); + page.addLine(line); + if (page.getLineNum() >= PageLineNum) { + page.setFullPage(true); + break; + } + } + } + } + CurrentPaIndex--; + startIndex = 0; + } + + + if (page.HasData()) { + Collections.reverse(page.getLines()); + } + + //尝试识别是否需要加段落间距数据 + PageHeight = PageHeight - param.PaddingTop - param.PaddingBottom; + if (paragraphMargin > 0 && LineHeight > 0 && PageHeight > LineHeight && page.getLineNum() > 0) { + int height = param.PaddingTop; + int textHeight = readerContext.getTxtConfig().textSize; + + List lines = new ArrayList<>(); + + for (int i = page.getLineNum() - 1; i >= 0; i--) { + ITxtLine line = page.getLine(i); + + if (i == page.getLineNum() - 1) {//底部那个不添加偏移量 + lines.add(line); + height = height + textHeight; + } else { + if (height + LineHeight > PageHeight) { + page.setFullPage(true); + if (height + textHeight <= PageHeight) { + lines.add(line); + } + break; + } else { + lines.add(line); + height = height + LineHeight; + if (line.isParagraphEndLine()) { + // if (height + paragraphMargin < PageHeight) { + height = height + paragraphMargin;//说明还有行数据 + // } + } + } + } + + } + if (height >= PageHeight) { + page.setFullPage(true); + } + page.setLines(lines); + + if (page.HasData()) { + Collections.reverse(page.getLines()); + } + } + return page; + } + + /** + * @param paragraphData 完整段落数据 + * @param paragraphIndex 段落下标 + * @param startCharIndex 开始字符位置 + * @param lineWidth 行宽度 + * @param textPadding 字体间距 + * @param paint 画笔 + * @return startCharIndex超过范围,返回空集合 + */ + //开始满行 + private List getLineFromParagraphStartOnBeginning(String paragraphData, int paragraphIndex, int startCharIndex, + float lineWidth, float textPadding, Paint paint) { + List lines = new ArrayList<>(); + int startIndex = startCharIndex; + startIndex = startIndex < 0 ? 0 : startIndex; + int paragraphLength = paragraphData.length(); + + if (startIndex >= paragraphLength) { + return lines; + } + if ( paragraphData.length() > 0) { + String s = paragraphData.substring(startIndex);//截取要的数据 + while (s.length() > 0) {// is[0] 为个数 is[1] 为是否满一行 + float[] is = TextBreakUtil.BreakText(s, lineWidth, textPadding, paint); + ITxtLine line; + if (is[1] != 1) {//不满一行 + line = getLineFromString(s, paragraphIndex, startIndex, is); + if (s.length() + startIndex >= paragraphLength) { + line.setParagraphEndLine(true); + } + lines.add(line); + break; + } + int num = (int) is[0]; + String lineStr = s.substring(0, num); + line = getLineFromString(lineStr, paragraphIndex, startIndex, is); + startIndex = startIndex + num; + if (line != null) { + lines.add(line); + } + s = s.substring(num); + } + } + return lines; + } + + /** + * @param paragraphData + * @param paragraphIndex + * @param endCharIndex + * @param lineWidth + * @param textPadding + * @param paint + * @return startCharIndex等于0,返回空集合,超过范围,为段落长度数据 + */ + //结束满行 + private List getLineFromParagraphOnEnd(String paragraphData, int paragraphIndex, int endCharIndex, + float lineWidth, float textPadding, Paint paint) { + List lines = new ArrayList<>(); + int startIndex = 0; + int paragraphLength = paragraphData.length(); + if (paragraphLength == 0 || endCharIndex <= 0) { + return lines; + } + endCharIndex = endCharIndex >= paragraphData.length() ? paragraphData.length() : endCharIndex; + + if (endCharIndex > 0) { + String s = paragraphData.substring(startIndex, endCharIndex);//截取要的数据 + while (s.length() > 0) {// is[0] 为个数 is[1] 为是否满一行 + float[] is = TextBreakUtil.BreakText(s, lineWidth, textPadding, paint); + ITxtLine line; + int num = (int) is[0]; + if (is[1] != 1) {//不满一行 + line = getLineFromString(s, paragraphIndex, startIndex, is); + // ELogger.log("isParagraphEndLine11",endCharIndex+"/"+startIndex+"/"+paragraphLength+"/"+s.length()+"/"+s); + if (endCharIndex == paragraphLength && s.length() + startIndex >= paragraphLength) { + line.setParagraphEndLine(true); + } + lines.add(line); + return lines; + } + + String lineStr = s.substring(0, num); + line = getLineFromString(lineStr, paragraphIndex, startIndex, is); + startIndex = startIndex + num; + if (line != null) { + lines.add(line); + } + s = s.substring(lineStr.length()); + } + } + return lines; + } + + + private ITxtLine getLineFromString(String str, int ParagraphIndex, int charIndex, float[] is) { + ITxtLine line = null; + int index = charIndex; + int cIndex = 2;//找到字符宽度 + if (str != null && str.length() > 0) { + line = new TxtLine(); + + char[] cs = str.toCharArray(); + for (char c : cs) { + TxtChar Char; + if (FormatUtil.isDigital(c)) { + Char = new NumChar(c); + } else if (FormatUtil.isLetter(c)) { + Char = new EnChar(c); + } else { + Char = new TxtChar(c); + } + Char.ParagraphIndex = ParagraphIndex; + Char.CharIndex = index++; + Char.CharWidth = is[cIndex++]; + line.addChar(Char); + } + + } + return line; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDrawerBase.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDrawerBase.java new file mode 100644 index 0000000..ce7cd15 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageDrawerBase.java @@ -0,0 +1,60 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Bitmap; +import android.graphics.Path; +import android.widget.Scroller; + +import com.bifan.txtreaderlib.interfaces.ITextSelectDrawer; + +/** + * Created by bifan-wei + * on 2017/12/2. + */ + +public class PageDrawerBase { + protected int PageSwitchTime = 400; + protected TxtReaderView readerView; + protected TxtReaderContext readerContext; + protected Scroller scroller; + protected Path mPath = new Path(); + protected ITextSelectDrawer textSelectDrawer; + + + public PageDrawerBase(TxtReaderView readerView, TxtReaderContext readerContext, Scroller scroller) { + this.readerView = readerView; + this.readerContext = readerContext; + this.scroller = scroller; + PageSwitchTime = TxtConfig.getPageSwitchDuration(readerContext.context); + } + protected int getWidth() { + return readerView.getWidth(); + } + protected float getMoveDistance() { + return readerView.getMoveDistance(); + } + protected int getHeight() { + return readerView.getHeight(); + } + protected Bitmap getTopPage() { + return readerView.getTopPage(); + } + protected Bitmap getBottomPage() { + return readerView.getBottomPage(); + } + + + + public ITextSelectDrawer getTextSelectDrawer() { + if (textSelectDrawer == null) { + textSelectDrawer = new NormalTextSelectDrawer(); + } + return textSelectDrawer; + } + + public void setTextSelectDrawer(ITextSelectDrawer textSelectDrawer) { + this.textSelectDrawer = textSelectDrawer; + } + + + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageParam.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageParam.java new file mode 100644 index 0000000..1767289 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PageParam.java @@ -0,0 +1,22 @@ +package com.bifan.txtreaderlib.main; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class PageParam { + public int PaddingLeft = 20; + public int PaddingBottom = 0; + public int PaddingTop = 20; + public int PaddingRight = 20; + public int ParagraphMargin = 0; + public int VerticalLinePadding = 30;//横行距 + public int HorizonalLinePadding = 30;//竖行距 + public int LinePadding = 30 ; + public int PageLineNum = -1; + public float LineWidth = 0; + public float LineHeight = 0; + public float TextPadding = 5; + public int PageWidth = 0; + public int PageHeight = 0; +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PaintContext.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PaintContext.java new file mode 100644 index 0000000..3211bb1 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/PaintContext.java @@ -0,0 +1,28 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Paint; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class PaintContext { + public Paint textPaint; + public Paint selectTextPaint; + public Paint sliderPaint; + public Paint notePaint; + + public PaintContext() { + textPaint = new Paint(); + selectTextPaint = new Paint(); + notePaint = new Paint(); + sliderPaint = new Paint(); + } + + public void onDestroy(){ + textPaint = null; + selectTextPaint =null; + notePaint = null; + sliderPaint =null; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ParagraphData.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ParagraphData.java new file mode 100644 index 0000000..847aee6 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ParagraphData.java @@ -0,0 +1,98 @@ +package com.bifan.txtreaderlib.main; + +import com.bifan.txtreaderlib.interfaces.IParagraphData; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class ParagraphData implements IParagraphData { + private int CharNum = 0; + private HashMap paragraph = new HashMap<>(); + private List charIndex = new ArrayList<>();//每个段落开始字符位置 + + @Override + public void addParagraph(int ParaIndex, String p) { + if (p != null) { + paragraph.put(ParaIndex, p); + charIndex.add(CharNum); + CharNum += p.length(); + } + } + + @Override + public void addParagraph(String p) { + addParagraph(paragraph.size(), p); + } + + @Override + public int getParagraphNum() { + return paragraph.size(); + } + + @Override + public int getCharNum() { + return CharNum; + } + + @Override + public int findParagraphIndexByCharIndex(int CharIndex) { + int pre = 0; + int next = 0; + int index = 0; + + for (Integer v : charIndex) { + if (index == 0) { + pre = v; + } else { + if (CharIndex >= pre && CharIndex < v) { + return index; + } else { + pre = next; + next = v; + } + } + index++; + } + return index; + } + + + @Override + public int getParaStartCharIndex(int ParaIndex) { + return charIndex.get(ParaIndex); + } + + @Override + public String getParagraphStr(int ParaIndex) { + return paragraph.get(ParaIndex); + } + + @Override + public String getParagraphStrFromStart(int ParaIndex, int CharIndex) { + String S = getParagraphStr(ParaIndex) + ""; + if (CharIndex < 0) CharIndex = 0; + if (CharIndex > S.length()) CharIndex = S.length(); + return S.substring(CharIndex); + } + + @Override + public String getParagraphStrToEnd(int ParaIndex, int CharIndex) { + String S = getParagraphStr(ParaIndex) + ""; + if (CharIndex < 0) CharIndex = 0; + if (CharIndex > S.length()) CharIndex = S.length(); + return S.substring(0, CharIndex); + } + + @Override + public void Clear() { + charIndex.clear(); + paragraph.clear(); + CharNum = 0; + } + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/SerialPageDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/SerialPageDrawer.java new file mode 100644 index 0000000..f13a80f --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/SerialPageDrawer.java @@ -0,0 +1,252 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Canvas; +import android.graphics.Region; +import android.graphics.drawable.GradientDrawable; +import android.view.MotionEvent; +import android.widget.Scroller; + +import com.bifan.txtreaderlib.interfaces.IReaderViewDrawer; + +/** + * Created by bifan-wei + * on 2017/12/2. + */ + +public class SerialPageDrawer extends PageDrawerBase implements IReaderViewDrawer { + private String tag = "SerialPageDrawer"; + private final static int BorderShadowWith = 5; + + public SerialPageDrawer(TxtReaderView readerView, TxtReaderContext readerContext, Scroller scroller) { + super(readerView, readerContext, scroller); + } + + @Override + public void drawPageNextPageShadow(Canvas canvas) { + //绘制执行下一页的页边阴影 + mPath.reset(); + int left = (int) getMoveDistance() + getWidth(); + int top = 0; + int right = left + BorderShadowWith; + int bottom = getHeight(); + if (left > BorderShadowWith) { + getPageNextBorderDrawable().setBounds(left, top, right, bottom); + getPageNextBorderDrawable().draw(canvas); + } + } + + @Override + public void drawPageNextBottomPage(Canvas canvas) { + //绘制执行下一页时的下面页部分 + float startPosition = getWidth() + getMoveDistance(); + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(getWidth(), 0); + mPath.lineTo(getWidth(), getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath,Region.Op.INTERSECT); + canvas.drawBitmap(getBottomPage(), startPosition, 0, null); + + + } + + @Override + public void drawPageNextTopPage(Canvas canvas) { + //绘制执行下一页时的上面页部分 + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(getWidth() , 0); + mPath.lineTo(getWidth() , getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath,Region.Op.INTERSECT); + canvas.drawBitmap(getTopPage(), getMoveDistance()+1, 0, null); + } + + @Override + public void drawPagePrePageShadow(Canvas canvas) { + //绘制执行上一页的页边阴影 + mPath.reset(); + int left = (int) getMoveDistance() - BorderShadowWith; + int top = 0; + int right = (int) getMoveDistance(); + int bottom = getHeight(); + if (right < getWidth() - BorderShadowWith) { + getPagePreBorderDrawable().setBounds(left, top, right, bottom); + getPagePreBorderDrawable().draw(canvas); + } + + } + + @Override + public void drawPagePreBottomPage(Canvas canvas) { + //绘制执行上一页时的下面页部分 + float startPosition = getMoveDistance(); + float x = 0; + mPath.reset(); + mPath.moveTo(x, 0); + mPath.lineTo(getWidth(), 0); + mPath.lineTo(getWidth(), getHeight()); + mPath.lineTo(x, getHeight()); + mPath.lineTo(x, 0); + canvas.clipPath(mPath, Region.Op.INTERSECT); + canvas.drawBitmap(getBottomPage(), startPosition-getWidth(), 0, null); + + } + + @Override + public void drawPagePreTopPage(Canvas canvas) { + //绘制执行上一页时的上面页部分 + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(getWidth(), 0); + mPath.lineTo(getWidth(), getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath, Region.Op.INTERSECT); + canvas.drawBitmap(getTopPage(), getMoveDistance(), 0, null); + } + + private Boolean onPageStateBackAnimation = false; + + @Override + public void startPageStateBackAnimation() { + if (readerView.isPagePre() || readerView.isPageNext()) { + onPageStateBackAnimation = true; + scroller.startScroll((int) readerView.mTouch.x, 0, -(int) getMoveDistance(), 0, PageSwitchTime); + postInvalidate(); + } + + } + + + @Override + public void startPageNextAnimation() { + scroller.startScroll(getWidth() + (int) getMoveDistance(), 0, -(getWidth() + (int) getMoveDistance()), 0, PageSwitchTime); + readerView.mDown.x = getWidth();//从getWidth()开始 + readerView.CurrentMode = TxtReaderBaseView.Mode.PageNextIng; + postInvalidate(); + } + + @Override + public void startPagePreAnimation() { + scroller.startScroll((int) getMoveDistance(), 0, getWidth() - (int) getMoveDistance(), 0, PageSwitchTime); + readerView.mDown.x = 0; + readerView.CurrentMode = TxtReaderBaseView.Mode.PagePreIng; + postInvalidate(); + } + + @Override + public void onTextSelectMoveForward(MotionEvent event) { + + } + + @Override + public void onTextSelectMoveBack(MotionEvent event) { + + } + + @Override + public void onPageMove(MotionEvent event) { + readerView.mTouch.x = event.getX(); + readerView.mTouch.y = event.getY(); + postInvalidate(); + } + + private void postInvalidate() { + readerView.postInvalidate(); + } + + @Override + public void drawNote(Canvas canvas) { + + } + + @Override + public void drawSelectedText(Canvas canvas) { + if (readerView.CurrentMode == TxtReaderBaseView.Mode.PressSelectText) { + drawPressSelectedText(canvas); + } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveBack) { + drawSelectedLinesText(canvas); + } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveForward) { + drawSelectedLinesText(canvas); + } + + } + + private void drawSelectedLinesText(Canvas canvas) { + getTextSelectDrawer().drawSelectedLines(readerView.getCurrentSelectTextLine(), canvas, readerContext.getPaintContext().selectTextPaint); + //draw slider + drawSlider(canvas); + + } + + private void drawPressSelectedText(Canvas canvas) { + getTextSelectDrawer().drawSelectedChar(readerView.FirstSelectedChar, canvas, readerContext.getPaintContext().selectTextPaint); + //draw slider + drawSlider(canvas); + } + + private void drawSlider(Canvas canvas) { + if (readerView.getLeftSliderPath() != null && readerView.getRightSliderPath() != null) { + canvas.drawPath(readerView.getLeftSliderPath(), readerContext.getPaintContext().sliderPaint); + canvas.drawPath(readerView.getRightSliderPath(), readerContext.getPaintContext().sliderPaint); + } + } + + @Override + public void computeScroll() { + if (scroller.computeScrollOffset()) { + readerView.mTouch.x = scroller.getCurrX(); + readerView.invalidate(); + checkPageData(); + } + } + + private synchronized void checkPageData() { + if (onPageStateBackAnimation) { + if ((getMoveDistance()>0&&getMoveDistance() <= 3) || (getMoveDistance()<0&&getMoveDistance() >= -3) ) { + scroller.abortAnimation(); + readerView.releaseTouch(); + readerView.invalidate(); + onPageStateBackAnimation = false; + } + } else { + if (readerView.mTouch.x == 0)//执行下一页数据获取 + { + readerView.doPageNextDone(); + scroller.abortAnimation(); + + } else if (readerView.mTouch.x == getWidth()) {//执行上一页数据获取 + readerView.doPagePreDone(); + scroller.abortAnimation(); + + } + } + + } + + + private GradientDrawable mPagePreBorderDrawable; + private GradientDrawable mPageNextBorderDrawable; + + private GradientDrawable getPagePreBorderDrawable() { + if (mPagePreBorderDrawable == null) { + int[] color = new int[]{0x55666666, 0x55666666, 0x55666666}; + mPagePreBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, color); + } + return mPagePreBorderDrawable; + } + + private GradientDrawable getPageNextBorderDrawable() { + if (mPageNextBorderDrawable == null) { + int[] color = new int[]{0x55666666, 0x55666666, 0x55666666}; + mPageNextBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, color); + } + return mPageNextBorderDrawable; + } + + +} + diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ShearPageDrawer.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ShearPageDrawer.java new file mode 100644 index 0000000..cbcc08e --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/ShearPageDrawer.java @@ -0,0 +1,253 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Canvas; +import android.graphics.Region; +import android.graphics.drawable.GradientDrawable; +import android.view.MotionEvent; +import android.widget.Scroller; + +import com.bifan.txtreaderlib.interfaces.IReaderViewDrawer; + +/** + * Created by bifan-wei + * on 2017/12/2. + */ + +public class ShearPageDrawer extends PageDrawerBase implements IReaderViewDrawer { + private String tag = "SerialPageDrawer"; + private final static int BorderShadowWith = 5; + + public ShearPageDrawer(TxtReaderView readerView, TxtReaderContext readerContext, Scroller scroller) { + super(readerView, readerContext, scroller); + } + + @Override + public void drawPageNextPageShadow(Canvas canvas) { + //绘制执行下一页的页边阴影 + mPath.reset(); + int left = (int) getMoveDistance() + getWidth(); + int top = 0; + int right = left + BorderShadowWith; + int bottom = getHeight(); + if (left > BorderShadowWith) { + getPageNextBorderDrawable().setBounds(left, top, right, bottom); + getPageNextBorderDrawable().draw(canvas); + } + } + + @Override + public void drawPageNextBottomPage(Canvas canvas) { + //绘制执行下一页时的下面页部分 + float startPosition = getWidth() + getMoveDistance(); + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(getWidth(), 0); + mPath.lineTo(getWidth(), getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath,Region.Op.INTERSECT); + canvas.drawBitmap(getBottomPage(), startPosition, startPosition, null); + + + } + + @Override + public void drawPageNextTopPage(Canvas canvas) { + //绘制执行下一页时的上面页部分 + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(getWidth() , 0); + mPath.lineTo(getWidth() , getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath,Region.Op.INTERSECT); + canvas.drawBitmap(getTopPage(), getMoveDistance()+1, 0, null); + } + + @Override + public void drawPagePrePageShadow(Canvas canvas) { + //绘制执行上一页的页边阴影 + mPath.reset(); + int left = (int) getMoveDistance() - BorderShadowWith; + int top = 0; + int right = (int) getMoveDistance(); + int bottom = getHeight(); + if (right < getWidth() - BorderShadowWith) { + getPagePreBorderDrawable().setBounds(left, top, right, bottom); + getPagePreBorderDrawable().draw(canvas); + } + + } + + @Override + public void drawPagePreBottomPage(Canvas canvas) { + //绘制执行上一页时的下面页部分 + float startPosition = getMoveDistance(); + float x = 0; + mPath.reset(); + mPath.moveTo(x, 0); + mPath.lineTo(getWidth(), 0); + mPath.lineTo(getWidth(), getHeight()); + mPath.lineTo(x, getHeight()); + mPath.lineTo(x, 0); + canvas.clipPath(mPath, Region.Op.INTERSECT); + float y = startPosition-getWidth(); + canvas.drawBitmap(getBottomPage(), y, y, null); + + } + + @Override + public void drawPagePreTopPage(Canvas canvas) { + //绘制执行上一页时的上面页部分 + mPath.reset(); + mPath.moveTo(0, 0); + mPath.lineTo(getWidth(), 0); + mPath.lineTo(getWidth(), getHeight()); + mPath.lineTo(0, getHeight()); + mPath.lineTo(0, 0); + canvas.clipPath(mPath, Region.Op.INTERSECT); + canvas.drawBitmap(getTopPage(), getMoveDistance(), 0, null); + } + + private Boolean onPageStateBackAnimation = false; + + @Override + public void startPageStateBackAnimation() { + if (readerView.isPagePre() || readerView.isPageNext()) { + onPageStateBackAnimation = true; + scroller.startScroll((int) readerView.mTouch.x, 0, -(int) getMoveDistance(), 0, PageSwitchTime); + postInvalidate(); + } + + } + + + @Override + public void startPageNextAnimation() { + scroller.startScroll(getWidth() + (int) getMoveDistance(), 0, -(getWidth() + (int) getMoveDistance()), 0, PageSwitchTime); + readerView.mDown.x = getWidth();//从getWidth()开始 + readerView.CurrentMode = TxtReaderBaseView.Mode.PageNextIng; + postInvalidate(); + } + + @Override + public void startPagePreAnimation() { + scroller.startScroll((int) getMoveDistance(), 0, getWidth() - (int) getMoveDistance(), 0, PageSwitchTime); + readerView.mDown.x = 0; + readerView.CurrentMode = TxtReaderBaseView.Mode.PagePreIng; + postInvalidate(); + } + + @Override + public void onTextSelectMoveForward(MotionEvent event) { + + } + + @Override + public void onTextSelectMoveBack(MotionEvent event) { + + } + + @Override + public void onPageMove(MotionEvent event) { + readerView.mTouch.x = event.getX(); + readerView.mTouch.y = event.getY(); + postInvalidate(); + } + + private void postInvalidate() { + readerView.postInvalidate(); + } + + @Override + public void drawNote(Canvas canvas) { + + } + + @Override + public void drawSelectedText(Canvas canvas) { + if (readerView.CurrentMode == TxtReaderBaseView.Mode.PressSelectText) { + drawPressSelectedText(canvas); + } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveBack) { + drawSelectedLinesText(canvas); + } else if (readerView.CurrentMode == TxtReaderBaseView.Mode.SelectMoveForward) { + drawSelectedLinesText(canvas); + } + + } + + private void drawSelectedLinesText(Canvas canvas) { + getTextSelectDrawer().drawSelectedLines(readerView.getCurrentSelectTextLine(), canvas, readerContext.getPaintContext().selectTextPaint); + //draw slider + drawSlider(canvas); + + } + + private void drawPressSelectedText(Canvas canvas) { + getTextSelectDrawer().drawSelectedChar(readerView.FirstSelectedChar, canvas, readerContext.getPaintContext().selectTextPaint); + //draw slider + drawSlider(canvas); + } + + private void drawSlider(Canvas canvas) { + if (readerView.getLeftSliderPath() != null && readerView.getRightSliderPath() != null) { + canvas.drawPath(readerView.getLeftSliderPath(), readerContext.getPaintContext().sliderPaint); + canvas.drawPath(readerView.getRightSliderPath(), readerContext.getPaintContext().sliderPaint); + } + } + + @Override + public void computeScroll() { + if (scroller.computeScrollOffset()) { + readerView.mTouch.x = scroller.getCurrX(); + readerView.invalidate(); + checkPageData(); + } + } + + private synchronized void checkPageData() { + if (onPageStateBackAnimation) { + if ((getMoveDistance()>0&&getMoveDistance() <= 3) || (getMoveDistance()<0&&getMoveDistance() >= -3) ) { + scroller.abortAnimation(); + readerView.releaseTouch(); + readerView.invalidate(); + onPageStateBackAnimation = false; + } + } else { + if (readerView.mTouch.x == 0)//执行下一页数据获取 + { + readerView.doPageNextDone(); + scroller.abortAnimation(); + + } else if (readerView.mTouch.x == getWidth()) {//执行上一页数据获取 + readerView.doPagePreDone(); + scroller.abortAnimation(); + + } + } + + } + + + private GradientDrawable mPagePreBorderDrawable; + private GradientDrawable mPageNextBorderDrawable; + + private GradientDrawable getPagePreBorderDrawable() { + if (mPagePreBorderDrawable == null) { + int[] color = new int[]{0x55666666, 0x55666666, 0x55666666}; + mPagePreBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, color); + } + return mPagePreBorderDrawable; + } + + private GradientDrawable getPageNextBorderDrawable() { + if (mPageNextBorderDrawable == null) { + int[] color = new int[]{0x55666666, 0x55666666, 0x55666666}; + mPageNextBorderDrawable = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, color); + } + return mPageNextBorderDrawable; + } + + +} + diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtConfig.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtConfig.java new file mode 100644 index 0000000..42bfa32 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtConfig.java @@ -0,0 +1,266 @@ +package com.bifan.txtreaderlib.main; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Color; + +/** + * Created by HP on 2017/11/26. + */ + +public class TxtConfig { + + public static final String SAVE_NAME = "TxtConfig"; + public static final String C_TEXT_SIZE = "TEXT_SIZE "; + public static final String C_TEXT_COLOR = "TEXT_COLOR"; + public static final String C_NOTE_TEXT_COLOR = "TEXT_COLOR"; + public static final String C_SLIDER_COLOR = "SLIDER_COLOR"; + public static final String C_SELECT_TEXT_COLOR = "SELECTED_TEXT_COLOR"; + public static final String C_BACKGROUND_COLOR = "BACKGROUND_COLOR"; + public static final String C_IS_SHOW_NOTE = "IS_SHOW_NOTE"; + public static final String C_CAN_PRESS_SELECT = "CAN_PRESS_SELECT"; + public static final String C_BOLD = "BOLD "; + public static final String C_SHOW_SPECIAL_CHAR = "SHOW_SPECIAL_CHAR "; + public static final String C_CENTER_CLICK_AREA = "CENTER_CLICK_AREA"; + public static final String C_PAGE_SWITCH_DURATION = "PAGE_SWITCH_DURATION"; + public static final String C_PAGE_VERTICAL_MODE = "PAGE_VERTICAL_MODE "; + public static final String C_PAGE_SWITCH_TYPE_MODE = "PAGE_SWITCH_SYPE_MODE "; + + public static final int PAGE_SWITCH_MODE_COVER = 1;//in px + public static final int PAGE_SWITCH_MODE_SERIAL = 2;//in px + public static final int PAGE_SWITCH_MODE_SHEAR = 3;//in px + + public static int Page_PaddingLeft = 20;//in px + public static int Page_PaddingBottom = 20;//in px + public static int Page_PaddingTop = 20;//in px + public static int Page_PaddingRight = 20;//in px + public static int Page_LinePadding = 30;//in px + public static int Page_Paragraph_margin = 20;//in px,为0,没有间距 + + public int Page_Switch_Mode = PAGE_SWITCH_MODE_COVER; + public static int MAX_TEXT_SIZE = 150;//in px + public static int MIN_TEXT_SIZE = 50;//in px + public static int DEFAULT_SELECT_TEXT_COLOR = Color.parseColor("#44f6950b"); + public static int DEFAULT_SLIDER_COLOR = Color.parseColor("#1f4cf5"); + + public int textSize = MIN_TEXT_SIZE;//字体大小 + public int textColor = Color.BLACK;//字体颜色 + public int backgroundColor = Color.WHITE;//背景颜色 + public int NoteColor = Color.RED;//笔记颜色 + public int SelectTextColor = DEFAULT_SELECT_TEXT_COLOR;//选中颜色 + public int SliderColor = DEFAULT_SLIDER_COLOR;//滑动条颜色 + + public static boolean DebugMode = true;//debug模式会进行打印日志,默认是开启 + + public Boolean showNote = true;//是否显示笔记 + public Boolean canPressSelect = true;//是否能长按选中 + + public Boolean VerticalPageMode = false; + public Boolean Bold = false;//是否加粗 + public Boolean ShowSpecialChar = true;//是否显示特殊符号,对于数字、英文,可以显示特定颜色 + public float CenterClickArea = 0.35f;//0~1,中间点击区域占View宽度的百分比,区域为高为宽两倍的矩形,如果为1f,说明点击翻页将不起效果 + public int PageSwitchDuration = 400;//页面滑动时间间隔,毫秒,建议不要低于200 + + public static SharedPreferences getS(Context context) { + SharedPreferences share = context.getSharedPreferences(SAVE_NAME, Context.MODE_PRIVATE); + return share; + } + + public static int getPageSwitchMode(Context context) { + SharedPreferences share = getS(context); + int PageSwitchMode = share.getInt(C_PAGE_SWITCH_TYPE_MODE, PAGE_SWITCH_MODE_COVER); + if(PageSwitchMode!=PAGE_SWITCH_MODE_COVER + &&PageSwitchMode!= PAGE_SWITCH_MODE_SERIAL + &&PageSwitchMode!= PAGE_SWITCH_MODE_SHEAR){ + return PAGE_SWITCH_MODE_COVER; + } + return PageSwitchMode; + } + + /** + * @param PageSwitchMode PAGE_SWITCH_MODE_COVER、PAGE_SWITCH_MODE_SERIAL、PAGE_SWITCH__MODE_SHEAR + */ + public static void savePageSwitchMode(Context context,int PageSwitchMode) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putInt(C_PAGE_SWITCH_TYPE_MODE, PageSwitchMode); + editor.apply(); + } + + + /** + * @param context + * @param duration 不能低于100,建议200以上 + */ + public static void savePageSwitchDuration(Context context, int duration) { + duration = Math.max(duration, 100); + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putInt(C_PAGE_SWITCH_DURATION, duration); + editor.apply(); + } + + public static int getPageSwitchDuration(Context context) { + SharedPreferences share = getS(context); + return share.getInt(C_PAGE_SWITCH_DURATION, 400); + } + + public static void saveTextSize(Context context, int textSize) { + textSize = Math.max(textSize, MIN_TEXT_SIZE); + textSize = Math.min(textSize, MAX_TEXT_SIZE); + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putInt(C_TEXT_SIZE, textSize); + editor.apply(); + } + + public static int getTextSize(Context context) { + SharedPreferences share = getS(context); + return share.getInt(C_TEXT_SIZE, MIN_TEXT_SIZE); + } + + public static void saveTextColor(Context context, int textColor) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putInt(C_TEXT_COLOR, textColor); + editor.apply(); + editor.commit(); + } + + public static int getTextColor(Context context) { + SharedPreferences share = getS(context); + return share.getInt(C_TEXT_COLOR, Color.BLACK); + } + + public static void saveNoteTextColor(Context context, int textColor) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putInt(C_NOTE_TEXT_COLOR, textColor); + editor.apply(); + editor.commit(); + } + + public static int getNoteTextColor(Context context) { + SharedPreferences share = getS(context); + return share.getInt(C_NOTE_TEXT_COLOR, Color.BLACK); + } + + public static void saveSelectTextColor(Context context, int textColor) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putInt(C_SELECT_TEXT_COLOR, textColor); + editor.apply(); + editor.commit(); + } + + public static int getSelectTextColor(Context context) { + SharedPreferences share = getS(context); + return share.getInt(C_SELECT_TEXT_COLOR, DEFAULT_SELECT_TEXT_COLOR); + } + + + public static void saveBackgroundColor(Context context, int BackgroundColor) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putInt(C_BACKGROUND_COLOR, BackgroundColor); + editor.apply(); + editor.commit(); + } + + public static int getBackgroundColor(Context context) { + SharedPreferences share = getS(context); + return share.getInt(C_BACKGROUND_COLOR, Color.WHITE); + } + + public static void saveCenterClickArea(Context context, float CenterClickArea) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putFloat(C_CENTER_CLICK_AREA, CenterClickArea); + editor.apply(); + editor.commit(); + } + + public static float getCenterClickArea(Context context) { + SharedPreferences share = getS(context); + return share.getFloat(C_CENTER_CLICK_AREA, 0.3f); + } + + public static void saveSliderColor(Context context, float CenterClickArea) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putFloat(C_SLIDER_COLOR, CenterClickArea); + editor.apply(); + editor.commit(); + } + + public static int getSliderColor(Context context) { + SharedPreferences share = getS(context); + return share.getInt(C_SLIDER_COLOR, DEFAULT_SLIDER_COLOR); + } + + public static void saveIsShowNote(Context context, Boolean IsShowNote) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putBoolean(C_IS_SHOW_NOTE, IsShowNote); + editor.apply(); + editor.commit(); + } + + public static Boolean getIsShowNote(Context context) { + SharedPreferences share = getS(context); + return share.getBoolean(C_IS_SHOW_NOTE, true); + } + + public static void saveCanPressSelect(Context context, Boolean CanPressSelect) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putBoolean(C_CAN_PRESS_SELECT, CanPressSelect); + editor.apply(); + editor.commit(); + } + + public static Boolean getCanPressSelect(Context context) { + SharedPreferences share = getS(context); + return share.getBoolean(C_CAN_PRESS_SELECT, true); + } + + + public static void saveIsBold(Context context, Boolean bold) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putBoolean(C_BOLD, bold); + editor.apply(); + editor.commit(); + } + + public static Boolean isBold(Context context) { + SharedPreferences share = getS(context); + return share.getBoolean(C_BOLD, false); + } + + public static void saveIsShowSpecialChar(Context context, Boolean showSpecialChar) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putBoolean(C_SHOW_SPECIAL_CHAR, showSpecialChar); + editor.apply(); + editor.commit(); + } + + public static Boolean IsShowSpecialChar(Context context) { + SharedPreferences share = getS(context); + return share.getBoolean(C_SHOW_SPECIAL_CHAR, true); + } + + public static void saveIsOnVerticalPageMode(Context context, Boolean IsOnVerticalPageMode) { + SharedPreferences share = getS(context); + SharedPreferences.Editor editor = share.edit(); + editor.putBoolean(C_PAGE_VERTICAL_MODE, IsOnVerticalPageMode); + editor.apply(); + editor.commit(); + } + + public static Boolean IsOnVerticalPageMode(Context context) { + SharedPreferences share = getS(context); + return share.getBoolean(C_PAGE_VERTICAL_MODE, false); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderBaseView.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderBaseView.java new file mode 100644 index 0000000..2bc1351 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderBaseView.java @@ -0,0 +1,1403 @@ +package com.bifan.txtreaderlib.main; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.RectF; +import android.graphics.Region; + +import androidx.annotation.Nullable; + +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Scroller; + +import com.bifan.txtreaderlib.bean.DefaultLeftSlider; +import com.bifan.txtreaderlib.bean.DefaultRightSlider; +import com.bifan.txtreaderlib.bean.Slider; +import com.bifan.txtreaderlib.bean.TxtChar; +import com.bifan.txtreaderlib.bean.TxtLine; +import com.bifan.txtreaderlib.bean.TxtMsg; +import com.bifan.txtreaderlib.interfaces.ICenterAreaClickListener; +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.IPage; +import com.bifan.txtreaderlib.interfaces.IPageChangeListener; +import com.bifan.txtreaderlib.interfaces.IPageEdgeListener; +import com.bifan.txtreaderlib.interfaces.ISliderListener; +import com.bifan.txtreaderlib.interfaces.ITxtLine; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.tasks.BitmapProduceTask; +import com.bifan.txtreaderlib.tasks.DestroyableTask; +import com.bifan.txtreaderlib.tasks.TextLoader; +import com.bifan.txtreaderlib.tasks.TxtFileLoader; +import com.bifan.txtreaderlib.utils.DisPlayUtil; +import com.bifan.txtreaderlib.utils.ELogger; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by bifa-wei + * on 2017/11/28. + */ + +public abstract class TxtReaderBaseView extends View implements GestureDetector.OnGestureListener { + private String tag = "TxtReaderBaseView"; + + protected static int SliderWidth = 40;//滑动条宽度 + protected static int PageChangeMinMoveDistance = 40;//页面切换需要的最小滑动距离 + protected TxtReaderContext readerContext;//阅读器上下文 + protected Scroller mScroller;//滑动器 + protected GestureDetector mGestureDetector;//手势检测器 + protected PointF mTouch = new PointF();//滑动坐标 + protected PointF mDown = new PointF();//点下的坐标 + protected TxtChar FirstSelectedChar = null;//第一个选中的字符 + protected TxtChar LastSelectedChar = null;//最后一个选中的字符 + protected Slider mLeftSlider = null;//左侧滑动条 + protected Slider mRightSlider = null;//右侧滑动条 + protected Bitmap TopPage = null;//顶部页 + protected Bitmap BottomPage = null;//底部页 + protected Mode CurrentMode = Mode.Normal;//当前页面模式 + protected boolean hasDown = false; + private IPageEdgeListener pageEdgeListener; + private IPageChangeListener pageChangeListener; + private ISliderListener sliderListener; + private ICenterAreaClickListener centerAreaClickListener; + private DestroyableTask mFileLoadTask, mStrLoadTask; + private TxtFileLoader mTxtFileLoader; + + public TxtReaderBaseView(Context context) { + super(context); + init(); + } + + public TxtReaderBaseView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + protected void init() { + if (mLeftSlider == null) { + mLeftSlider = new DefaultLeftSlider(); + } + if (mRightSlider == null) { + mRightSlider = new DefaultRightSlider(); + } + SliderWidth = DisPlayUtil.dip2px(getContext(), 13); + mLeftSlider.SliderWidth = SliderWidth; + mRightSlider.SliderWidth = SliderWidth; + setLayerType(View.LAYER_TYPE_HARDWARE, null); + readerContext = new TxtReaderContext(getContext()); + mScroller = new TxtReaderScroller(getContext()); + mGestureDetector = new GestureDetector(getContext(), this); + PageChangeMinMoveDistance = DisPlayUtil.dip2px(getContext(), 30); + readerContext.setPageParam(new PageParam()); + setClickable(true); + } + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + //把默认背景绘制出来,不然默认是白色的,导致显示有点奇怪 + if (readerContext != null) { + canvas.drawColor(readerContext.getTxtConfig().backgroundColor); + } + //初始化完成才允许绘制 + if (readerContext.InitDone()) { + //绘制页面行数据 + drawLineText(canvas); + //是否显示笔记 该功能未完成 + if (readerContext.getTxtConfig().showNote) { + //绘制笔记 + drawNote(canvas); + } + //不是正常模式,可能需要绘制长按选择的文字 + if (readerContext.getTxtConfig().canPressSelect && CurrentMode != Mode.Normal) { + drawSelectedText(canvas); + } + } + } + + + /** + * @param canvas 绘制页面数据 + */ + protected abstract void drawLineText(Canvas canvas); + + /** + * @param canvas 绘制笔记 + */ + protected abstract void drawNote(Canvas canvas); + + /** + * @param canvas 绘制滑动选中的文字 + */ + protected abstract void drawSelectedText(Canvas canvas); + + + @Override + public boolean onTouchEvent(MotionEvent event) { + //检查是否在滑动中 + if (mScroller.computeScrollOffset() + || CurrentMode == Mode.PageNextIng + || CurrentMode == Mode.PagePreIng) { + if (hasDown) { + hasDown = false; + } + return true; + } + + //手势处理完成,捕捉了的话,拦截它 + Boolean Deal = mGestureDetector.onTouchEvent(event); + //判断是否已经被处理了 + if (Deal) { + return true; + } + + if (!hasDown) { + //如果在滑动中,拦截了ACTION_DOWN事件,在滑动结束 + //事件不会被Deal,出现没有初始化的情况,可能出现翻下一页却是在翻上一页的情况 + return true; + } + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + onActionUp(event); + break; + case MotionEvent.ACTION_MOVE: + onActionMove(event); + break; + } + return true; + } + + /** + * @param event onActionUp + */ + protected void onActionUp(MotionEvent event) { + //默认正常模式下才能响应up事件 + if (CurrentMode == Mode.Normal) { + startPageUpAnimation(event); + } + } + + + /** + * @param event onActionMove + */ + protected void onActionMove(MotionEvent event) { + if (CurrentMode == Mode.Normal) { + //正常模式,执行页面滑动 + onPageMove(event); + } else { + //当前需要执行向后滑动选择文字 + if (CurrentMode == Mode.SelectMoveBack) { + float dx = event.getX() - mDown.x; + float dy = event.getY() - mDown.y; + float x = mRightSlider.getX(dx); + float y = mRightSlider.getY(dy); + + if (CanMoveBack(x, y)) { + TxtChar moveToChar = findCharByPositionWhileMove(x, y); + if (FirstSelectedChar != null && moveToChar != null) { + if (moveToChar.Top > FirstSelectedChar.Top + || (moveToChar.Top == FirstSelectedChar.Top + && moveToChar.Left >= FirstSelectedChar.Left)) { + LastSelectedChar = moveToChar; + checkSelectedText(); + onTextSelectMoveBack(event); + invalidate(); + } + } + } + + } else if (CurrentMode == Mode.SelectMoveForward) { //当前需要执行向前滑动选择文字 + float dx = event.getX() - mDown.x; + float dy = event.getY() - mDown.y; + + float x = mLeftSlider.getX(dx); + float y = mLeftSlider.getY(dy); + + if (CanMoveForward(x, y)) { + TxtChar moveToChar = findCharByPositionWhileMove(x, y); + if (LastSelectedChar != null && moveToChar != null) { + if (moveToChar.Bottom < LastSelectedChar.Bottom + || (moveToChar.Bottom == LastSelectedChar.Bottom + && moveToChar.Right <= LastSelectedChar.Right)) { + FirstSelectedChar = moveToChar; + checkSelectedText(); + onTextSelectMoveForward(event); + invalidate(); + } + } + } + } else if (CurrentMode == Mode.PressUnSelectText) { + + } + } + } + + /** + * 执行上一页动画 + * + * @param event + */ + protected void startPageUpAnimation(MotionEvent event) { + if (getMoveDistance() < -PageChangeMinMoveDistance || getMoveDistance() > PageChangeMinMoveDistance) { + if (isPagePre()) { + if (!isFirstPage()) { + startPagePreAnimation(); + } else { + releaseTouch(); + invalidate(); + } + } else if (isPageNext()) { + if (!isLastPage()) { + startPageNextAnimation(); + } else { + releaseTouch(); + invalidate(); + } + } + } else { + //没有超出距离,自动还原 + if ((getMoveDistance() > 0 && isFirstPage()) || (getMoveDistance() < 0 && isLastPage())) { + //这种情况不执行 + } else { + //如果只是移动一点点,释放即可,不需要恢复 + if ((getMoveDistance() > 0 && getMoveDistance() < 5) || (getMoveDistance() <= 0 && getMoveDistance() > -5)) { + releaseTouch(); + invalidate(); + } else {//ss + startPageStateBackAnimation(); + } + } + + } + + } + + + /** + * @return 当前页是否是第一页 + */ + protected synchronized Boolean isFirstPage() { + return readerContext.getPageData().FirstPage() == null || getTopPage() == null; + } + + /** + * @return 当前页是否是最后一页 + */ + protected synchronized Boolean isLastPage() { + return readerContext.getPageData().LastPage() == null || getBottomPage() == null; + } + + /** + * 执行恢复到原状态动画 + */ + protected abstract void startPageStateBackAnimation(); + + /** + * 执行滑动到下一页 + */ + protected abstract void startPageNextAnimation(); + + /** + * 执行滑动到上一页 + */ + protected abstract void startPagePreAnimation(); + + /** + * @param event 向前滑动选择文字 + */ + protected abstract void onTextSelectMoveForward(MotionEvent event); + + /** + * @param event 向后滑动选择文字 + */ + protected abstract void onTextSelectMoveBack(MotionEvent event); + + /** + * @param event 页面移动 + */ + protected abstract void onPageMove(MotionEvent event); + + + @Override + public boolean onDown(MotionEvent motionEvent) { + mDown.x = motionEvent.getX(); + mDown.y = motionEvent.getY(); + mTouch.x = motionEvent.getX(); + mTouch.y = motionEvent.getY(); + hasDown = true; + + if (CurrentMode == Mode.PressSelectText + || CurrentMode == Mode.SelectMoveForward + || CurrentMode == Mode.SelectMoveBack) { + CurrentMode = Mode.PressSelectText; + Path leftSliderPath = getLeftSliderPath(); + Path rightSliderPath = getRightSliderPath(); + + if (leftSliderPath != null && rightSliderPath != null) { + + Boolean downOnLeftSlider = computeRegion(getLeftSliderPath()).contains((int) mDown.x, (int) mDown.y); + Boolean downOnRightSlider = computeRegion(getRightSliderPath()).contains((int) mDown.x, (int) mDown.y); + + if (downOnLeftSlider || downOnRightSlider) { + if (downOnLeftSlider) { + CurrentMode = Mode.SelectMoveForward; + setLeftSlider(FirstSelectedChar); + } else { + + CurrentMode = Mode.SelectMoveBack; + setRightSlider(LastSelectedChar); + } + return true; + } + } + } else { + if (CurrentMode == Mode.PagePreIng || CurrentMode == Mode.PageNextIng) {//执行动画的使用不允许刷新状态 + } else { + CurrentMode = Mode.Normal; + invalidate(); + } + return true; + } + return true; + } + + + /** + * 释放了滑动条 + */ + protected void onReleasedSlider() { + //已经释放了滑动选择 + if (sliderListener != null) { + sliderListener.onReleaseSlider(); + } + } + + /** + * 显示了滑动条 + */ + protected void onShownSlider() { + //开始显示滑动选择 + if (sliderListener != null) { + sliderListener.onShowSlider(FirstSelectedChar); + sliderListener.onShowSlider(FirstSelectedChar.getValueStr()); + } + } + + + /** + * 轻击翻页 + */ + @Override + public boolean onSingleTapUp(MotionEvent e) { + //当前是长按滑动事件的话,没有点击到滑动条,应该释放 + if (CurrentMode == Mode.PressSelectText + || CurrentMode == Mode.SelectMoveForward + || CurrentMode == Mode.SelectMoveBack) { + //没有点击到滑动条,释放 + CurrentMode = Mode.Normal; + onReleasedSlider(); + invalidate(); + return true; + } + + Boolean dealCenterClickAndDonChangePage = dealCenterClickAndDoChangePage(e); + if (dealCenterClickAndDonChangePage) { + return true; + } + return false; + } + + /** + * 处理中间区域点击事件并且可能执行点击翻页效果 + */ + private boolean dealCenterClickAndDoChangePage(MotionEvent e) { + + if (CurrentMode == Mode.Normal && readerContext.InitDone()) { + float widthPercent = readerContext.getTxtConfig().CenterClickArea; + //捕捉异常情况 + widthPercent = widthPercent < 0 ? 0 : widthPercent; + widthPercent = widthPercent > 1 ? 1 : widthPercent; + + int width = (int) (getWidth() * widthPercent); + int left = getWidth() / 2 - width / 2; + int top = getHeight() / 2 - width; + int bottom = top + width + width; + int right = left + width; + + int x = (int) e.getX(); + int y = (int) e.getY(); + + boolean needPagePre = x < left; + boolean needPageNext = x > right; + boolean inCenter = x > left && x < right && y > top && y < bottom; + boolean deal = false; + + if (inCenter) { + //点击中间区域 + if (centerAreaClickListener != null) { + deal = centerAreaClickListener.onCenterClick(widthPercent); + } + } else { + if (centerAreaClickListener != null) { + deal = centerAreaClickListener.onOutSideCenterClick(widthPercent); + } + } + //getMoveDistance() < -PageChangeMinMoveDistance || getMoveDistance() > PageChangeMinMoveDistance + // if ((getMoveDistance() > 0 && isFirstPage()) || (getMoveDistance() < 0 && isLastPage())) {} 这样的情况才不执行 + + //如果这个事件没有被处理,将可能会执行翻页事件 + if (!deal) { + if (needPagePre && !isFirstPage()) { + // mTouch.x - mDown.x>10 + //模拟滑动执行翻上一页手势 + mDown.x = 0; + mTouch.x = mDown.x + 15; + tryDoPagePre(); + startPagePreAnimation(); + return true; + } + + if (needPageNext && !isLastPage()) { + //mTouch.x - mDown.x<-10 + ///模拟滑动执行翻下一页手势 + mDown.x = getWidth(); + mTouch.x = mDown.x - 15; + tryDoPageNext(); + startPageNextAnimation(); + return true; + } + } + + } + return false; + } + + /** + * 长按事件,执行检测长按文字 + */ + @Override + public void onLongPress(MotionEvent e) { + if (CurrentMode == Mode.Normal) { + onPressSelectText(e); + } + } + + /** + * @param motionEvent motionEvent + */ + @Override + public void onShowPress(MotionEvent motionEvent) { + ELogger.log(tag, "onShowPress ,CurrentMode:" + CurrentMode); + } + + /** + * @param motionEvent motionEvent + * @param motionEvent1 motionEvent1 + * @param v v + * @param v1 v1 + * @return + */ + @Override + public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) { + return false; + } + + /** + * 快速滑动翻页 + * + * @param e1 e1 + * @param e2 e2 + * @param velocityX velocityX + * @param velocityY velocityY + * @return + */ + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + float MaxVelocityX = 1000; + if (CurrentMode == Mode.Normal) {//正常情况下快速滑动,执行翻页动作 + if (isPagePre() && !isFirstPage() && velocityX > MaxVelocityX) { + startPagePreAnimation(); + return true; + } else if (isPageNext() && !isLastPage() && velocityX < -MaxVelocityX) { + startPageNextAnimation(); + return true; + } + } + return false; + } + + /** + * 长按获取按着的文字,这时 FirstSelectedChar = LastSelectedChar + */ + protected void onPressSelectText(MotionEvent e) { + TxtChar selectedChar = findCharByPosition(e.getX(), e.getY()); + if (selectedChar != null) + ELogger.log("onPressSelectText", selectedChar.toString()); + else + ELogger.log("onPressSelectText", "is null" + e.getX() + "," + e.getY()); + if (selectedChar != null) { + FirstSelectedChar = selectedChar; + LastSelectedChar = selectedChar; + setLeftSlider(FirstSelectedChar); + setRightSlider(LastSelectedChar); + CurrentMode = Mode.PressSelectText; + onShownSlider(); + } else { + CurrentMode = Mode.PressUnSelectText; + FirstSelectedChar = null; + LastSelectedChar = null; + onReleasedSlider(); + } + releaseTouch(); + postInvalidate(); + + } + + /** + * @param FirstSelectedChar 设置左滑动条数据 + */ + private void setLeftSlider(TxtChar FirstSelectedChar) { + mLeftSlider.Left = FirstSelectedChar.Left - SliderWidth * 2; + mLeftSlider.Right = FirstSelectedChar.Left; + mLeftSlider.Top = FirstSelectedChar.Bottom; + mLeftSlider.Bottom = FirstSelectedChar.Bottom + SliderWidth * 2; + } + + /** + * @param LastSelectedChar 设置右滑动条数据 + */ + private void setRightSlider(TxtChar LastSelectedChar) { + mRightSlider.Left = LastSelectedChar.Right; + mRightSlider.Right = LastSelectedChar.Right + SliderWidth * 2; + mRightSlider.Top = LastSelectedChar.Bottom; + mRightSlider.Bottom = LastSelectedChar.Bottom + SliderWidth * 2; + } + + + /** + * @return 找到长按选中的文字,找不到返回null + */ + private TxtChar findCharByPosition(float positionX, float positionY) { + boolean isVerticalMode = false; + if (readerContext != null && readerContext.getTxtConfig() != null) { + isVerticalMode = readerContext.getTxtConfig().VerticalPageMode; + } + if (isVerticalMode) { + return findCharByPositionOfVerticalMode(positionX, positionY); + } else { + return findCharByPositionOfHorizontalMode(positionX, positionY); + } + } + + private TxtChar findCharByPositionOfVerticalMode(float positionX, float positionY) { + IPage page = readerContext.getPageData().MidPage(); + int offset = readerContext.getPageParam().LinePadding / 2; + //当前页面有数据才执行查找 + if (page != null && page.HasData()) { + List lines = page.getLines(); + for (ITxtLine line : lines) { + List chars = line.getTxtChars(); + if (chars != null && chars.size() > 0) { + for (TxtChar c : chars) { + if (positionX > (c.Left - offset) && positionX < (c.Right + offset)) { + if (positionY > c.Top && positionY <= c.Bottom) { + return c; + } + } else { + break;//说明在下一行 + } + } + } + } + } else { + ELogger.log(tag, "page not null and page hasData()"); + } + return null; + } + + private TxtChar findCharByPositionOfHorizontalMode(float positionX, float positionY) { + IPage page = readerContext.getPageData().MidPage(); + int offset = readerContext.getPageParam().LinePadding / 2; + //当前页面有数据才执行查找 + if (page != null && page.HasData()) { + List lines = page.getLines(); + for (ITxtLine line : lines) { + List chars = line.getTxtChars(); + if (chars != null && chars.size() > 0) { + for (TxtChar c : chars) { + if (positionY > (c.Top - offset) && positionY < (c.Bottom + offset)) { + if (positionX > c.Left && positionX <= c.Right) { + return c; + } + } else { + break;//说明在下一行 + } + } + } + } + } else { + ELogger.log(tag, "page not null and page hasData()"); + } + return null; + } + + /** + * @return 找到长按选中的文字,找不到返回null + */ + private TxtChar findCharByPositionWhileMove(float positionX, float positionY) { + IPage page = readerContext.getPageData().MidPage(); + int offset = readerContext.getPageParam().LinePadding / 2; + //当前页面有数据才执行查找 + if (page != null && page.HasData()) { + List lines = page.getLines(); + for (ITxtLine line : lines) { + List chars = line.getTxtChars(); + if (chars != null && chars.size() > 0) { + for (TxtChar c : chars) { + if (positionY > (c.Top - offset) && positionY < (c.Bottom + offset)) { + if (positionX > c.Left && positionX < c.Right) { + return c; + } else {//说明在行的左边或者右边啊 + TxtChar first = chars.get(0); + TxtChar last = chars.get(chars.size() - 1); + if (positionX < first.Left) { + return first; + } else if (positionX > last.Right) { + return last; + } + } + } else { + break;//说明在下一行 + } + } + } + } + } else { + ELogger.log(tag, "page not null and page hasData()"); + } + return null; + } + + private Path mSliderPath = new Path(); + + /** + * @return 可能返回null + */ + protected Path getLeftSliderPath() { + return mLeftSlider.getPath(FirstSelectedChar, mSliderPath); + + } + + /** + * @return 可能返回null + */ + protected Path getRightSliderPath() { + return mRightSlider.getPath(LastSelectedChar, mSliderPath); + } + + //当前滑动选择的数据 + private final List mSelectLines = new ArrayList<>(); + + /** + * @return 获取当前选中的行文字 + */ + protected synchronized List getCurrentSelectTextLine() { + return mSelectLines; + } + + + /** + * 检测滑动选中的文字 + */ + protected synchronized void checkSelectedText() { + Boolean Started = false; + Boolean Ended = false; + //清空之前选择的数据 + mSelectLines.clear(); + IPage currentPage = readerContext.getPageData().MidPage(); + //当前页面没有数据或者没有选择或者已经释放了长按选择事件,不执行 + if (currentPage == null || !currentPage.HasData() || FirstSelectedChar == null || LastSelectedChar == null) { + return; + } + //获取当前页面行数据 + List lines = currentPage.getLines(); + // 找到选择的字符数据,转化为选择的行,然后将行选择背景画出来 + for (ITxtLine l : lines) { + ITxtLine selectLine = new TxtLine(); + for (TxtChar c : l.getTxtChars()) { + if (!Started) { + if (c.ParagraphIndex == FirstSelectedChar.ParagraphIndex && c.CharIndex == FirstSelectedChar.CharIndex) { + Started = true; + selectLine.addChar(c); + if (c.ParagraphIndex == LastSelectedChar.ParagraphIndex && c.CharIndex == LastSelectedChar.CharIndex) { + Ended = true; + break; + } + } + } else { + if (c.ParagraphIndex == LastSelectedChar.ParagraphIndex && c.CharIndex == LastSelectedChar.CharIndex) { + Ended = true; + if (selectLine.getTxtChars() == null || !selectLine.getTxtChars().contains(c)) { + selectLine.addChar(c); + } + break; + } else { + selectLine.addChar(c); + } + } + } + + if (selectLine.HasData()) { + mSelectLines.add(selectLine); + } + + if (Started && Ended) { + return; + } + } + } + + /** + * @return 获取当前选中的文字,不会返回null + */ + protected String getCurrentSelectedText() { + String text = ""; + for (ITxtLine l : mSelectLines) { + text = text + l.getLineStr(); + } + return text; + } + + /** + * @param path + * @return 计算区域 + */ + + private Region computeRegion(Path path) { + Region region = new Region(); + RectF f = new RectF(); + path.computeBounds(f, true); + region.setPath(path, new Region((int) f.left, (int) f.top, (int) f.right, (int) f.bottom)); + return region; + } + + /** + * @param TouchX + * @param TouchY + * @return 是否可以向后滑动 + */ + private boolean CanMoveBack(float TouchX, float TouchY) { + if (FirstSelectedChar != null) { + Path p = new Path(); + p.moveTo(FirstSelectedChar.Right, FirstSelectedChar.Top); + p.lineTo(getWidth(), FirstSelectedChar.Top); + p.lineTo(getWidth(), getHeight()); + p.lineTo(0, getHeight()); + p.lineTo(0, FirstSelectedChar.Bottom); + p.lineTo(FirstSelectedChar.Right, FirstSelectedChar.Bottom); + p.lineTo(FirstSelectedChar.Right, FirstSelectedChar.Top); + return computeRegion(p).contains((int) TouchX, (int) TouchY); + } else { + return false; + } + } + + /** + * @param TouchX + * @param TouchY + * @return 是否可以向前滑动 + */ + private boolean CanMoveForward(float TouchX, float TouchY) { + if (LastSelectedChar != null) { + Path p = new Path(); + p.moveTo(LastSelectedChar.Left, LastSelectedChar.Top); + p.lineTo(getWidth(), LastSelectedChar.Top); + p.lineTo(getWidth(), 0); + p.lineTo(0, 0); + p.lineTo(0, LastSelectedChar.Bottom); + p.lineTo(LastSelectedChar.Left, LastSelectedChar.Bottom); + p.lineTo(LastSelectedChar.Left, LastSelectedChar.Top); + return computeRegion(p).contains((int) TouchX, (int) TouchY); + } else { + return false; + } + } + + + protected Boolean isPageNext() { + //是否是执行下一页 + return getMoveDistance() < -10; + } + + protected Boolean isPagePre() { + //是否是执行上一页 + return getMoveDistance() > 10; + } + + /** + * @return 获取当前滑动距离 + */ + protected synchronized float getMoveDistance() { + int i = (int) (mTouch.x - mDown.x); + float m = mTouch.x - mDown.x; + if (i < m) { + return i + 1; + } + return m; + } + + protected void tryDoPagePre() { + BottomPage = readerContext.getBitmapData().FirstPage(); + } + + protected void tryDoPageNext() { + BottomPage = readerContext.getBitmapData().LastPage(); + } + + protected void checkMoveState() { + //检测页面滑动状态更新显示数据 + if (isPagePre()) { + tryDoPagePre(); + } else if (isPageNext()) { + tryDoPageNext(); + } else { + TopPage = readerContext.getBitmapData().MidPage(); + tryDoPageNext(); + } + } + + protected void doPagePreDone() { + //执行获取上一页数据 + IPage firstPage = readerContext.getPageData().FirstPage(); + if (firstPage == null) {//没有上一页数据了 + ELogger.log(tag, "没有上一页数据了"); + CurrentMode = Mode.Normal; + return; + } + pagePreTask.Run(null, readerContext); + } + + protected void doPageNextDone() { + //执行获取下一页数据 + IPage lastPage = readerContext.getPageData().LastPage(); + if (lastPage == null) {//没有下一页数据了 + CurrentMode = Mode.Normal; + return; + } + + pageNextTask.Run(null, readerContext); + } + + protected Bitmap getTopPage() { + if (TopPage != null && TopPage.isRecycled()) { + TopPage = null; + } + return TopPage; + } + + protected Bitmap getBottomPage() { + if (BottomPage != null && BottomPage.isRecycled()) { + BottomPage = null; + } + return BottomPage; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (readerContext != null) { + readerContext.Clear(); + } + } + + /** + * 释放触摸事件 + */ + protected void releaseTouch() { + mTouch.x = 0; + mDown.x = 0; + hasDown = false; + } + + public enum Mode { + Normal, //正常模式 + PagePreIng,//执行上一页数据获取等相关操作 + PageNextIng,//执行下一页数据获取等相关操作 + PressSelectText,//长按选中文字 + PressUnSelectText,//长按但是未选中文字 + SelectMoveForward, //向前滑动选中文字 + SelectMoveBack//向后滑动选中文字 + } + + + //---------------------------获取上一页、获取下一页数据-------------------------------------- + + protected final ITxtTask pageNextTask = new PageNextTask(); + protected final ITxtTask pagePreTask = new PagePreTask(); + protected final BitmapProduceTask bitmapProduceTask = new BitmapProduceTask(); + + private class PageNextTask implements ITxtTask { + @Override + public void Run(ILoadListener callBack, final TxtReaderContext readerContext) { + CurrentMode = Mode.PageNextIng; + getPageNextData(); + bitmapProduceTask.Run(new ILoadListener() { + @Override + public void onSuccess() { + releaseTouch(); + checkMoveState(); + + post(new Runnable() { + @Override + public void run() { + invalidate(); + CurrentMode = Mode.Normal; + onPageProgress(readerContext.getPageData().MidPage()); + } + }); + + } + + @Override + public void onFail(TxtMsg txtMsg) { + CurrentMode = Mode.Normal; + ELogger.log(tag + "PageNextTask", "PageNextTask onFail" + txtMsg); + } + + @Override + public void onMessage(String message) { + CurrentMode = Mode.Normal; + ELogger.log(tag + "PageNextTask", "PageNextTask onMessage" + message); + } + }, readerContext); + } + + private void getPageNextData() { + + IPage nextMidPage = readerContext.getPageData().LastPage(); + IPage nextFirstPage = readerContext.getPageData().MidPage(); + + IPage firstPage = null; + IPage midPage; + IPage nextPage = null; + + if (nextFirstPage != null && nextFirstPage.HasData()) { + if (nextFirstPage.isFullPage()) { //说明,nextFirstPage是完整的页数据,直接获取 + firstPage = nextFirstPage; + } + } + + if (nextMidPage != null && nextMidPage.isFullPage()) {//说明之前的LastPage是完整的数据,直接获取 + midPage = nextMidPage; + } else { + midPage = nextMidPage;//说明之前的LastPage不是完整的数据,也直接获取 + } + + if (midPage != null && midPage.isFullPage()) { + //midPage是完整页,说明可能有下一页数据,否则没有下一页数据了 + nextPage = readerContext.getPageDataPipeline().getPageStartFromProgress(midPage.getLastChar().ParagraphIndex, midPage.getLastChar().CharIndex + 1); + } + + + if (firstPage != null && nextFirstPage != null) { + readerContext.getBitmapData().setFirstPage(readerContext.getBitmapData().MidPage()); + readerContext.getPageData().refreshTag[0] = 0; + + } + + if (midPage != null && midPage.HasData()) { + readerContext.getBitmapData().setMidPage(readerContext.getBitmapData().LastPage()); + readerContext.getPageData().refreshTag[1] = 0; + } + + readerContext.getBitmapData().setLastPage(null); + readerContext.getPageData().refreshTag[2] = 1; + readerContext.getPageData().setFirstPage(firstPage); + readerContext.getPageData().setMidPage(midPage); + readerContext.getPageData().setLastPage(nextPage); + + } + } + + + private class PagePreTask implements ITxtTask { + @Override + public void Run(ILoadListener callBack, final TxtReaderContext readerContext) { + CurrentMode = Mode.PagePreIng; + getPagePreData(); + bitmapProduceTask.Run(new ILoadListener() { + @Override + public void onSuccess() { + releaseTouch(); + checkMoveState(); + post(new Runnable() { + @Override + public void run() { + invalidate(); + CurrentMode = Mode.Normal; + onPageProgress(readerContext.getPageData().MidPage()); + } + }); + } + + @Override + public void onFail(TxtMsg txtMsg) { + CurrentMode = Mode.Normal; + ELogger.log(tag + "PagePreTask", "PageNextTask onFail" + txtMsg); + } + + @Override + public void onMessage(String message) { + CurrentMode = Mode.Normal; + ELogger.log(tag + "PagePreTask", "PageNextTask onMessage" + message); + } + }, readerContext); + } + + private void getPagePreData() { + IPage nextMayMidPage = readerContext.getPageData().FirstPage(); + IPage nextMayLastPage = readerContext.getPageData().MidPage(); + + IPage firstPage = null; + IPage midPage = null; + IPage nextPage = null; + + if (nextMayMidPage != null && nextMayMidPage.HasData()) { + if (nextMayMidPage.isFullPage()) {//之前的FirstPage是满页的,直接获取 + midPage = nextMayMidPage; + } else {//之前的FirstPage不是是满页的,直接从头开始获取 + midPage = readerContext.getPageDataPipeline().getPageStartFromProgress(0, 0); + } + } + + if (midPage != null && midPage.isFullPage()) {//现在说明midPage是满页的,可能有上一页数据与下一页数据 + if (midPage.getFirstChar().ParagraphIndex == 0 && midPage.getFirstChar().CharIndex == 0) { + firstPage = null;//之前的FirstPage不是是满页的,直接从头开始获取,没有firstPage + } else { + + firstPage = readerContext.getPageDataPipeline().getPageEndToProgress(midPage.getFirstChar().ParagraphIndex, midPage.getFirstChar().CharIndex); + } + nextPage = readerContext.getPageDataPipeline().getPageStartFromProgress(midPage.getLastChar().ParagraphIndex, midPage.getLastChar().CharIndex + 1); + } + + + //判断是否是相同数据然后进行判断是否需要进行刷新 + int needRefresh = 1; + if (isSamePageData(nextPage, nextMayLastPage)) { + needRefresh = 0; + readerContext.getBitmapData().setLastPage(readerContext.getBitmapData().MidPage()); + nextPage = nextMayLastPage; + } + readerContext.getPageData().refreshTag[2] = needRefresh; + + //判断是否是相同数据然后进行判断是否需要进行刷新 + needRefresh = 1; + if (isSamePageData(midPage, nextMayMidPage)) { + needRefresh = 0; + readerContext.getBitmapData().setMidPage(readerContext.getBitmapData().FirstPage()); + midPage = nextMayMidPage; + } + readerContext.getPageData().refreshTag[1] = needRefresh; + readerContext.getBitmapData().setFirstPage(null); + readerContext.getPageData().refreshTag[0] = 1; + readerContext.getPageData().setFirstPage(firstPage); + readerContext.getPageData().setMidPage(midPage); + readerContext.getPageData().setLastPage(nextPage); + } + } + + + private boolean isSamePageData(IPage f, IPage to) { + if (f != null && to != null && f.HasData() && to.HasData()) { + TxtChar fF = f.getFirstChar(); + TxtChar fL = f.getLastChar(); + TxtChar toF = to.getFirstChar(); + TxtChar toL = to.getLastChar(); + return fF.equals(toF) && fL.equals(toL); + } + return false; + } + + protected void onPageProgress(IPage page) { + if (page != null && page.HasData()) { + TxtChar lastChar = page.getLastChar(); + onProgressCallBack(getProgress(lastChar.ParagraphIndex, lastChar.CharIndex)); + } else { + ELogger.log(tag, "onPageProgress ,page data may be empty"); + } + + } + + protected float getProgress(int ParagraphIndex, int chartIndex) { + float progress = 0; + int pN = readerContext.getParagraphData().getParagraphNum(); + if (pN > 0 && pN > ParagraphIndex) { + int index = readerContext.getParagraphData().getParaStartCharIndex(ParagraphIndex) + chartIndex; + int num = readerContext.getParagraphData().getCharNum(); + if (num > 0) { + if (index > num) { + progress = 1; + } else { + progress = (float) index / (float) num; + } + } + } + return progress; + } + + protected void onProgressCallBack(float progress) { + if (pageChangeListener != null) { + pageChangeListener.onCurrentPage(progress); + } + if (pageEdgeListener != null) { + if (isFirstPage()) { + pageEdgeListener.onCurrentFirstPage(); + } + if (isLastPage()) { + pageEdgeListener.onCurrentLastPage(); + } + } + } + + + private class TxtReaderScroller extends Scroller { + public TxtReaderScroller(Context context) { + super(context); + } + + @Override + public void abortAnimation() { + super.abortAnimation(); + releaseTouch(); + } + } + + /** + * 刷新数据标签,int r1, int r2, int r3决定了页面数据是否出现刷新 + * ,不需要的话直接使用缓存bitmap + * + * @param r1 + * @param r2 + * @param r3 + */ + protected void refreshTag(int r1, int r2, int r3) { + readerContext.getPageData().refreshTag[0] = r1; + readerContext.getPageData().refreshTag[1] = r2; + readerContext.getPageData().refreshTag[2] = r3; + } + + + /** + * @param filePath + * @param listener + */ + private void loadFile(final String filePath, final ILoadListener listener) { + destroyFileLoadTask(); + final TxtFileLoader loader = new TxtFileLoader(); + DestroyableTask task = new DestroyableTask(loader::onStop); + task.excuse(() -> loader.load(filePath, readerContext, new DataLoadListener(listener))); + mFileLoadTask = task; + } + + /** + * @param text + * @param listener + */ + private void loadTextStr(final String text, final ILoadListener listener) { + destroyStrLoadTask(); + final TextLoader loader = new TextLoader(); + DestroyableTask task = new DestroyableTask(null); + task.excuse(() -> loader.load(text, readerContext, new DataLoadListener(listener))); + mStrLoadTask = task; + } + + private void destroyFileLoadTask() { + if (mFileLoadTask != null) { + mFileLoadTask.destroy(); + } + } + + private void destroyStrLoadTask() { + if (mStrLoadTask != null) { + mStrLoadTask.destroy(); + } + } + + /** + * 数据加载监听 + */ + private class DataLoadListener implements ILoadListener { + ILoadListener listener; + + public DataLoadListener(ILoadListener listener) { + this.listener = listener; + } + + @Override + public void onSuccess() { + checkMoveState(); + postInvalidate(); + post(new Runnable() { + @Override + public void run() { + onPageProgress(readerContext.getPageData().MidPage()); + if (listener != null) { + listener.onSuccess(); + } + } + }); + + } + + @Override + public void onFail(TxtMsg txtMsg) { + if (listener != null) { + listener.onFail(txtMsg); + } + } + + @Override + public void onMessage(String message) { + if (listener != null) { + listener.onMessage(message); + } + } + } + + private void initReaderContext() { + PageChangeMinMoveDistance = getWidth() / 5; + PageParam param = readerContext.getPageParam(); + param.PageWidth = getWidth(); + param.PageHeight = getHeight(); + } + + + //------------------------------------------------------------- + + + /** + * @param pageEdgeListener 当前页是首页获取尾页监听,注意如果文本只有一页,那边首页与尾页都会回调 + */ + public void setOnPageEdgeListener(IPageEdgeListener pageEdgeListener) { + this.pageEdgeListener = pageEdgeListener; + } + + /** + * @param pageChangeListener 页面进度改变监听 + */ + public void setPageChangeListener(IPageChangeListener pageChangeListener) { + this.pageChangeListener = pageChangeListener; + } + + /** + * @param sliderListener 长按选择出现与消失监听 + */ + public void setOnSliderListener(ISliderListener sliderListener) { + this.sliderListener = sliderListener; + } + + /** + * @param centerAreaClickListener 中间区域点击监听 + */ + public void setOnCenterAreaClickListener(ICenterAreaClickListener centerAreaClickListener) { + this.centerAreaClickListener = centerAreaClickListener; + } + + /** + * 通过文件路径加载文件 + * + * @param filePath 文件路径 + * @param listener 监听 + */ + public void loadTxtFile(final String filePath, final ILoadListener listener) { + post(new Runnable() { + @Override + public void run() { + initReaderContext(); + loadFile(filePath, listener); + } + + }); + } + + /** + * 通过字符串加载 + * + * @param text 文本字符串数据 + * @param listener 监听 + */ + public void loadText(final String text, final ILoadListener listener) { + post(new Runnable() { + @Override + public void run() { + initReaderContext(); + loadTextStr(text, listener); + } + }); + } + + /** + * @return 获取当前页的第一个字符, 当前页每页数据,返回null + */ + public TxtChar getCurrentFirstChar() { + IPage page = readerContext.getPageData().MidPage(); + //当前页面有数据才执行查找 + if (page != null && page.HasData()) { + return page.getFirstChar(); + } + return null; + } + + + /** + * @return 获取当前页的第一行, 当前页每页数据,返回null + */ + public ITxtLine getCurrentFirstLines() { + IPage page = readerContext.getPageData().MidPage(); + //当前页面有数据才执行查找 + if (page != null && page.HasData()) { + return page.getFirstLine(); + } + return null; + } + + /** + * @param leftSlider 设置左侧滑动条 + */ + public void setLeftSlider(Slider leftSlider) { + this.mLeftSlider = leftSlider; + this.mLeftSlider.SliderWidth = SliderWidth; + } + + /** + * @param rightSlider 设置右侧滑动条 + */ + public void setRightSlider(Slider rightSlider) { + this.mRightSlider = rightSlider; + this.mRightSlider.SliderWidth = SliderWidth; + + } + + /** + * 调用该方法释放长按选择模式 + */ + public void releaseSelectedState() { + CurrentMode = Mode.Normal; + postInvalidate(); + } + + public void onDestroy() { + destroyFileLoadTask(); + destroyStrLoadTask(); + } + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderContext.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderContext.java new file mode 100644 index 0000000..3c916c0 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderContext.java @@ -0,0 +1,156 @@ +package com.bifan.txtreaderlib.main; + +import android.content.Context; + +import com.bifan.txtreaderlib.bean.TxtFileMsg; +import com.bifan.txtreaderlib.interfaces.IChapter; +import com.bifan.txtreaderlib.interfaces.IChapterMatcher; +import com.bifan.txtreaderlib.interfaces.IPageDataPipeline; +import com.bifan.txtreaderlib.interfaces.IParagraphData; + +import java.util.List; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class TxtReaderContext { + public Context context; + private IParagraphData paragraphData; + private PageParam pageParam; + private TxtFileMsg fileMsg; + private List chapters; + private IPageDataPipeline pageDataPipeline; + private PaintContext paintContext; + private TxtConfig txtConfig; + private Boolean InitDone = false; + private IChapterMatcher chapterMatcher; + + private final BitmapData bitmapData = new BitmapData(); + private final PageData pageData = new PageData(); + + public TxtReaderContext(Context context) { + this.context = context; + } + + public TxtFileMsg getFileMsg() { + return fileMsg; + } + + public void setFileMsg(TxtFileMsg fileMsg) { + this.fileMsg = fileMsg; + } + + public void setParagraphData(IParagraphData paragraphData) { + this.paragraphData = paragraphData; + } + + public IParagraphData getParagraphData() { + return paragraphData; + } + + public PageParam getPageParam() { + return pageParam; + } + + public void setPageParam(PageParam pageParam) { + this.pageParam = pageParam; + } + + public Context getContext() { + return context; + } + + public List getChapters() { + return chapters; + } + + public void setContext(Context context) { + this.context = context; + } + + public void setChapters(List chapters) { + this.chapters = chapters; + } + + public IChapterMatcher getChapterMatcher() { + return chapterMatcher; + } + + public void setChapterMatcher(IChapterMatcher chapterMatcher) { + this.chapterMatcher = chapterMatcher; + } + + public PageData getPageData() { + return pageData; + } + + public IPageDataPipeline getPageDataPipeline() { + if (pageDataPipeline == null) { + if (getTxtConfig().VerticalPageMode) { + pageDataPipeline = new VerticalPageDataPipeline(this); + }else{ + pageDataPipeline = new PageDataPipeline(this); + } + } + return pageDataPipeline; + } + + public PaintContext getPaintContext() { + if (paintContext == null) { + paintContext = new PaintContext(); + } + return paintContext; + } + + public void setPaintContext(PaintContext paintContext) { + this.paintContext = paintContext; + } + + public void setPageDataPipeline(IPageDataPipeline pageDataPipeline) { + this.pageDataPipeline = pageDataPipeline; + } + + public TxtConfig getTxtConfig() { + if (txtConfig == null) { + txtConfig = new TxtConfig(); + } + return txtConfig; + } + + public void setTxtConfig(TxtConfig txtConfig) { + this.txtConfig = txtConfig; + } + + public BitmapData getBitmapData() { + return bitmapData; + } + + public Boolean InitDone() { + return InitDone; + } + + public void setInitDone(Boolean initDone) { + InitDone = initDone; + } + + public void Clear() { + if (paragraphData != null) { + paragraphData.Clear(); + paragraphData = null; + } + if (paintContext != null) { + paintContext.onDestroy(); + paintContext = null; + } + if (chapters != null) { + chapters.clear(); + chapters = null; + } + bitmapData.onDestroy(); + pageData.onDestroy(); + chapterMatcher = null; + + } + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderView.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderView.java new file mode 100644 index 0000000..0d8fbfa --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/TxtReaderView.java @@ -0,0 +1,616 @@ +package com.bifan.txtreaderlib.main; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import com.bifan.txtreaderlib.bean.FileReadRecordBean; +import com.bifan.txtreaderlib.bean.TxtChar; +import com.bifan.txtreaderlib.bean.TxtFileMsg; +import com.bifan.txtreaderlib.interfaces.IChapter; +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.IPage; +import com.bifan.txtreaderlib.interfaces.IReaderViewDrawer; +import com.bifan.txtreaderlib.interfaces.ITextSelectListener; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.tasks.DrawPrepareTask; +import com.bifan.txtreaderlib.tasks.TxtConfigInitTask; +import com.bifan.txtreaderlib.tasks.TxtPageLoadTask; +import com.bifan.txtreaderlib.utils.ELogger; +import com.bifan.txtreaderlib.utils.FileUtil; +import com.bifan.txtreaderlib.utils.TxtBitmapUtil; + +import java.io.File; +import java.util.List; + +import androidx.annotation.Nullable; + +/** + * Created by bifa-wei + * on 2017/11/28. + */ + +public class TxtReaderView extends TxtReaderBaseView { + private String tag = "TxtReaderView"; + + public TxtReaderView(Context context) { + super(context); + } + + public TxtReaderView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + private IReaderViewDrawer drawer = null; + + @Override + protected void init() { + super.init(); + } + + @Override + protected void drawLineText(Canvas canvas) { + if (isPagePre() || isPageNext()) { + if (isPagePre()) { + //当前是第一页,只显示第一页 + if (isFirstPage()) { + if (getTopPage() != null) { + canvas.drawBitmap(getTopPage(), 0, 0, null); + } + } else { + //绘制上一页 + if (getTopPage() != null) { + drawPagePreTopPage(canvas); + } + //绘制下一页 + if (getBottomPage() != null) { + drawPagePreBottomPage(canvas); + } + //绘制阴影线 + drawPagePrePageShadow(canvas); + } + } else { + if (getTopPage() != null) { + drawPageNextTopPage(canvas); + } + if (getBottomPage() != null) { + drawPageNextBottomPage(canvas); + } + drawPageNextPageShadow(canvas); + + } + } else { + //说明没有触碰移动屏幕 + if (getTopPage() != null) { + canvas.drawBitmap(getTopPage(), 0, 0, null); + } + } + + } + + private void drawPageNextPageShadow(Canvas canvas) { + getDrawer().drawPageNextPageShadow(canvas); + + } + + private void drawPageNextBottomPage(Canvas canvas) { + //绘制执行下一页时的下面页部分 + getDrawer().drawPageNextBottomPage(canvas); + + + } + + private void drawPageNextTopPage(Canvas canvas) { + //绘制执行下一页时的上面页部分 + getDrawer().drawPageNextTopPage(canvas); + + } + + private void drawPagePrePageShadow(Canvas canvas) { + //绘制执行上一页的页边阴影 + getDrawer().drawPagePrePageShadow(canvas); + + } + + private void drawPagePreBottomPage(Canvas canvas) { + //绘制执行上一页时的下面页部分 + getDrawer().drawPagePreBottomPage(canvas); + + } + + private void drawPagePreTopPage(Canvas canvas) { + //绘制执行上一页时的上面页部分 + getDrawer().drawPagePreTopPage(canvas); + } + + @Override + protected void startPageStateBackAnimation() { + getDrawer().startPageStateBackAnimation(); + } + + @Override + protected void startPageNextAnimation() { + getDrawer().startPageNextAnimation(); + } + + @Override + protected void startPagePreAnimation() { + getDrawer().startPagePreAnimation(); + } + + + @Override + protected void onPageMove(MotionEvent event) { + + mTouch.x = event.getX(); + mTouch.y = event.getY(); + + + checkMoveState(); + + if (getMoveDistance() > 0 && isFirstPage()) { + ELogger.log(tag, "是第一页了"); + return; + } + + if (getMoveDistance() < 0 && isLastPage()) { + ELogger.log(tag, "是最后一页了"); + return; + } + invalidate(); + } + + protected void onActionUp(MotionEvent event) { + super.onActionUp(event); + if (CurrentMode == Mode.SelectMoveBack) { + if (textSelectListener != null) { + textSelectListener.onTextSelected(getCurrentSelectedText()); + } + } else if (CurrentMode == Mode.SelectMoveForward) { + if (textSelectListener != null) { + textSelectListener.onTextSelected(getCurrentSelectedText()); + } + } + + } + + @Override + protected void drawNote(Canvas canvas) { + getDrawer().drawNote(canvas); + } + + @Override + protected void drawSelectedText(Canvas canvas) { + getDrawer().drawSelectedText(canvas); + } + + @Override + protected void onTextSelectMoveForward(MotionEvent event) { + getDrawer().onTextSelectMoveForward(event); + if (textSelectListener != null) { + textSelectListener.onTextChanging(FirstSelectedChar, LastSelectedChar); + textSelectListener.onTextChanging(getCurrentSelectedText()); + } + } + + @Override + protected void onTextSelectMoveBack(MotionEvent event) { + getDrawer().onTextSelectMoveBack(event); + if (textSelectListener != null) { + textSelectListener.onTextChanging(FirstSelectedChar, LastSelectedChar); + textSelectListener.onTextChanging(getCurrentSelectedText()); + } + } + + @Override + public void computeScroll() { + super.computeScroll(); + getDrawer().computeScroll(); + } + + + private IReaderViewDrawer getDrawer() { + if (drawer == null) { + int pageSwitchMode = readerContext.getTxtConfig().Page_Switch_Mode; + switch (pageSwitchMode) { + case TxtConfig.PAGE_SWITCH_MODE_SERIAL: + drawer = new SerialPageDrawer(this, readerContext, mScroller); + break; + case TxtConfig.PAGE_SWITCH_MODE_SHEAR: + drawer = new ShearPageDrawer(this, readerContext, mScroller); + break; + default: + drawer = new NormalPageDrawer(this, readerContext, mScroller); + } + + } + return drawer; + } + + private ITextSelectListener textSelectListener; + + public void setOnTextSelectListener(ITextSelectListener textSelectListener) { + this.textSelectListener = textSelectListener; + } + + public TxtReaderContext getTxtReaderContext() { + return readerContext; + } + + private void Reload() { + saveProgress(); + readerContext.getPageData().refreshTag[0] = 1; + readerContext.getPageData().refreshTag[1] = 1; + readerContext.getPageData().refreshTag[2] = 1; + TxtConfigInitTask configInitTask = new TxtConfigInitTask(); + configInitTask.Run(actionLoadListener, readerContext); + } + + /** + * 设置字体大小 + * + * @param textSize min 25 max 70 in px + */ + public void setTextSize(int textSize) { + readerContext.getTxtConfig().saveTextSize(getContext(), textSize); + if (getWidth() > 0) { + Reload(); + } + } + + + /** + * 获取字体大小 in px + * + * @return + */ + public int getTextSize() { + return readerContext.getTxtConfig().getTextSize(getContext()); + } + + /** + * 获取背景颜色 + * + * @return + */ + public int getBackgroundColor() { + return readerContext.getTxtConfig().getBackgroundColor(getContext()); + } + + /** + * 设置样式 + * + * @param backgroundColor + * @param textColor + */ + public void setStyle(int backgroundColor, int textColor) { + saveProgress(); + TxtConfig.saveTextColor(getContext(), textColor); + TxtConfig.saveBackgroundColor(getContext(), backgroundColor); + if (getWidth() > 0) { + readerContext.getTxtConfig().textColor = textColor; + readerContext.getTxtConfig().backgroundColor = backgroundColor; + if (readerContext.getBitmapData().getBgBitmap() != null) { + readerContext.getBitmapData().getBgBitmap().recycle(); + } + int width = readerContext.getPageParam().PageWidth; + int height = readerContext.getPageParam().PageHeight; + readerContext.getBitmapData().setBgBitmap(TxtBitmapUtil.createBitmap(backgroundColor, width, height)); + refreshCurrentView(); + } + } + + + /** + * 从指定进度加载 + * + * @param progress 0~100 + */ + public void loadFromProgress(float progress) { + if (readerContext == null || readerContext.getParagraphData() == null) return; + if (progress < 0) progress = 0; + if (progress > 100) progress = 100; + float p = progress / 100; + int charNum = readerContext.getParagraphData().getCharNum(); + int charIndex = (int) (p * charNum); + int paragraphNum = readerContext.getParagraphData().getParagraphNum(); + int paragraphIndex = readerContext.getParagraphData().findParagraphIndexByCharIndex(charIndex); + + if (progress == 100 || paragraphIndex >= paragraphNum) { + paragraphIndex = paragraphNum - 1; + } + + if (paragraphIndex < 0) { + paragraphIndex = 0; + } + + ELogger.log(tag, "loadFromProgress ,progress:" + progress + "/paragraphIndex:" + paragraphIndex + "/paragraphNum:" + paragraphNum); + loadFromProgress(paragraphIndex, 0); + } + + /** + * 根据字符的准确位置跳转进度 + * + * @param charIndex + * @param paragraphIndex + */ + public void loadFromProgress(final int paragraphIndex, final int charIndex) { + refreshTag(1, 1, 1); + TxtPageLoadTask txtPageLoadTask = new TxtPageLoadTask(paragraphIndex, charIndex); + txtPageLoadTask.Run(new LoadListenerAdapter() { + @Override + public void onSuccess() { + if (readerContext != null) { + checkMoveState(); + postInvalidate(); + post(new Runnable() { + @Override + public void run() { + onProgressCallBack(getProgress(paragraphIndex, charIndex)); + tryFetchFirstPage(); + } + }); + } + } + }, readerContext); + } + + + private void tryFetchFirstPage() { + IPage midPage = readerContext.getPageData().MidPage(); + IPage firstPage = null; + onPageProgress(midPage); + if (midPage != null && midPage.HasData()) { + if (midPage.getFirstChar().ParagraphIndex == 0 && midPage.getFirstChar().CharIndex == 0) { + } else { + firstPage = readerContext.getPageDataPipeline().getPageEndToProgress(midPage.getFirstChar().ParagraphIndex, midPage.getFirstChar().CharIndex); + } + } + if (firstPage != null && firstPage.HasData()) { + if (!firstPage.isFullPage()) { + //说明是开始数据,重新刷新界面 + refreshTag(1, 1, 1); + loadFromProgress(0, 0); + } else { + refreshTag(1, 0, 0); + readerContext.getPageData().setFirstPage(firstPage); + ITxtTask task = new DrawPrepareTask(); + task.Run(actionLoadListener, readerContext);//获取上一页数据 + } + } + } + + /** + * 获取当前页章节 + * + * @return 没有章节返回null, 当前页没有数据返回null + */ + public IChapter getCurrentChapter() { + List chapters = readerContext.getChapters(); + IPage midPage = readerContext.getPageData().MidPage(); + if (chapters == null || chapters.size() == 0 || midPage == null || !midPage.HasData()) { + return null; + } + IChapter lastChapter = readerContext.getChapters().get(readerContext.getChapters().size() - 1); + int currentP = midPage.getFirstChar().ParagraphIndex; + int lastP = lastChapter.getStartParagraphIndex(); + int pre = 0; + int next = 0; + int index = 1; + + for (int i = 0; i < chapters.size(); i++) { + int startP = chapters.get(i).getStartParagraphIndex(); + if (i == 0) { + pre = startP; + } else { + next = startP; + if (currentP >= pre && currentP < next) { + index = i; + break; + } else { + pre = next; + } + } + } + if (currentP >= lastP) { + return lastChapter; + } else { + return chapters.get(index - 1); + } + } + + /** + * 跳转到上一章 + * + * @return 是否成功 + */ + public Boolean jumpToPreChapter() { + IChapter currentChapter = getCurrentChapter(); + List chapters = getChapters(); + if (chapters == null || currentChapter == null) { + ELogger.log(tag, "jumpToPreChapter false chapters == null or currentChapter == null"); + return false; + } + + int index = currentChapter.getIndex(); + + if (index == 0 || chapters.size() == 0) { + ELogger.log(tag, "jumpToPreChapter false index == 0 or chapters.size() == 0"); + return false; + } + refreshTag(1, 1, 1); + + IChapter preChapter = chapters.get(index - 1); + loadFromProgress(preChapter.getStartParagraphIndex(), 0); + return true; + } + + /** + * 跳转到下一章 + * + * @return 是否成功 + */ + public Boolean jumpToNextChapter() { + IChapter currentChapter = getCurrentChapter(); + List chapters = getChapters(); + if (chapters == null || currentChapter == null) { + ELogger.log(tag, "jumpToNextChapter false chapters == null or currentChapter == null"); + return false; + } + int index = currentChapter.getIndex(); + if (index >= chapters.size() - 1 || chapters.size() == 0) { + ELogger.log(tag, "jumpToNextChapter false < chapters.size() - 1 or chapters.size() == 0"); + return false; + } + + refreshTag(1, 1, 1); + + IChapter nextChapter = chapters.get(index + 1); + loadFromProgress(nextChapter.getStartParagraphIndex(), 0); + return true; + } + + /** + * 保存当前进度 + */ + public void saveProgress() { + IPage currentPage = readerContext.getPageData().MidPage(); + if (currentPage != null && currentPage.HasData() && readerContext.getFileMsg() != null) { + TxtChar firstChar = currentPage.getFirstChar(); + readerContext.getFileMsg().PreParagraphIndex = firstChar.ParagraphIndex; + readerContext.getFileMsg().PreCharIndex = firstChar.CharIndex; + readerContext.getFileMsg().CurrentParagraphIndex = firstChar.ParagraphIndex; + readerContext.getFileMsg().CurrentCharIndex = firstChar.CharIndex; + } + } + + + /** + * @param isBold 字体否加粗 + */ + public void setTextBold(boolean isBold) { + TxtConfig.saveIsBold(getContext(), isBold); + getTxtReaderContext().getTxtConfig().Bold = isBold; + refreshCurrentView(); + } + + /** + * 平移切换页面 + */ + public void setPageSwitchByTranslate() { + TxtConfig.savePageSwitchMode(getContext(), TxtConfig.PAGE_SWITCH_MODE_SERIAL); + getTxtReaderContext().getTxtConfig().Page_Switch_Mode = TxtConfig.PAGE_SWITCH_MODE_SERIAL; + drawer = new SerialPageDrawer(this, readerContext, mScroller); + } + + /** + * 剪切切换页面 + */ + public void setPageSwitchByShear() { + TxtConfig.savePageSwitchMode(getContext(), TxtConfig.PAGE_SWITCH_MODE_SHEAR); + getTxtReaderContext().getTxtConfig().Page_Switch_Mode = TxtConfig.PAGE_SWITCH_MODE_SHEAR; + drawer = new ShearPageDrawer(this, readerContext, mScroller); + } + + /** + * 滑盖切换页面 + */ + public void setPageSwitchByCover() { + TxtConfig.savePageSwitchMode(getContext(), TxtConfig.PAGE_SWITCH_MODE_COVER); + getTxtReaderContext().getTxtConfig().Page_Switch_Mode = TxtConfig.PAGE_SWITCH_MODE_COVER; + drawer = new NormalPageDrawer(this, readerContext, mScroller); + } + + /** + * 保存当前进度到数据库,建议退出时调用 + */ + public void saveCurrentProgress() { + TxtFileMsg fileMsg = getTxtReaderContext().getFileMsg(); + if (getTxtReaderContext().InitDone() && fileMsg != null) { + String path = fileMsg.FilePath; + if (path != null && new File(path).exists()) { + //说明当前是有数据的 + IPage midPage = getTxtReaderContext().getPageData().MidPage(); + if (midPage != null && midPage.HasData()) { + FileReadRecordDB readRecordDB = new FileReadRecordDB(readerContext.getContext()); + readRecordDB.createTable(); + FileReadRecordBean r = new FileReadRecordBean(); + r.fileName = fileMsg.FileName; + r.filePath = fileMsg.FilePath; + try { + r.fileHashName = FileUtil.getMD5Checksum(path); + } catch (Exception e) { + ELogger.log(tag, "saveCurrentProgress Exception:" + e.toString()); + readRecordDB.closeTable(); + return; + } + + r.paragraphIndex = midPage.getFirstChar().ParagraphIndex; + r.chartIndex = midPage.getFirstChar().CharIndex; + readRecordDB.insertData(r); + readRecordDB.closeTable(); + } else { + ELogger.log(tag, "saveCurrentProgress midPage is false empty"); + } + } + + } + } + + private void refreshCurrentView() { + if (getWidth() > 0) { + refreshTag(1, 1, 1); + ITxtTask task = new DrawPrepareTask(); + task.Run(actionLoadListener, readerContext); + } + } + + /** + * @return 获取章节数据 + */ + public List getChapters() { + return readerContext.getChapters(); + } + + + /** + * @param progress 获取指定进度的章节信息 + * @return + */ + public IChapter getChapterFromProgress(int progress) { + List chapters = getChapters(); + if (chapters != null && chapters.size() > 0) { + int paragraphNum = getTxtReaderContext().getParagraphData().getParagraphNum(); + int terminalParagraphIndex = progress * paragraphNum / 100; + + if (terminalParagraphIndex == 0) { + return chapters.get(0); + } + for (IChapter chapter : chapters) { + int startIndex = chapter.getStartParagraphIndex(); + int endIndex = chapter.getEndParagraphIndex(); + ELogger.log("getChapterFromProgress", startIndex + "," + endIndex); + if (terminalParagraphIndex >= startIndex && terminalParagraphIndex < endIndex) { + return chapter; + } + } + } + return null; + } + + private ILoadListener actionLoadListener = new LoadListenerAdapter() { + @Override + public void onSuccess() { + checkMoveState(); + postInvalidate(); + post(new Runnable() { + @Override + public void run() { + onPageProgress(readerContext.getPageData().MidPage()); + } + }); + + } + }; +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/VerticalPageDataPipeline.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/VerticalPageDataPipeline.java new file mode 100644 index 0000000..457422f --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/main/VerticalPageDataPipeline.java @@ -0,0 +1,298 @@ +package com.bifan.txtreaderlib.main; + +import android.graphics.Paint; + +import com.bifan.txtreaderlib.bean.EnChar; +import com.bifan.txtreaderlib.bean.NumChar; +import com.bifan.txtreaderlib.bean.Page; +import com.bifan.txtreaderlib.bean.TxtChar; +import com.bifan.txtreaderlib.bean.TxtLine; +import com.bifan.txtreaderlib.interfaces.IPage; +import com.bifan.txtreaderlib.interfaces.IPageDataPipeline; +import com.bifan.txtreaderlib.interfaces.IParagraphData; +import com.bifan.txtreaderlib.interfaces.ITxtLine; +import com.bifan.txtreaderlib.utils.FormatUtil; +import com.bifan.txtreaderlib.utils.TextBreakUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * created by : bifan-wei + */ +public class VerticalPageDataPipeline implements IPageDataPipeline{ + private TxtReaderContext readerContext; + + public VerticalPageDataPipeline(TxtReaderContext readerContext) { + this.readerContext = readerContext; + } + @Override + public IPage getPageStartFromProgress(int paragraphIndex, int charIndex) { + IParagraphData data = readerContext.getParagraphData(); + PageParam param = readerContext.getPageParam(); + + if (data == null) return null; + + int PageLineNum = param.PageLineNum; + int PageWidth = param.PageWidth; + int LineWidth = (int) param.LineWidth; + float lineHeight = readerContext.getPageParam().LineHeight; + float textPadding = readerContext.getPageParam().TextPadding; + + int CurrentPaIndex = paragraphIndex; + int startIndex = charIndex; + int ParagraphNum = data.getParagraphNum(); + + int paragraphMargin = param.ParagraphMargin; + + if (CurrentPaIndex >= ParagraphNum || CurrentPaIndex < 0) return null;//超过返回null + + IPage page = new Page(); + + //获取页面数据 + while (page.getLineNum() < PageLineNum && CurrentPaIndex < ParagraphNum) { + String paragraphStr = data.getParagraphStr(CurrentPaIndex); + if (paragraphStr != null && paragraphStr.length() > 0) { + List lines = getLineFromParagraphStartOnBeginning(paragraphStr, CurrentPaIndex, startIndex, lineHeight, textPadding, readerContext.getPaintContext().textPaint); + for (ITxtLine line : lines) { + page.addLine(line); + if (page.getLineNum() >= PageLineNum) { + page.setFullPage(true); + break; + } + } + + startIndex = 0; + } + CurrentPaIndex++; + } + + //尝试识别是否需要加段落间距数据 + int textWidth = readerContext.getTxtConfig().textSize; + if (paragraphMargin > 0 && LineWidth > 0 && PageWidth > LineWidth && page.getLineNum() > 0) { + int width = param.PaddingLeft; + List lines = new ArrayList<>(); + for (int i = 0; i < page.getLineNum(); i++) { + ITxtLine line = page.getLine(i); + width = width + LineWidth; + if (width > PageWidth) { + page.setFullPage(true); + if (width - LineWidth + textWidth <= PageWidth) { + lines.add(line); + } + break; + } else { + lines.add(line); + } + if (line.isParagraphEndLine()) { + width = width + paragraphMargin;//说明还有行数据 + } + } + if (width >= PageWidth) { + page.setFullPage(true); + } + page.setLines(lines); + } + + return page; + } + + @Override + public IPage getPageEndToProgress(int paragraphIndex, int charIndex) { + + IParagraphData data = readerContext.getParagraphData(); + PageParam param = readerContext.getPageParam(); + + if(data==null) return null; + + int PageLineNum = param.PageLineNum; + int PageWidth = param.PageWidth; + int LineHeight = (int) param.LineHeight; + int LineWidth = (int) param.LineWidth; + int CurrentPaIndex = paragraphIndex; + int startIndex = charIndex; + int ParagraphNum = data.getParagraphNum(); + float textPadding = readerContext.getPageParam().TextPadding; + + if (charIndex == 0) {//说明上页开始是段落开始位置,段落左移 + CurrentPaIndex--;// + startIndex = 0; + } + if (CurrentPaIndex >= ParagraphNum || CurrentPaIndex < 0) return null;//超过返回null + + IPage page = new Page(); + int paragraphMargin = param.ParagraphMargin; + while (page.getLineNum() < PageLineNum && CurrentPaIndex >= 0) { + String paragraphStr = data.getParagraphStr(CurrentPaIndex); + + if (paragraphStr != null && paragraphStr.length() > 0) { + if (startIndex == 0) {//说明上页开始是段落开始位置 + startIndex = paragraphStr.length(); + } + List lines = getLineFromParagraphOnEnd(paragraphStr, CurrentPaIndex, startIndex, LineHeight, textPadding, readerContext.getPaintContext().textPaint); + if (lines.size() > 0) { + for (int i = lines.size() - 1; i >= 0; i--) { + ITxtLine line = lines.get(i); + page.addLine(line); + if (page.getLineNum() >= PageLineNum) { + page.setFullPage(true); + break; + } + } + } + } + CurrentPaIndex--; + startIndex = 0; + } + + + if (page.HasData()) { + Collections.reverse(page.getLines()); + } + + if (paragraphMargin > 0 && LineHeight > 0 && PageWidth > LineHeight && page.getLineNum() > 0) { + int width = param.PaddingLeft; + int textWidth = readerContext.getTxtConfig().textSize; + List lines = new ArrayList<>(); + for (int i = page.getLineNum() - 1; i >= 0; i--) { + ITxtLine line = page.getLine(i); + if (i == page.getLineNum() - 1) {//底部那个不添加偏移量 + lines.add(line); + width = width + textWidth; + } else { + if (width + LineWidth > PageWidth) { + page.setFullPage(true); + if (width + textWidth <= PageWidth) { + lines.add(line); + } + break; + } else { + lines.add(line); + width = width + LineWidth; + if (line.isParagraphEndLine()) { + width = width + paragraphMargin;//说明还有行数据 + } + } + } + } + if (width >= PageWidth) { + page.setFullPage(true); + } + page.setLines(lines); + if (page.HasData()) { + Collections.reverse(page.getLines()); + } + } + return page; + } + + private List getLineFromParagraphOnEnd(String paragraphData, int paragraphIndex, int endCharIndex, + float lineHeight, float textPadding, Paint paint) { + List lines = new ArrayList<>(); + int startIndex = 0; + int paragraphLength = paragraphData.length(); + if (paragraphLength == 0 || endCharIndex <= 0) { + return lines; + } + endCharIndex = endCharIndex >= paragraphData.length() ? paragraphData.length() : endCharIndex; + + if (endCharIndex > 0) { + String s = paragraphData.substring(startIndex, endCharIndex);//截取要的数据 + while (s.length() > 0) {// is[0] 为个数 is[1] 为是否满一行 + float[] is = TextBreakUtil.BreakTextVertical(s, lineHeight, textPadding, paint); + ITxtLine line; + int num = (int) is[0]; + if (is[1] != 1) {//不满一行 + line = getLineFromString(s, paragraphIndex, startIndex, is); + if (endCharIndex == paragraphLength && s.length() + startIndex >= paragraphLength) { + line.setParagraphEndLine(true); + } + lines.add(line); + return lines; + } + + String lineStr = s.substring(0, num); + line = getLineFromString(lineStr, paragraphIndex, startIndex, is); + startIndex = startIndex + num; + if (line != null) { + lines.add(line); + } + s = s.substring(lineStr.length()); + } + } + return lines; + } + + + /** + * @param paragraphData + * @param paragraphIndex + * @param startCharIndex + * @param lineHeight + * @param textPadding + * @param paint + * @return + */ + private List getLineFromParagraphStartOnBeginning(String paragraphData, int paragraphIndex, int startCharIndex, + float lineHeight, float textPadding, Paint paint) { + List lines = new ArrayList<>(); + int startIndex = startCharIndex; + startIndex = startIndex < 0 ? 0 : startIndex; + int paragraphLength = paragraphData.length(); + + if ( startIndex >= paragraphLength) { + return lines; + } + if ( paragraphData.length() > 0) { + String s = paragraphData.substring(startIndex);//截取要的数据 + while (s.length() > 0) {// is[0] 为个数 is[1] 为是否满一行 + float[] is = TextBreakUtil.BreakTextVertical(s, lineHeight, textPadding, paint); + ITxtLine line; + if (is[1] != 1) {//不满一行 + line = getLineFromString(s, paragraphIndex, startIndex, is); + if (s.length() + startIndex >= paragraphLength) { + line.setParagraphEndLine(true); + } + lines.add(line); + break; + } + int num = (int) is[0]; + String lineStr = s.substring(0, num); + line = getLineFromString(lineStr, paragraphIndex, startIndex, is); + startIndex = startIndex + num; + if (line != null) { + lines.add(line); + } + s = s.substring(num); + } + } + return lines; + } + private ITxtLine getLineFromString(String str, int ParagraphIndex, int charIndex, float[] is) { + ITxtLine line = null; + int index = charIndex; + int cIndex = 2;//找到字符宽度 + if (str != null && str.length() > 0) { + line = new TxtLine(); + + char[] cs = str.toCharArray(); + for (char c : cs) { + TxtChar Char; + if (FormatUtil.isDigital(c)) { + Char = new NumChar(c); + } else if (FormatUtil.isLetter(c)) { + Char = new EnChar(c); + } else { + Char = new TxtChar(c); + } + Char.ParagraphIndex = ParagraphIndex; + Char.CharIndex = index++; + Char.CharWidth = is[cIndex++]; + line.addChar(Char); + } + + } + return line; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/BitmapProduceTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/BitmapProduceTask.java new file mode 100644 index 0000000..7cfa0dd --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/BitmapProduceTask.java @@ -0,0 +1,62 @@ +package com.bifan.txtreaderlib.tasks; + +import android.graphics.Bitmap; + +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.IPage; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.main.TxtReaderContext; +import com.bifan.txtreaderlib.utils.ELogger; +import com.bifan.txtreaderlib.utils.TxtBitmapUtil; + +/** + * Created by bifan-wei + * on 2017/11/27. + */ + +public class BitmapProduceTask implements ITxtTask { + private String tag = "BitmapProduceTask"; + + @Override + public void Run(ILoadListener callBack, TxtReaderContext readerContext) { + ELogger.log(tag, "produce bitmap"); + callBack.onMessage("start to produce bitmap"); + + int[] rs = readerContext.getPageData().refreshTag; + IPage[] pages = readerContext.getPageData().getPages(); + Bitmap[] bitmaps = readerContext.getBitmapData().getPages(); + + int index = 0; + for (int neeRefresh : rs) { + IPage page = pages[index]; + if (neeRefresh == 1) { + ELogger.log(tag, "page " + index + " neeRefresh"); + Bitmap bitmap ; + if(readerContext.getTxtConfig().VerticalPageMode){ + bitmap = TxtBitmapUtil.createVerticalPage( + readerContext.getBitmapData().getBgBitmap(), + readerContext.getPaintContext(), + readerContext.getPageParam(), + readerContext.getTxtConfig(), page); + }else { + bitmap = TxtBitmapUtil.createHorizontalPage( + readerContext.getBitmapData().getBgBitmap(), + readerContext.getPaintContext(), + readerContext.getPageParam(), + readerContext.getTxtConfig(), page); + } + + bitmaps[index] = bitmap; + } else { + ELogger.log(tag, "page " + index + " no neeRefresh"); + //no neeRefresh ,do not change + } + index++; + } + ELogger.log(tag, "already done ,call back success"); + callBack.onMessage("already done ,call back success"); + readerContext.setInitDone(true); + //already done ,call back success + callBack.onSuccess(); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DestroyableTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DestroyableTask.java new file mode 100644 index 0000000..5da42bc --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DestroyableTask.java @@ -0,0 +1,32 @@ +package com.bifan.txtreaderlib.tasks; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * author: bifan-wei + * 2021/11/13. + */ +public class DestroyableTask { + private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); + private final IShutdownCall mShutdownCall; + + public DestroyableTask(IShutdownCall shutdownCall) { + this.mShutdownCall = shutdownCall; + } + + public void excuse(Runnable runnable) { + mExecutorService.submit(runnable); + } + + public void destroy() { + if (mShutdownCall != null) { + mShutdownCall.clearIfNeedBeforeShutdown(); + } + mExecutorService.shutdownNow(); + } + + public interface IShutdownCall { + void clearIfNeedBeforeShutdown(); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DrawPrepareTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DrawPrepareTask.java new file mode 100644 index 0000000..391784d --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/DrawPrepareTask.java @@ -0,0 +1,34 @@ +package com.bifan.txtreaderlib.tasks; + +import android.content.Context; +import android.graphics.Color; + +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.main.PaintContext; +import com.bifan.txtreaderlib.main.TxtConfig; +import com.bifan.txtreaderlib.main.TxtReaderContext; +import com.bifan.txtreaderlib.utils.ELogger; + +/** + * Created by bifan-wei + * on 2017/11/27. + */ + +public class DrawPrepareTask implements ITxtTask { + private String tag = "DrawPrepareTask"; + + @Override + public void Run(ILoadListener callBack, TxtReaderContext readerContext) { + callBack.onMessage("start do DrawPrepare"); + ELogger.log(tag, "do DrawPrepare"); + initPainContext(readerContext.context,readerContext.getPaintContext(), readerContext.getTxtConfig()); + readerContext.getPaintContext().textPaint.setColor(Color.WHITE); + ITxtTask txtTask = new BitmapProduceTask(); + txtTask.Run(callBack, readerContext); + } + + private void initPainContext(Context context,PaintContext paintContext, TxtConfig txtConfig) { + TxtConfigInitTask.initPainContext(context,paintContext, txtConfig); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/FileDataLoadTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/FileDataLoadTask.java new file mode 100644 index 0000000..ae81131 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/FileDataLoadTask.java @@ -0,0 +1,156 @@ +package com.bifan.txtreaderlib.tasks; + +import com.bifan.txtreaderlib.bean.Chapter; +import com.bifan.txtreaderlib.bean.TxtMsg; +import com.bifan.txtreaderlib.interfaces.IChapter; +import com.bifan.txtreaderlib.interfaces.IChapterMatcher; +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.IParagraphData; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.main.ParagraphData; +import com.bifan.txtreaderlib.main.TxtReaderContext; +import com.bifan.txtreaderlib.utils.ELogger; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author bifan-wei + * @description + * @time 2021/11/13 16:52 + */ + +public class FileDataLoadTask implements ITxtTask { + private static final String ChapterPatternStr = "(^.{0,3}\\s*第)(.{1,9})[章节卷集部篇回](\\s*)"; + private String tag = "FileDataLoadTask"; + private IChapterMatcher chapterMatcher; + private boolean stop = false; + + public void onStop() { + stop = true; + } + + @Override + public void Run(ILoadListener callBack, TxtReaderContext readerContext) { + stop = false; + IParagraphData paragraphData = new ParagraphData(); + chapterMatcher = readerContext.getChapterMatcher(); + List chapter = new ArrayList<>(); + callBack.onMessage("start read file data"); + Boolean readSuccess = ReadData(readerContext.getFileMsg().FilePath, readerContext.getFileMsg().FileCode, paragraphData, chapter); + if (readSuccess) { + ELogger.log(tag, "ReadData readSuccess"); + callBack.onMessage(" read file data success"); + readerContext.setParagraphData(paragraphData); + readerContext.setChapters(chapter); + ITxtTask txtTask = new TxtConfigInitTask(); + txtTask.Run(callBack, readerContext); + + } else { + callBack.onFail(TxtMsg.InitError); + callBack.onMessage("ReadData fail on FileDataLoadTask"); + } + stop = true; + } + + private Boolean ReadData(String filePath, String Charset, IParagraphData paragraphData, List chapters) { + File file = new File(filePath); + BufferedReader bufferedReader = null; + ELogger.log(tag, "start to ReadData"); + ELogger.log(tag, "--file Charset:" + Charset); + try { + bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charset)); + try { + String data; + int index = 0; + int chapterIndex = 0; + while (!stop && (data = bufferedReader.readLine()) != null) { + if (data.length() > 0) { + IChapter chapter = compileChapter(data, paragraphData.getCharNum(), index, chapterIndex); + paragraphData.addParagraph(data); + if (chapter != null) { + chapterIndex++; + chapters.add(chapter); + } + index++; + } + } + initChapterEndIndex(chapters, paragraphData.getParagraphNum()); + return !stop; + } catch (IOException e) { + ELogger.log(tag, "IOException:" + e.toString()); + } + } catch (UnsupportedEncodingException e) { + ELogger.log(tag, "UnsupportedEncodingException:" + e.toString()); + } catch (FileNotFoundException e) { + ELogger.log(tag, "FileNotFoundException:" + e.toString()); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return false; + } + + + private void initChapterEndIndex(List chapters, int paragraphNum) { + if (chapters != null && chapters.size() > 0) { + for (int i = 0, sum = chapters.size(); i < sum; i++) { + int nextIndex = i + 1; + IChapter chapter = chapters.get(i); + if (nextIndex < sum) { + int startIndex = chapter.getStartParagraphIndex(); + int endIndex = chapters.get(nextIndex).getEndParagraphIndex() - 1; + if (endIndex < startIndex) { + endIndex = startIndex; + } + chapter.setEndParagraphIndex(endIndex); + } else { + int endIndex = paragraphNum - 1; + endIndex = Math.max(endIndex, 0); + chapter.setEndParagraphIndex(endIndex); + } + } + } + } + + + + /** + * @param data 文本数据 + * @param chapterStartIndex 开始字符在全文中的位置 + * @param ParagraphIndex 段落位置 + * @param chapterIndex 章节位置 + * @return 没有识别到章节数据返回null + */ + private IChapter compileChapter(String data, int chapterStartIndex, int ParagraphIndex, int chapterIndex) { + if (chapterMatcher == null) { + if (data.trim().startsWith("第") || data.contains("第")) { + Pattern p = Pattern.compile(ChapterPatternStr); + Matcher matcher = p.matcher(data); + if (matcher.find()) { + int startIndex = 0; + int endIndex = data.length(); + return new Chapter(chapterStartIndex, chapterIndex, data, ParagraphIndex, ParagraphIndex, startIndex, endIndex); + } + } + return null; + } else { + return chapterMatcher.match(data, ParagraphIndex); + } + + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TextLoader.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TextLoader.java new file mode 100644 index 0000000..883ce6e --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TextLoader.java @@ -0,0 +1,33 @@ +package com.bifan.txtreaderlib.tasks; + +import com.bifan.txtreaderlib.interfaces.IChapter; +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.IParagraphData; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.main.ParagraphData; +import com.bifan.txtreaderlib.main.TxtReaderContext; +import com.bifan.txtreaderlib.utils.ELogger; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by bifan-wei + * on 2018/1/28. + */ + +public class TextLoader { + private final String tag = "FileDataLoadTask"; + + public void load(String text, TxtReaderContext readerContext, ILoadListener callBack) { + IParagraphData paragraphData = new ParagraphData(); + List chapter = new ArrayList<>(); + callBack.onMessage("start read text"); + ELogger.log(tag, "start read text"); + paragraphData.addParagraph(text + ""); + readerContext.setParagraphData(paragraphData); + readerContext.setChapters(chapter); + ITxtTask txtTask = new TxtConfigInitTask(); + txtTask.Run(callBack, readerContext); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtConfigInitTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtConfigInitTask.java new file mode 100644 index 0000000..468d4ba --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtConfigInitTask.java @@ -0,0 +1,134 @@ +package com.bifan.txtreaderlib.tasks; + +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Paint; +import android.graphics.Typeface; + +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.main.PageParam; +import com.bifan.txtreaderlib.main.PaintContext; +import com.bifan.txtreaderlib.main.TxtConfig; +import com.bifan.txtreaderlib.main.TxtReaderContext; +import com.bifan.txtreaderlib.utils.ELogger; +import com.bifan.txtreaderlib.utils.TxtBitmapUtil; + +/** + * Created by HP on 2017/11/26. + */ + +public class TxtConfigInitTask implements ITxtTask { + private String tag = "TxtConfigInitTask"; + + @Override + public void Run(ILoadListener callBack, TxtReaderContext readerContext) { + ELogger.log(tag, "do TxtConfigInit"); + callBack.onMessage("start init settings in TxtConfigInitTask"); + + TxtConfig config = readerContext.getTxtConfig(); + initTxtConfig(readerContext, config); + + //if not null ,do recycle + if (readerContext.getBitmapData().getBgBitmap() != null) { + readerContext.getBitmapData().getBgBitmap().recycle(); + } + + int width = readerContext.getPageParam().PageWidth; + int height = readerContext.getPageParam().PageHeight; + + //init the bg bitmap + readerContext.getBitmapData().setBgBitmap(TxtBitmapUtil.createBitmap(config.backgroundColor, width, height)); + //initPageParam + initPageParam(readerContext); + //start draw prepare + + //get preRead Progress + int startParagraphIndex = 0; + int startCharIndex = 0; + + if (readerContext.getFileMsg() != null) { + startParagraphIndex = readerContext.getFileMsg().PreParagraphIndex; + startCharIndex = readerContext.getFileMsg().PreCharIndex; + } + //init Context + initPainContext(readerContext.context, readerContext.getPaintContext(), readerContext.getTxtConfig()); + + ITxtTask txtTask = new TxtPageLoadTask(startParagraphIndex, startCharIndex); + txtTask.Run(callBack, readerContext); + } + + /** + * get pre or default config + * + * @param readerContext + * @param config + */ + private void initTxtConfig(TxtReaderContext readerContext, TxtConfig config) { + config.showNote = TxtConfig.getIsShowNote(readerContext.context); + config.canPressSelect = TxtConfig.getCanPressSelect(readerContext.context); + config.textColor = TxtConfig.getTextColor(readerContext.context); + config.textSize = TxtConfig.getTextSize(readerContext.context); + config.backgroundColor = TxtConfig.getBackgroundColor(readerContext.context); + config.NoteColor = TxtConfig.getNoteTextColor(readerContext.context); + config.SelectTextColor = TxtConfig.getSelectTextColor(readerContext.context); + config.SliderColor = TxtConfig.getSliderColor(readerContext.context); + config.Bold = TxtConfig.isBold(readerContext.context); + config.Page_Switch_Mode = TxtConfig.getPageSwitchMode(readerContext.context); + config.ShowSpecialChar = TxtConfig.IsShowSpecialChar(readerContext.context); + config.VerticalPageMode = TxtConfig.IsOnVerticalPageMode(readerContext.context); + config.PageSwitchDuration = TxtConfig.getPageSwitchDuration(readerContext.context); + } + + /** + * @param paintContext + * @param txtConfig + */ + public static void initPainContext(Context context, PaintContext paintContext, TxtConfig txtConfig) { + paintContext.textPaint.setTextSize(txtConfig.textSize); + paintContext.textPaint.setFakeBoldText(txtConfig.Bold); + paintContext.textPaint.setTextAlign(Paint.Align.LEFT); + paintContext.textPaint.setColor(txtConfig.textColor); + paintContext.textPaint.setAntiAlias(true); + paintContext.notePaint.setTextSize(txtConfig.textSize); + paintContext.notePaint.setColor(txtConfig.NoteColor); + paintContext.notePaint.setTextAlign(Paint.Align.LEFT); + paintContext.notePaint.setAntiAlias(true); + paintContext.selectTextPaint.setTextSize(txtConfig.textSize); + paintContext.selectTextPaint.setColor(txtConfig.SelectTextColor); + paintContext.selectTextPaint.setTextAlign(Paint.Align.LEFT); + paintContext.selectTextPaint.setAntiAlias(true); + paintContext.sliderPaint.setColor(txtConfig.SliderColor); + paintContext.sliderPaint.setAntiAlias(true); + paintContext.textPaint.setFakeBoldText(txtConfig.Bold); + paintContext.textPaint.setTypeface(null); + if (txtConfig.VerticalPageMode) { + AssetManager mgr = context.getAssets(); + Typeface tf = Typeface.createFromAsset(mgr, "fonts/text_style.TTF"); + paintContext.textPaint.setTypeface(tf); + } + } + + private void initPageParam(TxtReaderContext readerContext) { + PageParam param = readerContext.getPageParam(); + int lineHeight = readerContext.getTxtConfig().textSize + param.LinePadding; + param.LineHeight = lineHeight; + if (!readerContext.getTxtConfig().VerticalPageMode) { + param.LineWidth = param.PageWidth - param.PaddingLeft - param.PaddingRight; + param.LineHeight = lineHeight; + param.PageLineNum = (param.PageHeight - param.PaddingTop - param.PaddingBottom - readerContext.getTxtConfig().textSize - 2) / lineHeight + 1; + } else { + param.LineWidth = lineHeight; + param.LineHeight = param.PageHeight - param.PaddingTop - param.PaddingBottom; + param.PageLineNum = (param.PageWidth - param.PaddingLeft - param.PaddingRight - readerContext.getTxtConfig().textSize - 2) / lineHeight + 1; + } + param.PaddingLeft = TxtConfig.Page_PaddingLeft; + param.LinePadding = TxtConfig.Page_LinePadding; + param.PaddingRight = TxtConfig.Page_PaddingRight; + param.PaddingTop = TxtConfig.Page_PaddingTop; + param.PaddingBottom = TxtConfig.Page_PaddingBottom; + param.ParagraphMargin = TxtConfig.Page_Paragraph_margin; + } + + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtFileLoader.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtFileLoader.java new file mode 100644 index 0000000..1668695 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtFileLoader.java @@ -0,0 +1,82 @@ +package com.bifan.txtreaderlib.tasks; + +import com.bifan.txtreaderlib.bean.FileReadRecordBean; +import com.bifan.txtreaderlib.bean.TxtFileMsg; +import com.bifan.txtreaderlib.bean.TxtMsg; +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.main.FileReadRecordDB; +import com.bifan.txtreaderlib.main.TxtReaderContext; +import com.bifan.txtreaderlib.utils.ELogger; +import com.bifan.txtreaderlib.utils.FileCharsetDetector; +import com.bifan.txtreaderlib.utils.FileUtil; + +import java.io.File; + + +/** + * @description + * @author bifan-wei + * @time 2021/11/13 16:53 + */ + +public class TxtFileLoader { + private String tag = "TxtFileLoader"; + private FileDataLoadTask mFileDataLoadTask; + + public void load(String filePath, TxtReaderContext readerContext, ILoadListener loadListener) { + load(filePath, null, readerContext, loadListener); + } + + public void load(String filePath, String fileName, TxtReaderContext readerContext, ILoadListener loadListener) { + onStop(); + if (!FileUtil.FileExist(filePath)) { + loadListener.onFail(TxtMsg.FileNoExist); + return; + } + loadListener.onMessage("initFile start"); + initFile(filePath, fileName, readerContext); + ELogger.log(tag, "initFile done"); + loadListener.onMessage("initFile done"); + mFileDataLoadTask = new FileDataLoadTask(); + mFileDataLoadTask.Run(loadListener, readerContext); + + } + + private void initFile(String filePath, String fileName, TxtReaderContext readerContext) { + File file = new File(filePath); + TxtFileMsg fileMsg = new TxtFileMsg(); + fileMsg.FileSize = file.getTotalSpace(); + fileMsg.FilePath = filePath; + fileMsg.FileCode = new FileCharsetDetector().getCharset(new File(filePath)); + + fileMsg.CurrentParagraphIndex = 0; + fileMsg.PreParagraphIndex = 0; + fileMsg.PreCharIndex = 0; + if (fileName == null || fileName.trim().length() == 0) { + fileName = file.getName(); + } + fileMsg.FileName = fileName; + + //获取之前的播放进度记录 + FileReadRecordDB readRecordDB = new FileReadRecordDB(readerContext.getContext()); + readRecordDB.createTable(); + try { + FileReadRecordBean r = readRecordDB.getRecordByHashName(FileUtil.getMD5Checksum(filePath)); + if (r != null) { + fileMsg.PreParagraphIndex = r.paragraphIndex; + fileMsg.PreCharIndex = r.chartIndex; + } + } catch (Exception e) { + e.printStackTrace(); + } + readRecordDB.createTable(); + readerContext.setFileMsg(fileMsg); + } + + public void onStop() { + if (mFileDataLoadTask != null) { + mFileDataLoadTask.onStop(); + } + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtPageLoadTask.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtPageLoadTask.java new file mode 100644 index 0000000..84d7dc6 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/tasks/TxtPageLoadTask.java @@ -0,0 +1,82 @@ +package com.bifan.txtreaderlib.tasks; + +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.IPage; +import com.bifan.txtreaderlib.interfaces.ITxtTask; +import com.bifan.txtreaderlib.main.TxtReaderContext; +import com.bifan.txtreaderlib.utils.ELogger; + +/** + * Created by bifan-wei + * on 2017/11/27. + */ + +public class TxtPageLoadTask implements ITxtTask { + private String tag = "TxtPageLoadTask"; + private final int startParagraphIndex; + private final int startCharIndex; + + public TxtPageLoadTask(int startParagraphIndex, int startCharIndex) { + this.startParagraphIndex = startParagraphIndex; + this.startCharIndex = startCharIndex; + } + + @Override + public void Run(ILoadListener callBack, TxtReaderContext readerContext) { + callBack.onMessage("start load pageData"); + + IPage firstPage = null; + IPage midPage; + IPage nextPage = null; + + midPage = readerContext.getPageDataPipeline().getPageStartFromProgress(startParagraphIndex, startCharIndex); + + if (midPage != null && midPage.HasData()) { + firstPage = readerContext.getPageDataPipeline().getPageEndToProgress(midPage.getFirstChar().ParagraphIndex, midPage.getFirstChar().CharIndex - 1); + } + + if (midPage != null && midPage.isFullPage()) { + nextPage = readerContext.getPageDataPipeline().getPageStartFromProgress(midPage.getLastChar().ParagraphIndex, midPage.getLastChar().CharIndex + 1); + } + + ELogger.log(tag, "获取进度数据完成"); + ELogger.log(tag, "startParagraphIndex/ startCharIndex+" + startParagraphIndex + "/" + startCharIndex); + + //show the data + if (firstPage != null) { + ELogger.log(tag, "firstPage:" + firstPage.toString()); + if (!firstPage.HasData()) { + firstPage = null; + } + } else { + ELogger.log(tag, "firstPage is null"); + } + + if (midPage != null) { + ELogger.log(tag, "midPage:" + midPage.toString()); + if (!midPage.HasData()) { + midPage = null; + } + } else { + ELogger.log(tag, "midPage is null"); + } + + if (nextPage != null) { + ELogger.log(tag, "nextPage:" + nextPage.toString()); + if (!nextPage.HasData()) { + nextPage = null; + } + } else { + ELogger.log(tag, "nextPage is null"); + } + + readerContext.getPageData().setFirstPage(firstPage); + readerContext.getPageData().setMidPage(midPage); + readerContext.getPageData().setLastPage(nextPage); + + ITxtTask txtTask = new DrawPrepareTask(); + txtTask.Run(callBack, readerContext); + + } + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChapterList.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChapterList.java new file mode 100644 index 0000000..c0aaddc --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChapterList.java @@ -0,0 +1,157 @@ +package com.bifan.txtreaderlib.ui; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.bifan.txtreaderlib.R; +import com.bifan.txtreaderlib.interfaces.IChapter; + +import java.util.List; + +/** + * Created by bifan-wei + * on 2017/12/12. + */ + +public class ChapterList extends PopupWindow { + private Context mContext; + private ListView mRootView; + private MyAdapter mAdapter; + private List mChapters; + private int CurrentIndex = -1; + private int AllCharNum ; + private int ViewHeight; + + public ChapterList(Context mContext, int ViewHeight, List mChapters, int allCharNum) { + super(mContext); + this.mContext = mContext; + this.ViewHeight = ViewHeight; + this.mChapters = mChapters; + this.AllCharNum = allCharNum; + initRootView(); + } + + public int getAllCharNum() { + return AllCharNum; + } + + public void setCurrentIndex(int currentIndex) { + CurrentIndex = currentIndex; + } + + public void notifyDataSetChanged() { + mAdapter.notifyDataSetChanged(); + } + + public ListView getListView() { + return mRootView; + } + + public BaseAdapter getAdapter() { + return mAdapter; + } + + protected void initRootView() { + WindowManager m = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics metrics = new DisplayMetrics(); + m.getDefaultDisplay().getMetrics(metrics); + + int ViewHeight = this.ViewHeight; + int ViewWidth = metrics.widthPixels; + mRootView = new ListView(mContext); + ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + mRootView.setLayoutParams(params); + this.setContentView(mRootView); + this.setWidth(ViewWidth); + this.setHeight(ViewHeight); + this.setFocusable(true); + this.setOutsideTouchable(true); + this.setAnimationStyle(R.style.HwTxtChapterMenuAnimation); + this.setBackgroundDrawable(new ColorDrawable(Color.parseColor("#1c1c1a"))); + mAdapter = new MyAdapter(); + mRootView.setAdapter(mAdapter); + } + + + private class MyAdapter extends BaseAdapter { + @Override + public int getCount() { + return mChapters == null ? 0 : mChapters.size(); + } + + @Override + public Object getItem(int i) { + return mChapters.get(i); + } + + @Override + public long getItemId(int i) { + return 0; + } + + private final int ColorNegative = Color.parseColor("#aeaca2"); + private final int ColorSelected = Color.parseColor("#fa4613"); + @Override + public View getView(int i, View view, ViewGroup viewGroup) { + Holder holder; + if (view == null) { + holder = new Holder(); + view = LayoutInflater.from(mContext).inflate(R.layout.adapter_chapterlist, null); + holder.index = view.findViewById(R.id.adapter_chapterList_index); + holder.title = view.findViewById(R.id.adapter_chapterList_title); + holder.progress = view.findViewById(R.id.adapter_chapterList_progress); + view.setTag(holder); + } else { + holder = (Holder) view.getTag(); + } + IChapter chapter = mChapters.get(i); + if (CurrentIndex == i) { + holder.title.setTextColor(ColorSelected); + holder.progress.setTextColor(Color.WHITE); + holder.progress.setText("当前"); + } else { + holder.title.setTextColor(Color.WHITE); + holder.progress.setTextColor(ColorNegative); + float p = 0; + if (AllCharNum > 0) { + p = (float) chapter.getStartIndex() / (float) AllCharNum; + if (p > 1) { + p = 1; + } + } + holder.progress.setText((int) (p * 100) + "%"); + } + + holder.index.setText(i + 1 + ""); + holder.title.setText((chapter.getTitle() + "").trim()); + return view; + } + + private class Holder { + TextView index; + TextView title; + TextView progress; + } + } + + public void onDestroy() { + mContext = null; + mRootView = null; + mAdapter = null; + if (mChapters != null) { + mChapters.clear(); + mChapters = null; + } + } + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChatWithGptActivity.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChatWithGptActivity.java new file mode 100644 index 0000000..2da2636 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/ChatWithGptActivity.java @@ -0,0 +1,237 @@ +package com.bifan.txtreaderlib.ui; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bifan.txtreaderlib.R; +import com.bifan.txtreaderlib.adapter.MessageAdapter; +import com.bifan.txtreaderlib.bean.ChatMessage_bmob; +import com.bifan.txtreaderlib.bean.ChatRequest; +import com.bifan.txtreaderlib.bean.ChatResponse; +import com.bifan.txtreaderlib.bean.Message; +import com.bifan.txtreaderlib.interfaces.ChatGPTService; + +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import cn.bmob.v3.Bmob; +import cn.bmob.v3.BmobQuery; +import cn.bmob.v3.exception.BmobException; +import cn.bmob.v3.listener.FindListener; +import cn.bmob.v3.listener.SaveListener; +import okhttp3.OkHttpClient; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class ChatWithGptActivity extends AppCompatActivity { + private RecyclerView recyclerView; + private MessageAdapter messageAdapter; + private EditText editTextMessage; + private TextView chatbot_title; + private Button buttonSend; + private String bookName = ""; + private String userid=""; + private ArrayList messageList = new ArrayList<>(); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_chatwithgpt); + Bmob.initialize(this, "8f9f1d1ea520b0ce4f84a6fa83a5f754"); + if (getIntent() != null && getIntent().hasExtra("book_name")) { + bookName = getIntent().getStringExtra("book_name"); + } + SharedPreferences sharedPreferences = getSharedPreferences("userinf", Context.MODE_PRIVATE); + String userIdFromPrefs = sharedPreferences.getString("user_id", ""); + userid = userIdFromPrefs != null ? userIdFromPrefs : ""; + + recyclerView = findViewById(R.id.chatRecyclerView); + editTextMessage = findViewById(R.id.inputEditText); + buttonSend = findViewById(R.id.sendButton); + chatbot_title=findViewById(R.id.chatbot_title); + messageAdapter = new MessageAdapter(messageList); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setAdapter(messageAdapter); + init(); + buttonSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String text = editTextMessage.getText().toString(); + if (!text.isEmpty()) { + messageList.add(new Message(text, 1)); // User message + messageAdapter.notifyItemInserted(messageList.size() - 1); + recyclerView.scrollToPosition(messageList.size() - 1); + editTextMessage.setText(""); + sendMessageToChatGPT(text); + ChatMessage_bmob chatMessage_bmob=new ChatMessage_bmob(text,1,bookName,userid); + chatMessage_bmob.save(new SaveListener() { + @Override + public void done(String objectId,BmobException e) { + if(e==null){ +// toast("添加数据成功,返回objectId为:"+objectId); + }else{ + //toast("创建数据失败:" + e.getMessage()); + } + } + }); + } + } + }); + } + private void init(){ + chatbot_title.setText(bookName); + BmobQuery bmobQuery = new BmobQuery<>(); + bmobQuery.addWhereEqualTo("relatedBook", bookName); + bmobQuery.addWhereEqualTo("userid", userid); + bmobQuery.findObjects(new FindListener() { + @Override + public void done(List messages, BmobException e) { + if (e == null && messages != null) { + if (!messages.isEmpty()) { + for (ChatMessage_bmob item : messages) { + if (item.getContent() == null) { + String content = "出现未知错误!"; + messageList.add(new Message(content, item.getIsuser())); + } else { + messageList.add(new Message(item.getContent(), item.getIsuser())); + } + } + } else { + // 如果没有消息,添加一条 bot 的欢迎消息 + String welcomeMessage = "你好!我是你的阅读助手。我可以帮助你解答关于书籍的各种问题,比如内容概述、作者信息、主要主题和角色分析等。请随时向我提问,现在,你有什么关于《" + bookName + "》的问题吗?"; + messageList.add(new Message(welcomeMessage, 0)); // 假设 0 表示系统或助手消息 + ChatMessage_bmob chatMessage_bmob=new ChatMessage_bmob(welcomeMessage,0,bookName,userid); + chatMessage_bmob.save(new SaveListener() { + @Override + public void done(String objectId,BmobException e) { + if(e==null){ +// toast("添加数据成功,返回objectId为:"+objectId); + }else{ + //toast("创建数据失败:" + e.getMessage()); + } + } + }); + + } + messageAdapter.notifyDataSetChanged(); + } else { + Log.e("BookInfo", "Error loading book info: " + (e != null ? e.getMessage() : "")); + } + } + }); + } + + /** + * 从数据库加载历史对话并构建消息列表 + */ + private List loadMessagesFromDatabase(String bookName, String userId) { + List messages = new ArrayList<>(); + BmobQuery bmobQuery = new BmobQuery<>(); + bmobQuery.addWhereEqualTo("relatedBook", bookName); + bmobQuery.addWhereEqualTo("userid", userId); + bmobQuery.order("createdAt"); + bmobQuery.setLimit(10); //设置上下文最多10条历史对话 + bmobQuery.findObjects(new FindListener() { + @Override + public void done(List chatMessages, BmobException e) { + if (e == null && chatMessages != null && !chatMessages.isEmpty()) { + for (ChatMessage_bmob item : chatMessages) { + // 根据消息来源判断角色,假设 0 表示用户,1 表示助手 + String role = item.getIsuser() == 0 ? "user" : "assistant"; + messages.add(new ChatRequest.Message(role, item.getContent())); + Log.d("test",item.getContent()); + } + } else { + Log.e("BookInfo", "Error loading book info: " + (e != null ? e.getMessage() : "No error details.")); + } + } + }); + return messages; + } + + private void sendMessageToChatGPT(String messageText) { + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(60, TimeUnit.SECONDS) + .readTimeout(60, TimeUnit.SECONDS) + .writeTimeout(60, TimeUnit.SECONDS) + .build(); + Retrofit retrofit = new Retrofit.Builder() + .client(client) + .baseUrl("https://api.openai-ch.top/") + .addConverterFactory(GsonConverterFactory.create()) + .build(); + ChatGPTService service = retrofit.create(ChatGPTService.class); + List messages = new ArrayList<>(); + String prompt = "你好!我是你的阅读助手。我可以帮助你解答关于书籍的各种问题,比如内容概述、作者信息、主要主题和角色分析等。请随时向我提问,我会用中文简体来回答你。现在,你有什么关于《" + bookName + "》的问题吗?"; + messages.add(new ChatRequest.Message("system", prompt)); + +// messages.add(new ChatRequest.Message("system", "You are a reading assistant. The user will ask you questions about the book '" + bookName + "'.")); + messages.addAll(loadMessagesFromDatabase(bookName, userid)); + messages.add(new ChatRequest.Message("user", messageText)); // 添加用户消息 + + ChatRequest request = new ChatRequest(messages); // 使用消息列表创建请求 + request.setModel("gpt-4-turbo"); // 根据需要选择不同的模型 + request.setMaxTokens(200); // 设置最大 token 数,根据需要调整 + request.setTemperature(0.5); // 设置适当的温度 + service.postMessage(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + ChatResponse chatResponse = response.body(); + if (chatResponse.choices != null && !chatResponse.choices.isEmpty()) { + ChatResponse.Message message = chatResponse.choices.get(0).message; + String reply = message.content; // 这是从 GPT-3.5 响应中提取的内容 + runOnUiThread(() -> { + messageList.add(new Message(reply, 0)); + messageAdapter.notifyItemInserted(messageList.size() - 1); + recyclerView.scrollToPosition(messageList.size() - 1); + ChatMessage_bmob chatMessage_bmob = new ChatMessage_bmob(reply, 0, bookName, userid); + chatMessage_bmob.save(new SaveListener() { + @Override + public void done(String objectId, BmobException e) { + if (e == null) { + // toast("添加数据成功,返回objectId为:" + objectId); + } else { + // toast("创建数据失败:" + e.getMessage()); + } + } + }); + }); + } + } else { + try { + String errorBody = response.errorBody() != null ? response.errorBody().string() : "Unknown error"; + Log.e("ChatGPT", "Error: " + errorBody); + } catch (IOException e) { + Log.e("ChatGPT", "Error reading error body", e); + } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e("ChatGPT", "Failure: " + t.getMessage()); + } + }); + } + + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/CircleView.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/CircleView.java new file mode 100644 index 0000000..fa93143 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/CircleView.java @@ -0,0 +1,74 @@ +package com.bifan.txtreaderlib.ui; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +import com.bifan.txtreaderlib.R; + + + +public class CircleView extends View { + private Paint mPaint; + private float Radius = 0.0F; + private int CoverColor = Color.parseColor("#66ffffff"); + + public CircleView(Context context) { + super(context); + this.init(); + } + + public CircleView(Context context, AttributeSet attrs) { + super(context, attrs); + getAttrs(attrs); + this.init(); + } + + public CircleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + getAttrs(attrs); + this.init(); + } + + private void getAttrs(AttributeSet attrs) { + TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CircleView); + Radius = ta.getDimension(R.styleable.CircleView_CircleRadius, Radius); + CoverColor = ta.getColor(R.styleable.CircleView_BgColor, CoverColor); + ta.recycle(); + } + + private void init() { + this.mPaint = new Paint(); + this.mPaint.setAntiAlias(true); + this.mPaint.setColor(this.CoverColor); + + } + + public void setCoverColor(int CoverColor) { + this.CoverColor = CoverColor; + this.mPaint.setColor(CoverColor); + this.postInvalidate(); + } + + @SuppressLint({"DrawAllocation"}) + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + int vWidth = this.getWidth(); + int vHeight = this.getHeight(); + int radius = Math.max(vHeight, vWidth) / 2; + if (Radius > 0) { + radius = (int) Radius; + } + float Cx = (float) (vWidth / 2); + float Cy = (float) (vHeight / 2); + this.mPaint.setColor(Color.WHITE); + canvas.drawCircle(Cx, Cy, (float) radius, this.mPaint); + this.mPaint.setColor(CoverColor); + canvas.drawCircle(Cx, Cy, (float) radius-3, this.mPaint); + } +} \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/HwTxtPlayActivity.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/HwTxtPlayActivity.java new file mode 100644 index 0000000..d8c6efa --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/ui/HwTxtPlayActivity.java @@ -0,0 +1,808 @@ +package com.bifan.txtreaderlib.ui; + +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; + +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.res.ResourcesCompat; + +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.bifan.txtreaderlib.R; +import com.bifan.txtreaderlib.bean.TxtChar; +import com.bifan.txtreaderlib.bean.TxtMsg; +import com.bifan.txtreaderlib.interfaces.ICenterAreaClickListener; +import com.bifan.txtreaderlib.interfaces.IChapter; +import com.bifan.txtreaderlib.interfaces.ILoadListener; +import com.bifan.txtreaderlib.interfaces.IPageChangeListener; +import com.bifan.txtreaderlib.interfaces.ISliderListener; +import com.bifan.txtreaderlib.interfaces.ITextSelectListener; +import com.bifan.txtreaderlib.main.TxtConfig; +import com.bifan.txtreaderlib.main.TxtReaderView; +import com.bifan.txtreaderlib.utils.ELogger; +import com.bifan.txtreaderlib.utils.FileProvider; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import java.io.File; + +/** + * Created by bifan-wei + * on 2017/12/8. + */ + +public class HwTxtPlayActivity extends AppCompatActivity { + protected Handler mHandler; + protected boolean FileExist = false; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getContentViewLayout()); + FileExist = getIntentData(); + init(); + loadFile(); + registerListener(); + } + + protected int getContentViewLayout() { + return R.layout.activity_hwtxtpaly; + } + + protected boolean getIntentData() { + // Get the intent that started this activity + Uri uri = getIntent().getData(); + if (uri != null) { + ELogger.log("getIntentData", "" + uri); + } else { + ELogger.log("getIntentData", "uri is null"); + } + if (uri != null) { + try { + String path = FileProvider.getFileAbsolutePath(this, uri); + if (!TextUtils.isEmpty(path)) { + if (path.contains("/storage/")) { + path = path.substring(path.indexOf("/storage/")); + } + ELogger.log("getIntentData", "path:" + path); + File file = new File(path); + if (file.exists()) { + FilePath = path; + FileName = file.getName(); + return true; + } else { + toast("文件不存在"); + return false; + } + } + return false; + } catch (Exception e) { + toast("文件出错了"); + } + } + + FilePath = getIntent().getStringExtra("FilePath"); + FileName = getIntent().getStringExtra("FileName"); + ContentStr = getIntent().getStringExtra("ContentStr"); + if (ContentStr == null) { + return FilePath != null && new File(FilePath).exists(); + } else { + return true; + } + + } + + + /** + * @param context 上下文 + * @param FilePath 文本文件路径 + */ + public static void loadTxtFile(Context context, String FilePath) { + loadTxtFile(context, FilePath, null); + } + + /** + * @param context 上下文 + * @param str 文本文内容 + */ + public static void loadStr(Context context, String str) { + loadTxtStr(context, str, null); + } + + /** + * @param context 上下文 + * @param str 文本显示内容 + * @param FileName 显示的书籍或者文件名称 + */ + public static void loadTxtStr(Context context, String str, String FileName) { + Intent intent = new Intent(); + intent.putExtra("ContentStr", str); + intent.putExtra("FileName", FileName); + intent.setClass(context, HwTxtPlayActivity.class); + context.startActivity(intent); + } + + /** + * @param context 上下文 + * @param FilePath 文本文件路径 + * @param FileName 显示的书籍或者文件名称 + */ + public static void loadTxtFile(Context context, String FilePath, String FileName) { + Intent intent = new Intent(); + intent.putExtra("FilePath", FilePath); + intent.putExtra("FileName", FileName); + intent.setClass(context, HwTxtPlayActivity.class); + context.startActivity(intent); + } + + protected View mTopDecoration, mBottomDecoration; + protected View mChapterMsgView; + protected TextView mChapterMsgName; + protected TextView mChapterMsgProgress; + protected TextView mChapterNameText; + protected TextView mChapterMenuText; + protected TextView mProgressText; + protected TextView mSettingText; + protected TextView mSelectedText; + protected TxtReaderView mTxtReaderView; + protected View mTopMenu; + protected View mBottomMenu; + protected View mCoverView; + protected View ClipboardView; + protected String CurrentSelectedText; + + protected ChapterList mChapterListPop; + protected MenuHolder mMenuHolder = new MenuHolder(); + + protected FloatingActionButton fab; + + protected void init() { + mHandler = new Handler(); + mChapterMsgView = findViewById(R.id.activity_hwTxtPlay_chapter_msg); + mChapterMsgName = findViewById(R.id.chapter_name); + mChapterMsgProgress = findViewById(R.id.chapter_progress); + mTopDecoration = findViewById(R.id.activity_hwTxtPlay_top); + mBottomDecoration = findViewById(R.id.activity_hwTxtPlay_bottom); + mTxtReaderView = findViewById(R.id.activity_hwTxtPlay_readerView); + mChapterNameText = findViewById(R.id.activity_hwTxtPlay_chapterName); + mChapterMenuText = findViewById(R.id.activity_hwTxtPlay_chapter_menuText); + mProgressText = findViewById(R.id.activity_hwTxtPlay_progress_text); + mSettingText = findViewById(R.id.activity_hwTxtPlay_setting_text); //设置 + mTopMenu = findViewById(R.id.activity_hwTxtPlay_menu_top); + mBottomMenu = findViewById(R.id.activity_hwTxtPlay_menu_bottom); + mCoverView = findViewById(R.id.activity_hwTxtPlay_cover); + ClipboardView = findViewById(R.id.activity_hwTxtPlay_ClipBoar); + mSelectedText = findViewById(R.id.activity_hwTxtPlay_selected_text); + + mMenuHolder.mTitle = findViewById(R.id.txtReader_menu_title); + mMenuHolder.mPreChapter = findViewById(R.id.txtReadr_menu_chapter_pre); + mMenuHolder.mNextChapter = findViewById(R.id.txtReadr_menu_chapter_next); + mMenuHolder.mSeekBar = findViewById(R.id.txtReadr_menu_seekbar); + mMenuHolder.mTextSizeDel = findViewById(R.id.txtRead_menu_textsize_del); + mMenuHolder.mTextSize = findViewById(R.id.txtRead_menu_textSize); + mMenuHolder.mTextSizeAdd = findViewById(R.id.txtRead_menu_textSize_add); + mMenuHolder.mBoldSelectedLayout = findViewById(R.id.txtRead_menu_textSetting1_bold); + mMenuHolder.mCoverSelectedLayout = findViewById(R.id.txtRead_menu_textSetting2_cover); + mMenuHolder.mShearSelectedLayout = findViewById(R.id.txtRead_menu_textSetting2_shear); + mMenuHolder.mTranslateSelectedLayout = findViewById(R.id.txtRead_menu_textSetting2_translate); + mMenuHolder.mStyle1 = findViewById(R.id.txtReader_menu_style1); + mMenuHolder.mStyle2 = findViewById(R.id.txtReader_menu_style2); + mMenuHolder.mStyle3 = findViewById(R.id.txtRead_menu_style3); + mMenuHolder.mStyle4 = findViewById(R.id.txtReader_menu_style4); + mMenuHolder.mStyle5 = findViewById(R.id.txtReader_menu_style5); + fab = findViewById(R.id.chatbot); + } + + private final int[] StyleTextColors = new int[]{ + Color.parseColor("#4a453a"), + Color.parseColor("#505550"), + Color.parseColor("#453e33"), + Color.parseColor("#8f8e88"), + Color.parseColor("#27576c") + }; + + protected String ContentStr = null; + protected String FilePath = null; + protected String FileName = null; + + protected void loadFile() { + TxtConfig.savePageSwitchDuration(this, 400); + if (ContentStr == null) { + if (TextUtils.isEmpty(FilePath) || !(new File(FilePath).exists())) { + toast("文件不存在"); + return; + } + } + mHandler.postDelayed(() -> { + //延迟加载避免闪一下的情况出现 + if (ContentStr == null) { + loadOurFile(); + } else { + loadStr(); + } + }, 300); + + + } + + /** + * + */ + protected void loadOurFile() { + mTxtReaderView.loadTxtFile(FilePath, new ILoadListener() { + @Override + public void onSuccess() { + if (!hasExisted) { + onLoadDataSuccess(); + } + } + + @Override + public void onFail(final TxtMsg txtMsg) { + if (!hasExisted) { + runOnUiThread(() -> onLoadDataFail(txtMsg)); + } + + } + + @Override + public void onMessage(String message) { + //加载过程信息 + } + }); + } + + /** + * @param txtMsg txtMsg + */ + protected void onLoadDataFail(TxtMsg txtMsg) { + //加载失败信息 + toast(String.valueOf(txtMsg)); + } + + /** + * + */ + protected void onLoadDataSuccess() { + if (TextUtils.isEmpty(FileName)) {//没有显示的名称,获取文件名显示 + FileName = mTxtReaderView.getTxtReaderContext().getFileMsg().FileName; + } + setBookName(FileName); + initWhenLoadDone(); + } + + private void loadStr() { + String testText = ContentStr; + mTxtReaderView.loadText(testText, new ILoadListener() { + @Override + public void onSuccess() { + setBookName("test with str"); + initWhenLoadDone(); + } + + @Override + public void onFail(TxtMsg txtMsg) { + //加载失败信息 + toast(txtMsg + ""); + } + + @Override + public void onMessage(String message) { + //加载过程信息 + } + }); + } + + protected void initWhenLoadDone() { + if (mTxtReaderView.getTxtReaderContext().getFileMsg() != null) { + FileName = mTxtReaderView.getTxtReaderContext().getFileMsg().FileName; + } + mMenuHolder.mTextSize.setText(String.valueOf(mTxtReaderView.getTextSize())); + mTopDecoration.setBackgroundColor(mTxtReaderView.getBackgroundColor()); + mBottomDecoration.setBackgroundColor(mTxtReaderView.getBackgroundColor()); + //mTxtReaderView.setLeftSlider(new MuiLeftSlider());//修改左滑动条 + //mTxtReaderView.setRightSlider(new MuiRightSlider());//修改右滑动条 + //字体初始化 + onTextSettingUi(mTxtReaderView.getTxtReaderContext().getTxtConfig().Bold); + //翻页初始化 + onPageSwitchSettingUi(mTxtReaderView.getTxtReaderContext().getTxtConfig().Page_Switch_Mode); + //保存的翻页模式 + int pageSwitchMode = mTxtReaderView.getTxtReaderContext().getTxtConfig().Page_Switch_Mode; + if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SERIAL) { + mTxtReaderView.setPageSwitchByTranslate(); + } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_COVER) { + mTxtReaderView.setPageSwitchByCover(); + } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SHEAR) { + mTxtReaderView.setPageSwitchByShear(); + } + //章节初始化 + if (mTxtReaderView.getChapters() != null && mTxtReaderView.getChapters().size() > 0) { + WindowManager m = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics metrics = new DisplayMetrics(); + m.getDefaultDisplay().getMetrics(metrics); + int ViewHeight = metrics.heightPixels - mTopDecoration.getHeight(); + mChapterListPop = new ChapterList(this, ViewHeight, mTxtReaderView.getChapters(), mTxtReaderView.getTxtReaderContext().getParagraphData().getCharNum()); + mChapterListPop.setOnDismissListener(this::hideNavigationBarStatusBar); + mChapterListPop.getListView().setOnItemClickListener((adapterView, view, i, l) -> { + IChapter chapter = (IChapter) mChapterListPop.getAdapter().getItem(i); + mChapterListPop.dismiss(); + mTxtReaderView.loadFromProgress(chapter.getStartParagraphIndex(), 0); + }); + } else { + Gone(mChapterMenuText); + } + } + + protected void registerListener() { + mSettingText.setOnClickListener(view -> Show(mTopMenu, mBottomMenu, mCoverView)); + setMenuListener(); + setSeekBarListener(); + setCenterClickListener(); + setPageChangeListener(); + setOnTextSelectListener(); + setStyleChangeListener(); + setExtraListener(); + setChatbotListener(); + } + + private void setChatbotListener() { + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // Define what happens when the fab is clicked +// Toast.makeText(HwTxtPlayActivity.this, "Floating Action Button Clicked!", Toast.LENGTH_SHORT).show(); + String bookName = FileName.substring(0, FileName.lastIndexOf(".")); + Intent intent = new Intent(HwTxtPlayActivity.this,ChatWithGptActivity.class); + intent.putExtra("book_name", bookName); + startActivity(intent); + } + }); + } + + //底部菜单中的按钮监听:上一章,下一章,字体减小、放大,粗体,翻页的滑盖、平移、剪切 + private void setExtraListener() { + mMenuHolder.mPreChapter.setOnClickListener(new ChapterChangeClickListener(true)); + mMenuHolder.mNextChapter.setOnClickListener(new ChapterChangeClickListener(false)); + mMenuHolder.mTextSizeAdd.setOnClickListener(new TextChangeClickListener(true)); + mMenuHolder.mTextSizeDel.setOnClickListener(new TextChangeClickListener(false)); + mMenuHolder.mBoldSelectedLayout.setOnClickListener(new TextSettingClickListener()); + mMenuHolder.mCoverSelectedLayout.setOnClickListener(new SwitchSettingClickListener(TxtConfig.PAGE_SWITCH_MODE_COVER)); + mMenuHolder.mTranslateSelectedLayout.setOnClickListener(new SwitchSettingClickListener(TxtConfig.PAGE_SWITCH_MODE_SERIAL)); + mMenuHolder.mShearSelectedLayout.setOnClickListener(new SwitchSettingClickListener(TxtConfig.PAGE_SWITCH_MODE_SHEAR)); + } + + //背景色修改 + protected void setStyleChangeListener() { + mMenuHolder.mStyle1.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor1), StyleTextColors[0])); + mMenuHolder.mStyle2.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor2), StyleTextColors[1])); + mMenuHolder.mStyle3.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor3), StyleTextColors[2])); + mMenuHolder.mStyle4.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor4), StyleTextColors[3])); + mMenuHolder.mStyle5.setOnClickListener(new StyleChangeClickListener(ContextCompat.getColor(this, R.color.hwTxtReader_styleColor5), StyleTextColors[4])); + } + + protected void setOnTextSelectListener() { + mTxtReaderView.setOnTextSelectListener(new ITextSelectListener() { + @Override + public void onTextChanging(TxtChar firstSelectedChar, TxtChar lastSelectedChar) { + //firstSelectedChar.Top + // firstSelectedChar.Bottom + // 这里可以根据 firstSelectedChar与lastSelectedChar的top与bottom的位置 + //计算显示你要显示的弹窗位置,如果需要的话 + } + + @Override + public void onTextChanging(String selectText) { + onCurrentSelectedText(selectText); + } + + @Override + public void onTextSelected(String selectText) { + onCurrentSelectedText(selectText); + } + }); + + mTxtReaderView.setOnSliderListener(new ISliderListener() { + @Override + public void onShowSlider(TxtChar txtChar) { + //TxtChar 为当前长按选中的字符 + // 这里可以根据 txtChar的top与bottom的位置 + //计算显示你要显示的弹窗位置,如果需要的话 + } + + @Override + public void onShowSlider(String currentSelectedText) { + onCurrentSelectedText(currentSelectedText); + Show(ClipboardView); + } + + @Override + public void onReleaseSlider() { + Gone(ClipboardView); + } + }); + + } + + protected void setPageChangeListener() { + mTxtReaderView.setPageChangeListener(progress -> { + int p = (int) (progress * 1000); + mProgressText.setText(((float) p / 10) + "%"); + mMenuHolder.mSeekBar.setProgress((int) (progress * 100)); + IChapter currentChapter = mTxtReaderView.getCurrentChapter(); + if (currentChapter != null) { + mChapterNameText.setText((currentChapter.getTitle() + "").trim()); + } else { + mChapterNameText.setText("无章节"); + } + }); + } + + protected void setCenterClickListener() { + mTxtReaderView.setOnCenterAreaClickListener(new ICenterAreaClickListener() { + @Override + public boolean onCenterClick(float widthPercentInView) { //点击中心区域才会弹出 + mSettingText.performClick(); + fab.setVisibility(View.VISIBLE); // Make FloatingActionButton visible + return true; + } + + @Override + public boolean onOutSideCenterClick(float widthPercentInView) { + if (mBottomMenu.getVisibility() == View.VISIBLE) { + mSettingText.performClick(); + fab.setVisibility(View.GONE); // 隐藏 FloatingActionButton + return true; + } + return false; + } + }); + } +// 设置菜单监听器 + protected void setMenuListener() { + mTopMenu.setOnTouchListener((view, motionEvent) -> true); + mBottomMenu.setOnTouchListener((view, motionEvent) -> true); + mCoverView.setOnTouchListener((view, motionEvent) -> { + Gone(mTopMenu, mBottomMenu, mCoverView, mChapterMsgView); + fab.setVisibility(View.GONE); // 同时隐藏 FloatingActionButton + return true; + }); + //为章节信息文本设置点击监听器,当点击时会显示或隐藏章节列表弹窗 + View.OnClickListener chapterMsgShowClick = v -> { + if (mChapterListPop != null) { + if (!mChapterListPop.isShowing()) { + mChapterListPop.showAsDropDown(mTopDecoration); + mHandler.postDelayed(() -> { + IChapter currentChapter = mTxtReaderView.getCurrentChapter(); + if (currentChapter != null) { + mChapterListPop.setCurrentIndex(currentChapter.getIndex()); + mChapterListPop.notifyDataSetChanged(); + } + }, 300); + } else { + mChapterListPop.dismiss(); + + } + } + }; + mChapterNameText.setOnClickListener(chapterMsgShowClick); + mChapterMenuText.setOnClickListener(chapterMsgShowClick); + mTopMenu.setOnClickListener(view -> { + if (mChapterListPop.isShowing()) { + mChapterListPop.dismiss(); + } + }); + } +// 设置进度条监听器 + protected void setSeekBarListener() { + mMenuHolder.mSeekBar.setOnTouchListener((view, motionEvent) -> { + if (motionEvent.getAction() == MotionEvent.ACTION_UP) { + mTxtReaderView.loadFromProgress(mMenuHolder.mSeekBar.getProgress()); + Gone(mChapterMsgView); + } + return false; + }); + mMenuHolder.mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, final int progress, boolean fromUser) { + if (fromUser) { + mHandler.post(() -> onShowChapterMsg(progress)); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + Gone(mChapterMsgView); + } + }); + + } + + + private void onShowChapterMsg(int progress) { + if (mTxtReaderView != null && mChapterListPop != null) { + IChapter chapter = mTxtReaderView.getChapterFromProgress(progress); + if (chapter != null) { + float p = (float) chapter.getStartIndex() / (float) mChapterListPop.getAllCharNum(); + if (p > 1) { + p = 1; + } + Show(mChapterMsgView); + mChapterMsgName.setText(chapter.getTitle()); + mChapterMsgProgress.setText((int) (p * 100) + "%"); + } + } + } + + private void onCurrentSelectedText(String SelectedText) { + String selectTextShow = String.format(getString(R.string.select_char_num), (SelectedText + "").length()); + mSelectedText.setText(selectTextShow); + CurrentSelectedText = SelectedText; + } + + private void onTextSettingUi(Boolean isBold) { + int rs = isBold ? R.drawable.ic_bold_selected : R.drawable.ic_bold_normal; + mMenuHolder.mBoldSelectedLayout.setBackgroundDrawable(ResourcesCompat.getDrawable(getResources(), rs, null)); + } + + private void onPageSwitchSettingUi(int pageSwitchMode) { + if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SERIAL) { + mMenuHolder.mTranslateSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_selected); + mMenuHolder.mCoverSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected); + mMenuHolder.mShearSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected); + } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_COVER) { + mMenuHolder.mTranslateSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected); + mMenuHolder.mCoverSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_selected); + mMenuHolder.mShearSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected); + } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SHEAR) { + mMenuHolder.mTranslateSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected); + mMenuHolder.mCoverSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_unselected); + mMenuHolder.mShearSelectedLayout.setBackgroundResource(R.drawable.shape_menu_textsetting_selected); + } + } + + private class TextSettingClickListener implements View.OnClickListener { + public TextSettingClickListener() { + } + + @Override + public void onClick(View view) { + if (FileExist) { + Boolean Bold = mTxtReaderView.getTxtReaderContext().getTxtConfig().Bold; + mTxtReaderView.setTextBold(!Bold); + onTextSettingUi(!Bold); + } + } + } + //设置滑盖 平移 剪切的翻页模式 + private class SwitchSettingClickListener implements View.OnClickListener { + private final int pageSwitchMode; + + public SwitchSettingClickListener(int pageSwitchMode) { + this.pageSwitchMode = pageSwitchMode; + } + + @Override + public void onClick(View view) { + if (FileExist) { + if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_COVER) { + mTxtReaderView.setPageSwitchByCover(); + } else if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SERIAL) { + mTxtReaderView.setPageSwitchByTranslate(); + } + if (pageSwitchMode == TxtConfig.PAGE_SWITCH_MODE_SHEAR) { + mTxtReaderView.setPageSwitchByShear(); + } + onPageSwitchSettingUi(pageSwitchMode); + } + } + } + + + private class ChapterChangeClickListener implements View.OnClickListener { + private final Boolean Pre; + + public ChapterChangeClickListener(Boolean pre) { + Pre = pre; + } + + @Override + public void onClick(View view) { + if (Pre) { + mTxtReaderView.jumpToPreChapter(); + } else { + mTxtReaderView.jumpToNextChapter(); + } + } + } + + private class TextChangeClickListener implements View.OnClickListener { + private final Boolean Add; + + public TextChangeClickListener(Boolean pre) { + Add = pre; + } + + @Override + public void onClick(View view) { + if (FileExist) { + int textSize = mTxtReaderView.getTextSize(); + if (Add) { + if (textSize + 2 <= TxtConfig.MAX_TEXT_SIZE) { + mTxtReaderView.setTextSize(textSize + 2); + mMenuHolder.mTextSize.setText(textSize + 2 + ""); + } + } else { + if (textSize - 2 >= TxtConfig.MIN_TEXT_SIZE) { + mTxtReaderView.setTextSize(textSize - 2); + mMenuHolder.mTextSize.setText(textSize - 2 + ""); + } + } + } + } + } + + private class StyleChangeClickListener implements View.OnClickListener { + private final int BgColor; + private final int TextColor; + + public StyleChangeClickListener(int bgColor, int textColor) { + BgColor = bgColor; + TextColor = textColor; + } + + @Override + public void onClick(View view) { + if (FileExist) { + mTxtReaderView.setStyle(BgColor, TextColor); + mTopDecoration.setBackgroundColor(BgColor); + mBottomDecoration.setBackgroundColor(BgColor); + } + } + } + + protected void setBookName(String name) { + mMenuHolder.mTitle.setText(name + ""); + } + + protected void Show(View... views) { + for (View v : views) { + v.setVisibility(View.VISIBLE); + } + } + + protected void Gone(View... views) { + for (View v : views) { + v.setVisibility(View.GONE); + } + } + + + private Toast t; + + protected void toast(final String msg) { + if (t != null) { + t.cancel(); + } + t = Toast.makeText(HwTxtPlayActivity.this, msg, Toast.LENGTH_SHORT); + t.show(); + } + + protected static class MenuHolder { + public TextView mTitle; + public TextView mPreChapter; + public TextView mNextChapter; + public SeekBar mSeekBar; + public View mTextSizeDel; + public View mTextSizeAdd; + public TextView mTextSize; + public View mBoldSelectedLayout; + public View mCoverSelectedLayout; + public View mShearSelectedLayout; + public View mTranslateSelectedLayout; + public View mStyle1; + public View mStyle2; + public View mStyle3; + public View mStyle4; + public View mStyle5; + } + + @Override + protected void onResume() { + super.onResume(); + hideNavigationBarStatusBar(); + } + + private void hideNavigationBarStatusBar() { + if (Build.VERSION.SDK_INT >= 19) { + View decorView = getWindow().getDecorView(); + decorView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + exist(); + } + + public void BackClick(View view) { + finish(); + } + + public void onCopyText(View view) { + if (!TextUtils.isEmpty(CurrentSelectedText)) { + toast("已经复制到粘贴板"); + ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(CurrentSelectedText + ""); + } + onCurrentSelectedText(""); + mTxtReaderView.releaseSelectedState(); + Gone(ClipboardView); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + finish(); + } + + @Override + public void finish() { + super.finish(); + exist(); + } + + protected boolean hasExisted = false; + + protected void exist() { + if (!hasExisted) { + ContentStr = null; + hasExisted = true; + if (mHandler != null) { + mHandler.removeCallbacksAndMessages(null); + mHandler = null; + } + if (mTxtReaderView != null) { + mTxtReaderView.saveCurrentProgress(); + mTxtReaderView.onDestroy(); + } + if (mTxtReaderView != null) { + mTxtReaderView.getTxtReaderContext().Clear(); + mTxtReaderView = null; + } + if (mChapterListPop != null) { + if (mChapterListPop.isShowing()) { + mChapterListPop.dismiss(); + } + mChapterListPop.onDestroy(); + mChapterListPop = null; + } + mMenuHolder = null; + } + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ConfigUtils.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ConfigUtils.java new file mode 100644 index 0000000..b0720a7 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ConfigUtils.java @@ -0,0 +1,21 @@ +package com.bifan.txtreaderlib.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class ConfigUtils { + private static final String CONFIG_FILE = "config.properties"; + + public static String getAuthKey() { + try { + Properties properties = new Properties(); + InputStream inputStream = ConfigUtils.class.getClassLoader().getResourceAsStream(CONFIG_FILE); + properties.load(inputStream); + return properties.getProperty("auth_key"); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/DisPlayUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/DisPlayUtil.java new file mode 100644 index 0000000..fd19c5b --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/DisPlayUtil.java @@ -0,0 +1,29 @@ +package com.bifan.txtreaderlib.utils; + +import android.content.Context; + +public class DisPlayUtil { + + public static int px2dip(Context context, float pxValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (pxValue / scale + 0.5f); + } + + + public static int dip2px(Context context, float dipValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dipValue * scale + 0.5f); + } + + + public static int px2sp(Context context, float pxValue) { + final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; + return (int) (pxValue / fontScale + 0.5f); + } + + + public static int sp2px(Context context, float spValue) { + final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; + return (int) (spValue * fontScale + 0.5f); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ELogger.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ELogger.java new file mode 100644 index 0000000..19434e0 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/ELogger.java @@ -0,0 +1,26 @@ +package com.bifan.txtreaderlib.utils; + +import android.util.Log; + +import com.bifan.txtreaderlib.interfaces.ITxtReaderLoggerListener; +import com.bifan.txtreaderlib.main.TxtConfig; + +/** + * Created by HP on 2017/11/15. + */ + +public class ELogger { + private static ITxtReaderLoggerListener l; + public static void setLoggerListener(ITxtReaderLoggerListener l) { + ELogger.l = l; + } + public static void log(String tag, String msg) { + if(TxtConfig.DebugMode) { + Log.e(tag, msg + ""); + if (l != null) { + l.onLog(tag, msg + ""); + } + } + } + +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileCharsetDetector.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileCharsetDetector.java new file mode 100644 index 0000000..04f0bf8 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileCharsetDetector.java @@ -0,0 +1,121 @@ +package com.bifan.txtreaderlib.utils; + +import android.text.TextUtils; + +import org.mozilla.intl.chardet.nsDetector; +import org.mozilla.intl.chardet.nsICharsetDetectionObserver; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class FileCharsetDetector { + private boolean found = false; + private String encoding = null; + + + public String getCharset(File file) { + String charset = "GBK"; + byte[] first3Bytes = new byte[3]; + try { + boolean checked = false; + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); + bis.mark(0); + int read = bis.read(first3Bytes, 0, 3); + if (read == -1) + return charset; + if (first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE) { + charset = "UTF-16LE"; + checked = true; + } else if (first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF) { + charset = "UTF-16BE"; + checked = true; + } else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB + && first3Bytes[2] == (byte) 0xBF) { + charset = "UTF-8"; + checked = true; + } + if (!checked) { + charset = guessFileEncoding(file, new nsDetector()); + if (!TextUtils.isEmpty(charset)) { + if (charset.equals("Big5")) { + charset = "GBK"; + } + } + } + bis.reset(); + bis.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return charset; + } + + + /** + * 获取文件的编码 + * @param file + * @param det + * @return + * @throws FileNotFoundException + * @throws IOException + */ + private String guessFileEncoding(File file, nsDetector det) throws IOException { + // Set an observer... + // The Notify() will be called when a matching charset is found. + det.Init(new nsICharsetDetectionObserver() { + @Override + public void Notify(String charset) { + encoding = charset; + found = true; + } + }); + + BufferedInputStream imp = new BufferedInputStream(new FileInputStream(file)); + byte[] buf = new byte[1024]; + int len; + boolean done; + boolean isAscii = false; + + while ((len = imp.read(buf, 0, buf.length)) != -1) { + // Check if the stream is only ascii. + isAscii = det.isAscii(buf, len); + if (isAscii) { + break; + } + // DoIt if non-ascii and not done yet. + done = det.DoIt(buf, len, false); + if (done) { + break; + } + } + imp.close(); + det.DataEnd(); + + if (isAscii) { + encoding = "ASCII"; + found = true; + } + + if (!found) { + String[] prob = det.getProbableCharsets(); + // 这里将可能的字符集组合起来返回 + for (int i = 0; i < prob.length; i++) { + if (i == 0) { + encoding = prob[i]; + } else { + encoding += "," + prob[i]; + } + } + if (prob.length > 0) { + // 在没有发现情况下,也可以只取第一个可能的编码,这里返回的是一个可能的序列 + return encoding; + } else { + return "GBK"; + } + } + return encoding; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileProvider.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileProvider.java new file mode 100644 index 0000000..3169980 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileProvider.java @@ -0,0 +1,233 @@ +package com.bifan.txtreaderlib.utils; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.FileUtils; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import androidx.annotation.RequiresApi; + +/** + * Created by bifan-wei + * on 2021/11/6. + */ + +public class FileProvider { + + + /** + * 根据Uri获取文件绝对路径,解决Android4.4以上版本Uri转换 兼容Android 10 + * + * @param context + * @param uri + */ + public static String getFileAbsolutePath(Context context, Uri uri) { + if (context == null || uri == null) { + return null; + } + + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { + return getRealFilePath(context, uri); + } + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && DocumentsContract.isDocumentUri(context, uri)) { + if (isExternalStorageDocument(uri)) { + String docId = DocumentsContract.getDocumentId(uri); + String[] split = docId.split(":"); + String type = split[0]; + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + } else if (isDownloadsDocument(uri)) { + String id = DocumentsContract.getDocumentId(uri); + Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + return getDataColumn(context, contentUri, null, null); + } else if (isMediaDocument(uri)) { + String docId = DocumentsContract.getDocumentId(uri); + String[] split = docId.split(":"); + String type = split[0]; + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + String selection = MediaStore.Images.Media._ID + "=?"; + String[] selectionArgs = new String[]{split[1]}; + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } // MediaStore (and general) + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + return uriToFileApiQ(context,uri); + } + else if ("content".equalsIgnoreCase(uri.getScheme())) { + // Return the remote address + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + return null; + } + + //此方法 只能用于4.4以下的版本 + private static String getRealFilePath(final Context context, final Uri uri) { + if (null == uri) { + return null; + } + final String scheme = uri.getScheme(); + String data = null; + if (scheme == null) { + data = uri.getPath(); + } else if (ContentResolver.SCHEME_FILE.equals(scheme)) { + data = uri.getPath(); + } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { + String[] projection = {MediaStore.Images.ImageColumns.DATA}; + Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); + +// Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null); + if (null != cursor) { + if (cursor.moveToFirst()) { + int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); + if (index > -1) { + data = cursor.getString(index); + } + } + cursor.close(); + } + } + return data; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + String column = MediaStore.Images.Media.DATA; + String[] projection = {column}; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + private static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + + /** + * Android 10 以上适配 另一种写法 + * @param context + * @param uri + * @return + */ + private static String getFileFromContentUri(Context context, Uri uri) { + if (uri == null) { + return null; + } + String filePath; + String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME}; + ContentResolver contentResolver = context.getContentResolver(); + Cursor cursor = contentResolver.query(uri, filePathColumn, null, + null, null); + if (cursor != null) { + cursor.moveToFirst(); + try { + filePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0])); + return filePath; + } catch (Exception e) { + } finally { + cursor.close(); + } + } + return ""; + } + + /** + * Android 10 以上适配 + * @param context + * @param uri + * @return + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + private static String uriToFileApiQ(Context context, Uri uri) { + File file = null; + //android10以上转换 + if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + file = new File(uri.getPath()); + } else if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { + //把文件复制到沙盒目录 + ContentResolver contentResolver = context.getContentResolver(); + Cursor cursor = contentResolver.query(uri, null, null, null, null); + if (cursor.moveToFirst()) { + String displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + try { + InputStream is = contentResolver.openInputStream(uri); + File cache = new File(context.getExternalCacheDir().getAbsolutePath(), Math.round((Math.random() + 1) * 1000) + displayName); + FileOutputStream fos = new FileOutputStream(cache); + FileUtils.copy(is, fos); + file = cache; + fos.close(); + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return file.getAbsolutePath(); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileUtil.java new file mode 100644 index 0000000..01214e5 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FileUtil.java @@ -0,0 +1,54 @@ +package com.bifan.txtreaderlib.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.MessageDigest; + + +public class FileUtil { + public static String getMD5Checksum(String filePath) throws Exception { + byte[] b = createChecksum(filePath); + String result = ""; + for (int i = 0; i < b.length; i++) { + result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1); + } + return result; + } + + + public static Boolean FileExist(String path) { + if (path != null && path.length() > 0) { + File file = new File(path); + return file.exists(); + } + return false; + } + + public static String getDefaultNameFromFilePath(String filePath) { + if (filePath == null || filePath.length() == 0) { + return ""; + } + File file = new File(filePath); + if (file.exists()) { + return file.getName(); + } + return filePath; + } + + private static byte[] createChecksum(String filename) throws Exception { + InputStream fis = new FileInputStream(filename); + byte[] buffer = new byte[1024]; + MessageDigest complete = MessageDigest.getInstance("MD5"); + int numRead; + do { + numRead = fis.read(buffer); + if (numRead > 0) { + complete.update(buffer, 0, numRead); + } + } while (numRead != -1); + + fis.close(); + return complete.digest(); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FormatUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FormatUtil.java new file mode 100644 index 0000000..a84efa6 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/FormatUtil.java @@ -0,0 +1,15 @@ +package com.bifan.txtreaderlib.utils; + +/* +* create by bifan-wei +* 2017-11-13 +*/ +public class FormatUtil { + public static Boolean isDigital(char Char) { + return Character.isDigit(Char); + } + + public static Boolean isLetter(char Char) { + return (Char >= 65 && Char <= 90) || (Char >= 97 && Char <= 122); + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TextBreakUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TextBreakUtil.java new file mode 100644 index 0000000..e9be714 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TextBreakUtil.java @@ -0,0 +1,120 @@ +package com.bifan.txtreaderlib.utils; + + +import android.graphics.Paint; + +public class TextBreakUtil { + + /** + * @param cs + * @param measureWidth + * @param textPadding + * @param paint + * @return is[0] 为个数 is[1] 为是否满一行,其余为字符宽度 + */ + private static float[] BreakText(char[] cs, float measureWidth, float textPadding, Paint paint) { + float width = 0; + int index = 2; + float[] is = new float[index + cs.length]; + + for (int i = 0, size = cs.length; i < size; i++) { + String measureStr = String.valueOf(cs[i]); + float charWidth = paint.measureText(measureStr); + if ((width + textPadding + charWidth) >= measureWidth) { + is[0] = i; + is[1] = 1; + return is; + } else { + is[index++] = charWidth; + } + + width += charWidth + textPadding; + } + is[0] = cs.length; + is[1] = 0; + return is; + } + + /** + * @param cs + * @param measureHeight + * @param textPadding + * @param paint + * @return + */ + private static float[] BreakTextVertical(char[] cs, float measureHeight, float textPadding, Paint paint) { + float height = 0; + int index = 2; + float[] is = new float[index + cs.length]; + for (int i = 0, size = cs.length; i < size; i++) { + float charHeight = paint.getTextSize(); + if ((height + textPadding + charHeight) >= measureHeight) { + is[0] = i; + is[1] = 1; + return is; + } else { + is[index++] = charHeight; + } + + height += charHeight + textPadding; + } + is[0] = cs.length; + is[1] = 0; + return is; + } + + + /** + * @param text + * @param measureHeight + * @param textPadding + * @param paint + * @return + */ + public static float[] BreakTextVertical(String text, float measureHeight, float textPadding, Paint paint) { + if (text == null || text.length() == 0) { + float[] is = new float[2]; + is[0] = 0; + is[1] = 0; + return is; + } + return BreakTextVertical(text.toCharArray(), measureHeight, textPadding, paint); + + } + + /** + * @param text + * @param measureWidth + * @param textPadding + * @param paint + * @return + */ + public static float[] BreakText(String text, float measureWidth, float textPadding, Paint paint) { + if (text == null || text.length() == 0) { + float[] is = new float[2]; + is[0] = 0; + is[1] = 0; + return is; + } + return BreakText(text.toCharArray(), measureWidth, textPadding, paint); + + } + + /** + * @param text + * @param textPadding + * @param paint + * @return + */ + public static float measureText(String text, float textPadding, Paint paint) { + if (text == null || text.length() == 0) return 0; + char[] cs = text.toCharArray(); + float width = 0; + for (int i = 0, size = cs.length; i < size; i++) { + String measureStr = String.valueOf(cs[i]); + float charWidth = paint.measureText(measureStr); + width += charWidth + textPadding; + } + return width; + } +} diff --git a/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TxtBitmapUtil.java b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TxtBitmapUtil.java new file mode 100644 index 0000000..27ea266 --- /dev/null +++ b/hwtxtreaderlib/src/main/java/com/bifan/txtreaderlib/utils/TxtBitmapUtil.java @@ -0,0 +1,148 @@ +package com.bifan.txtreaderlib.utils; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; + +import com.bifan.txtreaderlib.bean.EnChar; +import com.bifan.txtreaderlib.bean.NumChar; +import com.bifan.txtreaderlib.bean.TxtChar; +import com.bifan.txtreaderlib.interfaces.IPage; +import com.bifan.txtreaderlib.interfaces.ITxtLine; +import com.bifan.txtreaderlib.main.PageParam; +import com.bifan.txtreaderlib.main.PaintContext; +import com.bifan.txtreaderlib.main.TxtConfig; + +import java.util.List; + +/** + * Created by bifan-wei + * on 2017/11/27. + */ + +public class TxtBitmapUtil { + public static final Bitmap createHorizontalPage(Bitmap bg, PaintContext paintContext, PageParam pageParam, TxtConfig txtConfig, IPage page) { + if (page == null || !page.HasData() || bg == null || bg.isRecycled()) { + return null; + } + Bitmap bitmap = bg.copy(Bitmap.Config.RGB_565, true); + Canvas canvas = new Canvas(bitmap); + List lines = page.getLines(); + int textHeight = txtConfig.textSize; + int lineHeight = textHeight + pageParam.LinePadding; + int topL = (int) (pageParam.PaddingLeft + pageParam.TextPadding) + 3; + int bottom = pageParam.PaddingTop + textHeight; + int bomL = bottom; + int paraMargin = pageParam.ParagraphMargin; + float CharPadding = pageParam.TextPadding; + Paint paint = paintContext.textPaint; + int defaultColor = txtConfig.textColor; + + float x = topL; + float y = bottom; + + if (!txtConfig.ShowSpecialChar) { + paint.setColor(defaultColor); + } + for (ITxtLine line : lines) { + if (line.HasData()) { + for (TxtChar txtChar : line.getTxtChars()) { + if (txtConfig.ShowSpecialChar) { + if (txtChar instanceof NumChar || txtChar instanceof EnChar) { + paint.setColor(txtChar.getTextColor()); + } else { + paint.setColor(defaultColor); + } + } + canvas.drawText(txtChar.getValueStr(), x, y, paint); + txtChar.Left = (int) x; + txtChar.Right = (int) (x + txtChar.CharWidth); + txtChar.Bottom = (int) y + 5; + txtChar.Top = txtChar.Bottom - textHeight; + x = txtChar.Right + CharPadding; + } + + x = topL; + y = y + lineHeight; + + if (line.isParagraphEndLine()) { + y = y + paraMargin; + + } + } + } + + return bitmap; + } + + public static final Bitmap createVerticalPage(Bitmap bg, PaintContext paintContext, PageParam pageParam, TxtConfig txtConfig, IPage page) { + if (page == null || !page.HasData() || bg == null || bg.isRecycled()) { + return null; + } + + Bitmap bitmap = bg.copy(Bitmap.Config.RGB_565, true); + Canvas canvas = new Canvas(bitmap); + List lines = page.getLines(); + int textHeight = txtConfig.textSize; + int lineWidth = (int) pageParam.LineWidth; + int bottom = pageParam.PaddingTop + textHeight; + float CharPadding = pageParam.TextPadding; + Paint paint = paintContext.textPaint; + int defaultColor = txtConfig.textColor; + + float x = calculateX(pageParam, txtConfig, page); + float y = bottom; + + if (!txtConfig.ShowSpecialChar) { + paint.setColor(defaultColor); + } + for (ITxtLine line : lines) { + if (line.HasData()) { + for (TxtChar txtChar : line.getTxtChars()) { + if (txtConfig.ShowSpecialChar) { + if (txtChar instanceof NumChar || txtChar instanceof EnChar) { + paint.setColor(txtChar.getTextColor()); + } else { + paint.setColor(defaultColor); + } + } + canvas.drawText(txtChar.getValueStr(), x, y, paint); + txtChar.Left = (int) x; + txtChar.Right = (int) (x + textHeight + 5); + txtChar.Bottom = (int) (y + 5); + txtChar.Top = (int) (txtChar.Bottom - txtChar.CharWidth); + y = y + CharPadding + textHeight; + } + x = x - lineWidth; + y = bottom; + } + } + + return bitmap; + } + + private static float calculateX(PageParam pageParam, TxtConfig txtConfig, IPage page) { + int lineNum = page.getLineNum(); + int margin = (pageParam.PageWidth - txtConfig.textSize * lineNum - pageParam.LinePadding * (lineNum - 1)) / 2; + return pageParam.PageWidth - margin - txtConfig.textSize; + } + + + public static Bitmap createBitmap(int bitmapStyleColor, int bitmapWidth, int bitmapHeight) { + int[] BitmapColor = getBitmapColor(bitmapStyleColor, bitmapWidth, bitmapHeight); + return Bitmap.createBitmap(BitmapColor, bitmapWidth, bitmapHeight, Bitmap.Config.RGB_565); + } + + private static int[] getBitmapColor(int color, int with, int height) { + int[] colors = new int[with * height]; + int STRIDE = height; + int c = color; + for (int y = 0; y < with; y++) {// use of x,y is legible then // the // + for (int x = 0; x < height; x++) { + colors[y * STRIDE + x] = c;// the shift operation generates + } + } + return colors; + } + +} diff --git a/hwtxtreaderlib/src/main/res/drawable/bg_message_bot.xml b/hwtxtreaderlib/src/main/res/drawable/bg_message_bot.xml new file mode 100644 index 0000000..685a315 --- /dev/null +++ b/hwtxtreaderlib/src/main/res/drawable/bg_message_bot.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/hwtxtreaderlib/src/main/res/drawable/bg_message_user.xml b/hwtxtreaderlib/src/main/res/drawable/bg_message_user.xml new file mode 100644 index 0000000..45298d1 --- /dev/null +++ b/hwtxtreaderlib/src/main/res/drawable/bg_message_user.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/hwtxtreaderlib/src/main/res/drawable/chatbot.png b/hwtxtreaderlib/src/main/res/drawable/chatbot.png new file mode 100644 index 0000000..92f77f6 Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/chatbot.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_bold_normal.png b/hwtxtreaderlib/src/main/res/drawable/ic_bold_normal.png new file mode 100644 index 0000000..1e2d7fa Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_bold_normal.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_bold_selected.png b/hwtxtreaderlib/src/main/res/drawable/ic_bold_selected.png new file mode 100644 index 0000000..f9cd235 Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_bold_selected.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_chapter.png b/hwtxtreaderlib/src/main/res/drawable/ic_chapter.png new file mode 100644 index 0000000..880260d Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_chapter.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_copy.png b/hwtxtreaderlib/src/main/res/drawable/ic_copy.png new file mode 100644 index 0000000..bbe1f5d Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_copy.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_key_add.png b/hwtxtreaderlib/src/main/res/drawable/ic_key_add.png new file mode 100644 index 0000000..ea27b34 Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_key_add.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_key_back.png b/hwtxtreaderlib/src/main/res/drawable/ic_key_back.png new file mode 100644 index 0000000..1be2480 Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_key_back.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_key_del.png b/hwtxtreaderlib/src/main/res/drawable/ic_key_del.png new file mode 100644 index 0000000..045c4be Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_key_del.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_regenerate.png b/hwtxtreaderlib/src/main/res/drawable/ic_regenerate.png new file mode 100644 index 0000000..bb76d4f Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_regenerate.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/ic_setting.png b/hwtxtreaderlib/src/main/res/drawable/ic_setting.png new file mode 100644 index 0000000..76cb1b4 Binary files /dev/null and b/hwtxtreaderlib/src/main/res/drawable/ic_setting.png differ diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_chapter_msg.xml b/hwtxtreaderlib/src/main/res/drawable/shape_chapter_msg.xml new file mode 100644 index 0000000..f69c998 --- /dev/null +++ b/hwtxtreaderlib/src/main/res/drawable/shape_chapter_msg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_little_ball.xml b/hwtxtreaderlib/src/main/res/drawable/shape_little_ball.xml new file mode 100644 index 0000000..7b7f893 --- /dev/null +++ b/hwtxtreaderlib/src/main/res/drawable/shape_little_ball.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_selected.xml b/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_selected.xml new file mode 100644 index 0000000..cad31ac --- /dev/null +++ b/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_selected.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_unselected.xml b/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_unselected.xml new file mode 100644 index 0000000..4d8cd80 --- /dev/null +++ b/hwtxtreaderlib/src/main/res/drawable/shape_menu_textsetting_unselected.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/res/drawable/shape_seekbar_thumb.xml b/hwtxtreaderlib/src/main/res/drawable/shape_seekbar_thumb.xml new file mode 100644 index 0000000..1e064c8 --- /dev/null +++ b/hwtxtreaderlib/src/main/res/drawable/shape_seekbar_thumb.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/res/drawable/txtview_po_seekbar.xml b/hwtxtreaderlib/src/main/res/drawable/txtview_po_seekbar.xml new file mode 100644 index 0000000..3576730 --- /dev/null +++ b/hwtxtreaderlib/src/main/res/drawable/txtview_po_seekbar.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hwtxtreaderlib/src/main/res/layout/activity_chatwithgpt.xml b/hwtxtreaderlib/src/main/res/layout/activity_chatwithgpt.xml new file mode 100644 index 0000000..4ff46d3 --- /dev/null +++ b/hwtxtreaderlib/src/main/res/layout/activity_chatwithgpt.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + +