kotlin开源项目 您所在的位置:网站首页 简书在线阅读 kotlin开源项目

kotlin开源项目

2023-12-02 14:22| 来源: 网络整理| 查看: 265

这是一款基于kotlin的免费Android小说应用。 本项目参考了https://github.com/390057892/reader 项目, 将原项目的阅读功能单独打包成了readerlib库来使用,摒除了原项目繁杂的依赖关系,可以方便的应用于其他项目。

在这里对原作者表示衷心的感谢!

项目明细 环境:

android studio 3.5 kotlin_version 1.3.61 kotlin_coroutines 1.3.3

特点:

本项目采用Serverless模式进行开发,数据源来自于jsoup抓取网页数据,功能大部分使用Android官方数据库框架Room在本地实现,使用了kotlin协程和Rxjava来处理异步数据,开箱即用。具有很好的学习和参考价值。

主要功能

1.在线搜索,分类浏览,查看详情 2.加入本地书架阅读 3.阅读偏好设置 4.记录搜索历史 5.记录阅读进度 6.添加本地书籍到书架 7.管理书架 (更多新增功能、UI优化、bug修复还在陆续填坑中...)

预览 device-2020-02-20-141340.png device-2020-02-20-141601.png device-2020-02-20-141616.png 项目架构

本项目采用采用MVC架构进行开发,所有核心功能都由BookRegistory抽象类实现,开发者只需要重写相关抽象方法即可实现对应的功能,将核心模块完全解耦出来,方便功能迁移和二次开发。

关键代码

BookRegistory模块

/** * Created by newbiechen on 17-5-8. * 存储关于书籍内容的信息(CollBook(收藏书籍),BookChapter(书籍列表),ChapterInfo(书籍章节),BookRecord(记录),BookSignTable书签) */ abstract class BookRepository { /** * 保存阅读记录 */ abstract fun saveBookRecord(mBookRecord: ReadRecordBean) /** * 获取阅读记录 */ abstract fun getBookRecord(bookUrl: String, readRecordListener: OnReadRecordListener) /** * 获取章节列表 */ abstract fun chapterBeans(mCollBook: BookBean, onChaptersListener: OnChaptersListener, start: Int = 0) /** * 获取章节内容 */ abstract fun requestChapterContents(mCollBook: BookBean, requestChapters: List, onChaptersListener: OnChaptersListener) abstract fun saveBookChaptersWithAsync(bookChapterBeanList: List, mCollBook: BookBean) /** * 存储章节 * * @param folderName * @param fileName * @param content */ fun saveChapterInfo(folderName: String, fileName: String, content: String) { val filePath = (Constant.BOOK_CACHE_PATH + folderName + File.separator + fileName + FileUtils.SUFFIX_NB) if (File(filePath).exists()) { return } val str = content.replace("\\\\n\\\\n".toRegex(), "\n") val file = BookManager.getBookFile(folderName, fileName) //获取流并存储 var writer: Writer? = null try { writer = BufferedWriter(FileWriter(file)) writer.write(str) writer.flush() } catch (e: IOException) { e.printStackTrace() close(writer) } } /** * 加入书架 */ abstract fun saveCollBookWithAsync(mCollBook: BookBean) /** * 书签是否已存在 */ abstract fun hasSigned(chapterUrl: String): Boolean /** * 添加书签 */ abstract fun addSign(mBookUrl: String, chapterUrl: String, chapterName: String, bookSignsListener: OnBookSignsListener) /** * 删除书签 */ abstract fun deleteSign(vararg bookSign: BookSignTable) /** * 获取书签列表 */ abstract fun getSigns(bookUrl: String, bookSignsListener: OnBookSignsListener) /** * 加载插图 */ abstract fun loadBitmap(context: Context, imageUrl: String, bitmapLoadListener: OnBitmapLoadListener) /** * 音量键翻页开关 */ abstract fun canTurnPageByVolume(): Boolean }

BookRegistory实现类

import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.text.TextUtils import android.util.Log import cn.mewlxy.novel.appDB import cn.mewlxy.novel.jsoup.DomSoup import cn.mewlxy.novel.jsoup.OnJSoupListener import cn.mewlxy.novel.utils.showToast import com.bumptech.glide.Glide import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import com.mewlxy.readlib.interfaces.OnBitmapLoadListener import com.mewlxy.readlib.interfaces.OnBookSignsListener import com.mewlxy.readlib.interfaces.OnChaptersListener import com.mewlxy.readlib.interfaces.OnReadRecordListener import com.mewlxy.readlib.model.* import com.mewlxy.readlib.utlis.MD5Utils import com.mewlxy.readlib.utlis.SpUtil import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jsoup.nodes.Document import org.reactivestreams.Subscriber import org.reactivestreams.Subscription /** * description: * author:luoxingyuan */ open class BookRepositoryImpl : BookRepository() { private val uiScope = CoroutineScope(Dispatchers.Main) private val domSoup = DomSoup() var lastSub: Subscription? = null companion object { val instance: BookRepositoryImpl by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { BookRepositoryImpl() } } private fun getChapterContent(chapterBean: ChapterBean): Single { return Single.create { uiScope.launch(Dispatchers.IO) { val chapterContent = appDB.chapterDao().getChapterContent(chapterBean.url) launch(Dispatchers.Main) { if (chapterContent.isNullOrBlank()) { domSoup.getSoup(chapterBean.url, object : OnJSoupListener { override fun start() { } override fun success(document: Document) { val paragraphTags = document.body().getElementById("content") .getElementsByTag("p") val stringBuilder = StringBuilder() for (p in paragraphTags) { stringBuilder.append("\t\t\t\t").append(p.text()).append("\n\n") } chapterBean.content = stringBuilder.toString() it.onSuccess(chapterBean) launch(Dispatchers.IO) { val chapterModel = ChapterModel() chapterModel.id = chapterBean.id chapterModel.name = chapterBean.name chapterModel.url = chapterBean.url chapterModel.content = chapterBean.content chapterModel.bookName = chapterBean.bookName chapterModel.bookUrl = chapterBean.bookUrl appDB.chapterDao().updates(chapterModel) } } override fun failed(errMsg: String) { it.onError(Throwable(errMsg)) } }) } else { chapterBean.content = chapterContent it.onSuccess(chapterBean) } } } } } override fun saveBookRecord(mBookRecord: ReadRecordBean) { try { uiScope.launch(Dispatchers.IO) { mBookRecord.bookMd5 = MD5Utils.strToMd5By16(mBookRecord.bookUrl)!! try { appDB.readRecordDao().inserts(ReadRecordModel.createReadRecordModel(mBookRecord)) } catch (e: Exception) { } } } catch (e: Exception) { Log.e("error", e.toString()) } } override fun getBookRecord(bookUrl: String, readRecordListener: OnReadRecordListener) { readRecordListener.onStart() var readRecordModel: ReadRecordModel? try { uiScope.launch(Dispatchers.IO) { readRecordModel = appDB.readRecordDao().getReadRecord(MD5Utils.strToMd5By16(bookUrl)!!) launch(Dispatchers.Main) { readRecordListener.onSuccess(if (readRecordModel == null) ReadRecordModel() else readRecordModel!!) } } } catch (e: Exception) { readRecordListener.onError(e.toString()) } } override fun chapterBeans(mCollBook: BookBean, onChaptersListener: OnChaptersListener, start: Int) { onChaptersListener.onStart() try { uiScope.launch(Dispatchers.IO) { val chapters = arrayListOf() chapters.addAll(appDB.chapterDao().getChaptersByBookUrl(mCollBook.url, start = start).map { return@map it.convert2ChapterBean() }) launch(Dispatchers.Main) { onChaptersListener.onSuccess(chapters) } } } catch (e: Exception) { onChaptersListener.onError(e.toString()) } } override fun requestChapterContents(mCollBook: BookBean, requestChapters: List, onChaptersListener: OnChaptersListener) { lastSub?.cancel() onChaptersListener.onStart() val singleList = requestChapters.map { return@map getChapterContent(it!!) } val newChapters = arrayListOf() Single.concat(singleList) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(object : Subscriber { override fun onComplete() { onChaptersListener.onSuccess(newChapters) } override fun onSubscribe(s: Subscription?) { s?.request(Int.MAX_VALUE.toLong()) lastSub = s } override fun onNext(chapterBean: ChapterBean) { newChapters.add(chapterBean) //存储章节内容到本地文件 if (chapterBean.content.isNotBlank()) { saveChapterInfo(MD5Utils.strToMd5By16(chapterBean.bookUrl)!!, chapterBean.name, chapterBean.content) } } override fun onError(t: Throwable?) { onChaptersListener.onError(t.toString()) } }) } override fun saveBookChaptersWithAsync(bookChapterBeanList: List, mCollBook: BookBean) { uiScope.launch(Dispatchers.IO) { try { appDB.chapterDao().inserts(*(bookChapterBeanList.map { return@map ChapterModel.convert2ChapterModel(it) }.toTypedArray())) } catch (e: Exception) { } } } override fun saveCollBookWithAsync(mCollBook: BookBean) { val bookModel = BookModel.convert2BookModel(mCollBook) if (!TextUtils.isEmpty(bookModel.url)) { uiScope.launch(Dispatchers.IO) { val url = appDB.bookDao().queryFavoriteByUrl(bookModel.url)?.url val favorite = appDB.bookDao().queryFavoriteByUrl(bookModel.url)?.favorite withContext(Dispatchers.Main) { if (TextUtils.isEmpty(url) && favorite == null) { launch(Dispatchers.IO) { bookModel.favorite = 1 try { appDB.bookDao().inserts(bookModel) } catch (e: Exception) { } } showToast("加入书架成功") } else if (!TextUtils.isEmpty(url) && favorite == 0) { launch(Dispatchers.IO) { bookModel.favorite = 1 appDB.bookDao().update(bookModel) } showToast("加入书架成功") } else { showToast("该书籍已在书架中") } } } } } //---------------------------------------------书签相关--------------------------------------------- override fun hasSigned(chapterUrl: String): Boolean { var bookSign: BookSignModel? = null uiScope.launch(Dispatchers.IO) { bookSign = appDB.bookSignDao().getSignsByChapterUrl(chapterUrl) } return bookSign != null } override fun addSign(mBookUrl: String, chapterUrl: String, chapterName: String, bookSignsListener: OnBookSignsListener) { bookSignsListener.onStart() val bookSign = BookSignModel() bookSign.bookUrl = mBookUrl bookSign.chapterUrl = chapterUrl bookSign.chapterName = chapterName try { uiScope.launch(Dispatchers.IO) { if (appDB.bookSignDao().getSignsByChapterUrl(chapterUrl) == null) { try { appDB.bookSignDao().inserts(bookSign) } catch (e: Exception) { } launch(Dispatchers.Main) { bookSignsListener.onSuccess(mutableListOf(bookSign)) } } else { launch(Dispatchers.Main) { showToast("本章节书签已经存在") } } } } catch (e: Exception) { bookSignsListener.onError(e.toString()) } } override fun deleteSign(vararg bookSign: BookSignTable) { uiScope.launch(Dispatchers.IO) { val list = bookSign.map { return@map it as BookSignModel }.toTypedArray() appDB.bookSignDao().delete(*list) } } override fun getSigns(bookUrl: String, bookSignsListener: OnBookSignsListener) { bookSignsListener.onStart() val bookSigns = mutableListOf() try { uiScope.launch(Dispatchers.IO) { bookSigns.addAll(appDB.bookSignDao().getSignsByBookUrl(bookUrl)) launch(Dispatchers.Main) { bookSignsListener.onSuccess(bookSigns) } } } catch (e: Exception) { bookSignsListener.onError(e.toString()) } } override fun loadBitmap(context: Context, imageUrl: String, bitmapLoadListener: OnBitmapLoadListener) { try { Glide.with(context).asBitmap().load(imageUrl).thumbnail(0.1f).into(object : SimpleTarget() { override fun onLoadStarted(placeholder: Drawable?) { bitmapLoadListener.onLoadStart() } override fun onResourceReady(resource: Bitmap, transition: Transition?) { bitmapLoadListener.onResourceReady(resource) } }) } catch (e: Exception) { bitmapLoadListener.onError("加载失败") showToast(e.toString()) } } override fun canTurnPageByVolume(): Boolean { return SpUtil.getBooleanValue("volume_turn_page", true) } } 项目地址

源码



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有