try read book online

This commit is contained in:
mwang 2019-04-05 23:59:31 +08:00
parent 9fb63f10aa
commit 90af9bbce2
34 changed files with 961 additions and 372 deletions

View File

@ -2,12 +2,12 @@
<litepal>
<dbname value="book" ></dbname>
<version value="1" ></version>
<version value="3" ></version>
<list>
<mapping class="com.novelbook.android.db.BookChapter"></mapping>
<mapping class="com.novelbook.android.db.Book"></mapping>
<mapping class="com.novelbook.android.db.Chapter"></mapping>
<mapping class="com.novelbook.android.db.Novel"></mapping>
<mapping class="com.novelbook.android.db.BookMarks"></mapping>
<mapping class="com.novelbook.android.db.BookDan"></mapping>
<mapping class="com.novelbook.android.db.SiteRule"></mapping>
</list>
</litepal>

View File

@ -119,7 +119,7 @@ public abstract class Activity_base extends AppCompatActivity {
{
Novel bk = new Novel();
bk.setAuthor("金庸");
bk.setNovelName("射雕英雄传" +(char)i);
bk.setName("射雕英雄传" +(char)i);
bk.setNovelType("武侠");
bk.setDescription("南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事");
mDatas.add(bk);

View File

@ -109,7 +109,7 @@ public class Activity_createShudan extends Activity_base {
{
Novel bk = new Novel ();
bk.setAuthor("金庸" +i);
bk.setNovelName("射雕" +(char)i);
bk.setName("射雕" +(char)i);
bk.setNovelType("cate" +i);
bk.setDescription(" 南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事");
mData .add(bk);
@ -185,7 +185,7 @@ public class Activity_createShudan extends Activity_base {
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.tvTitle.setText(mDatas.get(position).getNovelName());
holder.tvTitle.setText(mDatas.get(position).getName());
holder.tvAuthor.setText(mDatas.get(position).getAuthor());
holder.tvCate.setText(mDatas.get(position).getNovelType());
holder.tvDesc.setText(mDatas.get(position).getDescription());

View File

@ -1,26 +1,41 @@
package com.novelbook.android;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.gson.Gson;
import com.novelbook.android.adapter.BookListAdapter;
import com.novelbook.android.bean.NovelSites;
import com.novelbook.android.db.Chapter;
import com.novelbook.android.db.Novel;
import com.novelbook.android.filechooser.FileChooserActivity;
import com.novelbook.android.netsubscribe.BookSubscribe;
import com.novelbook.android.netsubscribe.MovieSubscribe;
import com.novelbook.android.netutils.HttpMethods;
import com.novelbook.android.netutils.OnSuccessAndFaultListener;
import com.novelbook.android.netutils.OnSuccessAndFaultSub;
import com.novelbook.android.utils.BookUtil;
import com.novelbook.android.utils.GsonUtil;
import com.novelbook.android.utils.NovelParseUtil;
import com.novelbook.android.utils.PageFactory;
import com.youth.banner.loader.ImageLoader;
import org.json.JSONException;
import org.json.JSONObject;
@ -41,21 +56,45 @@ import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import static com.novelbook.android.FileActivity.EXTERNAL_STORAGE_REQ_CODE;
public class BookActivity extends Activity_base {
private PageFactory pageFactory;
String novelId="f2619820112625133c14dcb170f5e092";
String muluUrl="https://www.qu.la/book/390/";
private Novel mNovel;
private Chapter mChapter;
static String TAG = BookActivity.class.getSimpleName();
BookListAdapter mAdapter;
// private BookListAdapter mAdapter;
private List<Novel> mData;;
private ArrayList<Chapter> mChapters = new ArrayList<>();
private Gson gson = new Gson();
private List<Chapter> mChapters = new ArrayList<>();
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.rvBooklist)
RecyclerView rvBooklist;
@BindView(R.id.btnShelf)
Button btnShelf;
@BindView(R.id.imageView)
ImageView imageView;
@BindView(R.id.title)
TextView txtTitle;
@BindView(R.id.desc)
TextView txtDesc;
@BindView(R.id.author)
TextView txtAuth;
@BindView(R.id.category)
TextView txtCategory;
@BindView(R.id.txtDesc2)
TextView txtDesc2;
@BindView(R.id.txtLatestCate)
TextView txtLatestCate;
@Override
public int getLayoutRes() {
@ -92,31 +131,111 @@ public class BookActivity extends Activity_base {
@Override
protected void initData() {
getBookInfo();
// getMuluInfo();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkPermission(BookActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE, EXTERNAL_STORAGE_REQ_CODE,"添加图书需要此权限,请允许");
}
pageFactory = PageFactory.getInstance();
setBookInfo();//set title ,data from novel list
if(mNovel==null) {
getBookInfo();
}
mData =getFakeData(5);
mAdapter = getBookListAdapter(mData);
}
@OnClick({R.id.btnRead,R.id.btnCacheBook})
private void setBookInfo() {
}
private MyLoader loader = new MyLoader();
public void setBookDetailInfo( ){
setShelfButtonText();
this.txtAuth.setText(mNovel.getAuthor());
this.txtCategory.setText(mNovel.getNovelType());
this.txtDesc.setText(mNovel.getDescription());
this.txtTitle.setText(mNovel.getName());
this.txtDesc2.setText(mNovel.getDescription());
this.txtLatestCate.setText(mNovel.getLastestChapterName());
if(mNovel.getLastUpateTime()>0){
;
this.txtLatestCate.setText( new Date(mNovel.getLastUpateTime()).toString() +"\n"+txtLatestCate.getText());
}
if(!TextUtils.isEmpty(mNovel.getCover())) {
loader.displayImage(BookActivity.this, mNovel.getCover(), imageView);
}
}
private class MyLoader extends ImageLoader {
@Override
public void displayImage(Context context, Object path, ImageView imageView) {
Glide.with(context).load((String) path).into(imageView);
}
}
void setShelfButtonText(){
String title = mNovel.isOnShelf()?"移除书架":"加入书架";
btnShelf.setText(title);
}
@OnClick({R.id.btnRead,R.id.btnCacheBook,R.id.btnShelf})
void submitButton(View view){
switch (view.getId()) {
case R.id.btnRead:
testBook();
readBook();
//testBook();
// openBook(new Novel() );
break;
case R.id.btnCacheBook:
cachBook();
cacheBook();
break;
case R.id.btnShelf:
shelfBook();
break;
}
}
private void cachBook() {
private void shelfBook() {
mNovel.setOnShelf(!mNovel.isOnShelf());
mNovel.save();
setShelfButtonText();
}
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
int wt = msg.what;
if (msg.what == 1 ) {
setBookDetailInfo();
}
}
};
private void readBook() {
if(mNovel ==null){
Toast.makeText(this,"网络错误",Toast.LENGTH_LONG);
return;
}
ReadActivity.openBook(mNovel , BookActivity.this);
}
private void getChapters() {
}
private void cacheBook() {
MovieSubscribe.getData(1,10,new OnSuccessAndFaultSub(new OnSuccessAndFaultListener() {
@Override
public void onSuccess(String result) {
@ -158,32 +277,33 @@ public class BookActivity extends Activity_base {
}).setCancelable(true).show();
return;
}
ReadActivity.openBook(book ,mChapters,this);
// ReadActivity.openBook(book ,null,this);
}
void getMuluInfo(){
BookSubscribe.getNovelMulu(novelId,new OnSuccessAndFaultSub(new OnSuccessAndFaultListener() {
BookSubscribe.getNovelSites(novelId,new OnSuccessAndFaultSub(new OnSuccessAndFaultListener() {
@Override
public void onSuccess(String result) {
//成功
try {
JSONObject jsonObject = new JSONObject(result);
muluUrl= jsonObject.getString("muluUrl");
NovelSites nvs = (NovelSites) gson.fromJson(result,NovelSites.class);
pageFactory.prepareBook(mNovel,nvs,BookActivity.this);
} catch (JSONException e) {
} catch ( Exception e) {
e.printStackTrace();
}
Toast.makeText(BookActivity.this,"muluUrl 请求成功:" + muluUrl,Toast.LENGTH_SHORT).show();
Toast.makeText(BookActivity.this,"getMuluInfo 请求成功 " ,Toast.LENGTH_SHORT).show();
}
@Override
public void onFault(String errorMsg) {
//失败
Toast.makeText(BookActivity.this,"Novel 请求失败:"+errorMsg,Toast.LENGTH_SHORT).show();
Toast.makeText(BookActivity.this,"getMuluInfo 请求失败"+errorMsg,Toast.LENGTH_SHORT).show();
}
},BookActivity.this));
}
@ -197,37 +317,10 @@ public class BookActivity extends Activity_base {
@Override
public void onSuccess(String result) {
//成功
Gson gson = new Gson();
// result ={"id":"f2619820112625133c14dcb170f5e092","novelType":"<EFBFBD><EFBFBD><EFBFBD><EFBFBD>","novelType2":"<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>½","name":"<EFBFBD><EFBFBD><EFBFBD>Ʋ<EFBFBD><EFBFBD>","author":"<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>","cover":"http:\/\/qidian.qpic.cn\/qdbimg\/349573\/1209977\/180","description":"","lastestChapterName":"<EFBFBD>ڰ<EFBFBD><EFBFBD><EFBFBD> <20><>ʼ","lastUpateTime":""}
// Novel nv = gson.fromJson(result,Novel.class); //to change id to noveId
Novel nv = new Novel();
try {
JSONObject jsonObject = new JSONObject(result);
nv.setNovelId(jsonObject.getString("id"));
// nv.setLastUpateTime(jsonObject.getLong("lastUpateTime"));
nv.setAuthor(jsonObject.getString("author"));
nv.setNovelName(jsonObject.getString("name"));
nv.setCover(jsonObject.getString("cover"));
nv.setNovelType(jsonObject.getString("novelType"));
nv.setNovelType2(jsonObject.getString("novelType2"));
nv.setLastestChapterName(jsonObject.getString("lastestChapterName"));
} catch (JSONException e) {
e.printStackTrace();
}
//to load image
//to load desc
//...
Novel nv ;
nv = gson.fromJson(result,Novel.class);
// nv = GsonUtil.getNovel(result);
@ -235,6 +328,7 @@ public class BookActivity extends Activity_base {
if(!isLocalDbExist){
// nv.saveAsync();
nv.save ();
}else{
List<Novel> nvs = LitePal.where("novelId=?",novelId).find(Novel.class);
@ -243,7 +337,7 @@ public class BookActivity extends Activity_base {
Log.d(TAG,String.format("novel id %s before update: lastUpdateTime: %s",novel.getId(),novel.getLastUpateTime()));
}
nv.setLastUpateTime(new Date().getTime());
// nv.setLastUpateTime(new Date().getTime());
nv.updateAll("novelId=?",novelId);
nvs = LitePal.where("novelId=?",novelId).find(Novel.class);
@ -258,9 +352,10 @@ public class BookActivity extends Activity_base {
if(nvs.size()>0) {
mNovel = nvs.get(0);
handler.sendEmptyMessage(1);
}
// getMuluInfo();
getMuluInfo();
Toast.makeText(BookActivity.this,"Novel 请求成功:"+result,Toast.LENGTH_SHORT).show();
}
@ -272,13 +367,13 @@ public class BookActivity extends Activity_base {
},BookActivity.this));
}
void testBook(){
/*
void readChapters( String url){
//to get mulu list
String url = muluUrl;// "https://www.qu.la/book/161/";//"https://www.qu.la/book/746/";
url = muluUrl;// "https://www.qu.la/book/161/";//"https://www.qu.la/book/746/";
Request request = new Request.Builder()
.url(url)
// .header("User-Agent", "OkHttp Example")
@ -298,7 +393,7 @@ public class BookActivity extends Activity_base {
String bodyStr = body.string();
Log.d(TAG, "onResponse: " +bodyStr);
onResponseProcess(bodyStr,url);
ReadActivity.openBook(mNovel ,mChapters,BookActivity.this);
ReadActivity.openBook(mNovel ,mChapter,BookActivity.this);
} catch (IOException e) {
e.printStackTrace();
}finally {
@ -315,51 +410,19 @@ void onResponseProcess( String content ,String url){
// HttpResult result ;
// HttpResult result2 = null;
try {
JSONObject siteJson = new JSONObject();
siteJson.put("chapterUrlRegexOnMulu", "");
siteJson.put("chapterUrlPattern", "/book/[\\d]+/[\\d]+\\.html$");
/*
String[] chapters = NovelParseUtil.getChapters(url, content, siteJson);
if (chapters != null) {
for (int i = 0; i < chapters.length; i += 2) {
Log.d(TAG,String.format("%s-->%s", chapters[i], chapters[i+1]));
}
}
*/
siteJson.put("chapterUrlRegexOnMulu", "<dd> <a[^>]*href=\"(/book/[\\d]+/[\\d]+\\.html)\">([^<]+)</a></dd>");
String[] chapters2 = NovelParseUtil.getChapters(url, content, siteJson);
if (chapters2 != null) {
for (int i = 0; i < chapters2.length; i += 2) {
Log.d(TAG, String.format("%s-->%s", chapters2[i], chapters2[i + 1]));
Chapter chapter = new Chapter();
chapter.setChapterName(chapters2[i + 1]);
chapter.setChapterUrl(chapters2[i ]);
mChapters.add(chapter);
mChapters = NovelParseUtil.getChapters(url, content, siteJson);
if (mChapters != null) {
int lastReadChapt = mNovel.getLastReadChapt();
// int index =lastReadChapt*2-2;
}
lastReadChapt = lastReadChapt >=mChapters.size() ? mChapters.size() -1:lastReadChapt;
lastReadChapt = lastReadChapt <=0 ? 1:lastReadChapt;
mChapter =mChapters.get(lastReadChapt-1);
/*
siteJson.put("chapterContentRegex", "<div id=\"content\">([\\s\\S]+?)</div>");
siteJson.put("chapterContentDumpRegex", "<script>chaptererror();</script>");
for (int i = 0; i < chapters2.length; i += 2) {
Request request = new Request.Builder()
.url(chapters2[i])
// .header("User-Agent", "OkHttp Example")
.build();
Response response = HttpMethods.getOkClient().newCall(request).execute();
// Log.d(TAG,String.format("%s-->%s\n%s" , chapters2[i], chapters2[i+1] , NovelParseUtil.getChapterContent(response.body().string(), siteJson)));
}
*/
}
} catch (JSONException e) {
// } catch (JSONException | IOException e) {
@ -368,7 +431,7 @@ void onResponseProcess( String content ,String url){
// result.close();
// if (result2 != null) result2.close();
}
}
}*/
//----------------绑定列表
@ -398,126 +461,5 @@ void onResponseProcess( String content ,String url){
super.onStop();
// blurLayout.pauseBlur();
}
/*
class BookListAdapter extends RecyclerView.Adapter< BookListAdapter.MyViewHolder> {
private final int EMPTY_VIEW = 1;
private final int PROGRESS_VIEW = 2;
private final int IMAGE_VIEW = 3;
private Context context;
private List<String> mDatas = new ArrayList<String>();
private OnItemClickListener mOnItemClickListener;
private int listItemID;
public BookListAdapter(Context context,List<String> mDatas,int listItemID,OnItemClickListener clickLitener) {
this.context = context;
this.mDatas = mDatas;
this.mOnItemClickListener = clickLitener;
this.listItemID = listItemID;
}
public BookListAdapter(Context context, OnItemClickListener clickLitener) {
this.context = context;
this.mOnItemClickListener = clickLitener;
}
@Override
public int getItemViewType(int position) {
if(mDatas.size() == 0){
return EMPTY_VIEW;
} else if(mDatas.get(position) == null){
return PROGRESS_VIEW;
} else {
return super.getItemViewType(position);
}
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
BookListAdapter.MyViewHolder holder = new BookListAdapter.MyViewHolder(LayoutInflater.from(
context).inflate(listItemID, parent,
false));
return holder;
}
public void setParameters(List<String> mDatas,int listItemID ) {
this.mDatas = mDatas;
this.listItemID = listItemID;
}
public void setOnItemClickLitener(OnItemClickListener mOnItemClickLitener)
{
this.mOnItemClickListener = mOnItemClickLitener;
}
@Override
public void onBindViewHolder( BookListAdapter.MyViewHolder holder, int position)
{
holder.tvTitle.setText(mDatas.get(position));
holder.tvAuthor.setText("金庸" +position);
holder.tvCate.setText("cate"+position);
holder.tvDesc.setText("this is desc " +position);
// 如果设置了回调则设置点击事件
if (mOnItemClickListener != null)
{
holder.itemView.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemClick(holder.itemView, pos);
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener()
{
@Override
public boolean onLongClick(View v)
{
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemLongClick(holder.itemView, pos);
return false;
}
});
}
}
@Override
public int getItemCount()
{
return mDatas.size();
}
public void addData(int position) {
mDatas.add(position, "Insert One");
notifyItemInserted(position);
}
public void removeData(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
}
class MyViewHolder extends RecyclerView.ViewHolder
{
@BindView(R.id.title)
TextView tvTitle;
@BindView(R.id.author)
TextView tvAuthor;
@BindView(R.id.category)
TextView tvCate;
@BindView(R.id.desc)
TextView tvDesc;
public MyViewHolder(View view)
{
super(view);
ButterKnife.bind(this, view);
//tvTitle = (TextView) view.findViewById(R.id.title);
// tvAuthor = (TextView) view.findViewById(R.id.author);
}
}
}
*/
}

View File

@ -157,7 +157,7 @@ public class FileActivity extends Activity_base {
for (File file : files) {
Novel bookList = new Novel();
String bookName = Fileutil.getFileNameNoEx(file.getName());
bookList.setNovelName(bookName);
bookList.setName(bookName);
bookList.setNovelPath(file.getAbsolutePath());
bookLists.add(bookList);
}
@ -206,7 +206,7 @@ public class FileActivity extends Activity_base {
adapter.cancel();
break;
case REPEAT:
msg = "书本" + repeatBookList.getNovelName() + "重复了";
msg = "书本" + repeatBookList.getName() + "重复了";
break;
}

View File

@ -119,7 +119,7 @@ public abstract class BasicFragment extends Fragment {
{
Novel bk = new Novel();
bk.setAuthor("金庸");
bk.setNovelName("射雕英雄传" +(char)i);
bk.setName("射雕英雄传" +(char)i);
bk.setNovelType("武侠");
bk.setDescription("南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事南宋时期的武林故事");
mDatas.add(bk);
@ -143,7 +143,7 @@ public abstract class BasicFragment extends Fragment {
book.setLastReadPos(book1.getLastReadPos());
book.setLastReadChapt(book1.getLastReadChapt());
}
Toast.makeText(activity, book.getNovelName() + "加载", Toast.LENGTH_SHORT).show();
Toast.makeText(activity, book.getName() + "加载", Toast.LENGTH_SHORT).show();
final String path = book.getNovelPath();
if(null ==path) {
@ -167,7 +167,7 @@ public abstract class BasicFragment extends Fragment {
}).setCancelable(true).show();
return;
}
// ReadActivity.openBook(book ,activity);
ReadActivity.openBook(book ,activity);
}
void showShudanDetail(int shuandanId){

View File

@ -28,7 +28,7 @@ public class BookMarkFragment extends BasicFragment {
@BindView(R.id.lv_bookmark)
ListView lv_bookmark;
private String bookpath;
private int novelId;
private String mArgument;
private List<BookMarks> bookMarksList;
private MarkAdapter markAdapter;
@ -44,10 +44,10 @@ public class BookMarkFragment extends BasicFragment {
pageFactory = PageFactory.getInstance();
Bundle bundle = getArguments();
if (bundle != null) {
bookpath = bundle.getString(ARGUMENT);
novelId = bundle.getInt(ARGUMENT);
}
bookMarksList = new ArrayList<>();
bookMarksList = LitePal.where("bookpath = ?", bookpath).find(BookMarks.class);
bookMarksList = LitePal.where("novelId = ?", novelId+"").find(BookMarks.class);
}
@ -79,7 +79,7 @@ public class BookMarkFragment extends BasicFragment {
public void onClick(DialogInterface dialog, int which) {
LitePal.delete(BookMarks.class,bookMarksList.get(position).getId());
bookMarksList.clear();
bookMarksList.addAll(LitePal.where("bookpath = ?", bookpath).find(BookMarks.class));
bookMarksList.addAll(LitePal.where("novelId = ?", novelId+"").find(BookMarks.class));
markAdapter.notifyDataSetChanged();
}
}).setCancelable(true).show();
@ -101,13 +101,13 @@ public class BookMarkFragment extends BasicFragment {
/**
* 用于从Activity传递数据到Fragment
* @param bookpath
* @param
* @return
*/
public static BookMarkFragment newInstance(String bookpath)
public static BookMarkFragment newInstance(int novelId)
{
Bundle bundle = new Bundle();
bundle.putString(ARGUMENT, bookpath);
bundle.putInt(ARGUMENT, novelId);
BookMarkFragment bookMarkFragment = new BookMarkFragment();
bookMarkFragment.setArguments(bundle);
return bookMarkFragment;

View File

@ -39,7 +39,7 @@ public class CatalogFragment extends BasicFragment {
@Override
protected void initData() {
pageFactory = PageFactory.getInstance();
catalogueList.addAll(pageFactory.getDirectoryList());
catalogueList.addAll(pageFactory.getChapters());
}

View File

@ -85,7 +85,7 @@ public class Fragment_Shelf extends BasicFragment {
flag = new boolean[100];
// mDatas = initData(mDatas,'X');
bookLists = LitePal.findAll(Novel.class);
bookLists = LitePal.where("isOnShelf=? or novelId=? ","1","").find(Novel.class);
@ -201,7 +201,7 @@ public class Fragment_Shelf extends BasicFragment {
.setBackgroundColor(getResources().getColor(android.R.color.transparent));
TextView tv =(TextView) bottomSheetDialog.findViewById(R.id.bdTitle);
tv.setText(bookLists.get(position).getNovelName());
tv.setText(bookLists.get(position).getName());
bottomSheetDialog.show();
}
@ -399,7 +399,7 @@ public class Fragment_Shelf extends BasicFragment {
public void onBindViewHolder(MyViewHolder holder, int position)
{
holder.tvTitle.setText(mDatas.get(position).getNovelName());
holder.tvTitle.setText(mDatas.get(position).getName());
if(holder.tvAuthor!=null) holder.tvAuthor.setText(mDatas.get(position).getAuthor());
if(holder.tvCate!=null) holder.tvCate.setText(mDatas.get(position).getNovelType());
if(holder.tvDesc!=null) holder.tvDesc.setText(mDatas.get(position).getDescription());

View File

@ -72,11 +72,11 @@ public class MarkActivity extends Activity_base {
}
});
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(FileUtils.getFileName(pageFactory.getBookPath()));
getSupportActionBar().setTitle(pageFactory.getBookName());
}
setTabsValue();
pager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(),pageFactory.getBookPath()));
pager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(),pageFactory.getNovle().getId()));
tabs.setViewPager(pager);
}

View File

@ -60,7 +60,7 @@ import butterknife.OnClick;
public class ReadActivity extends Activity_base implements SpeechSynthesizerListener {
private static final String TAG = "ReadActivity";
private final static String EXTRA_BOOK = "book";
private final static String EXTRA_CHAPTERS = "chapters";
// private final static String EXTRA_CHAPTER = "chapter";
private final static int MESSAGE_CHANGEPROGRESS = 1;
@BindView(R.id.bookpage)
@ -106,7 +106,7 @@ public class ReadActivity extends Activity_base implements SpeechSynthesizerLis
private Config config;
private WindowManager.LayoutParams lp;
private Novel book;
private List<Chapter> mChapters;
// private Chapter mChapter;
private PageFactory pageFactory;
private int screenWidth, screenHeight;
// popwindow是否显示
@ -198,12 +198,12 @@ public class ReadActivity extends Activity_base implements SpeechSynthesizerLis
//获取intent中的携带的信息
Intent intent = getIntent();
book = (Novel) intent.getSerializableExtra(EXTRA_BOOK);
mChapters = (ArrayList<Chapter>) intent.getSerializableExtra(EXTRA_CHAPTERS);
// mChapter = ( Chapter ) intent.getSerializableExtra(EXTRA_CHAPTER);
bookpage.setPageMode(config.getPageMode());
pageFactory.setPageWidget(bookpage);
try {
pageFactory.openBook(book,mChapters);
pageFactory.openBook(book,this);
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "打开电子书失败", Toast.LENGTH_SHORT).show();
@ -474,7 +474,7 @@ public class ReadActivity extends Activity_base implements SpeechSynthesizerLis
if (id == R.id.action_add_bookmark){
if (pageFactory.getCurrentPage() != null) {
List<BookMarks> bookMarksList = LitePal.where("bookpath = ? and begin = ?", pageFactory.getBookPath(),pageFactory.getCurrentPage().getBegin() + "").find(BookMarks.class);
List<BookMarks> bookMarksList = LitePal.where("novelId = ? and begin = ?", pageFactory.getNovle().getId()+"",pageFactory.getCurrentPage().getBegin() + "").find(BookMarks.class);
if (!bookMarksList.isEmpty()){
Toast.makeText(ReadActivity.this, "该书签已存在", Toast.LENGTH_SHORT).show();
@ -491,7 +491,7 @@ public class ReadActivity extends Activity_base implements SpeechSynthesizerLis
bookMarks.setTime(time);
bookMarks.setBegin(pageFactory.getCurrentPage().getBegin());
bookMarks.setText(word);
bookMarks.setBookpath(pageFactory.getBookPath());
bookMarks.setNovelId(pageFactory.getNovle().getId());
bookMarks.save();
Toast.makeText(ReadActivity.this, "书签添加成功", Toast.LENGTH_SHORT).show();
@ -527,14 +527,15 @@ public class ReadActivity extends Activity_base implements SpeechSynthesizerLis
}
public static boolean openBook(final Novel book,final ArrayList<Chapter> chapters ,Activity context) {
public static boolean openBook(final Novel book, Activity context) {
if (book == null){
throw new NullPointerException("Novel can not be null");
}
Intent intent = new Intent(context, ReadActivity.class);
intent.putExtra(EXTRA_BOOK, book);
intent.putExtra(EXTRA_CHAPTERS, chapters);
// intent.putExtra(EXTRA_CHAPTER, chapter);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.overridePendingTransition(R.anim.in_from_right, R.anim.out_to_left);

View File

@ -80,7 +80,7 @@ public class BookListAdapter extends RecyclerView.Adapter< MyViewHolder> {
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.tvTitle.setText(mDatas.get(position).getNovelName());
holder.tvTitle.setText(mDatas.get(position).getName());
if (holder.tvAuthor != null) holder.tvAuthor.setText(mDatas.get(position).getAuthor());
if (holder.tvCate != null) holder.tvCate.setText(mDatas.get(position).getNovelType());
if (holder.tvDesc != null) holder.tvDesc.setText(mDatas.get(position).getDescription());

View File

@ -15,12 +15,12 @@ import com.novelbook.android.Fragments.CatalogFragment;
public class MyPagerAdapter extends FragmentPagerAdapter {
private CatalogFragment catalogueFragment;
private BookMarkFragment bookMarkFragment;
private String bookPath;
private int novelId;
private final String[] titles = { "目录", "书签" };
public MyPagerAdapter(FragmentManager fm,String bookPath) {
public MyPagerAdapter(FragmentManager fm,int novelId) {
super(fm);
this.bookPath = bookPath;
this.novelId = novelId;
}
@Override
@ -42,7 +42,7 @@ public class MyPagerAdapter extends FragmentPagerAdapter {
//创建bookMarkFragment实例时同时把需要intent中的值传入
// catalogueFragment = CatalogFragment
// bookMarkFragment = BookMarkFragment.newInstance(MarkActivity.getBookpath_intent());
catalogueFragment = CatalogFragment.newInstance(bookPath);
catalogueFragment = CatalogFragment.newInstance("");
}
return catalogueFragment;
@ -51,7 +51,7 @@ public class MyPagerAdapter extends FragmentPagerAdapter {
//catalogueFragment = new CatalogueFragment();
// catalogueFragment = CatalogueFragment.newInstance(MarkActivity.getBookpath_intent());
// bookMarkFragment = BookMarkFragment.newInstance(MarkActivity.getBookpath_intent());
bookMarkFragment = BookMarkFragment.newInstance(bookPath);
bookMarkFragment = BookMarkFragment.newInstance(novelId);
}
return bookMarkFragment;
}

View File

@ -96,7 +96,7 @@ public class ShelfAdapter extends BaseAdapter implements DragGridListener {
viewHolder.deleteItem_IB.setVisibility(View.INVISIBLE);
}
viewHolder.name.setVisibility(View.VISIBLE);
String fileName = bilist.get(position).getNovelName();
String fileName = bilist.get(position).getName();
viewHolder.name.setText(fileName);
}else {
contentView.setVisibility(View.INVISIBLE);
@ -167,9 +167,9 @@ public class ShelfAdapter extends BaseAdapter implements DragGridListener {
public void updateBookPosition (int position,int databaseId,List<Novel> bookLists) {
Novel book = new Novel();
String bookpath = bookLists.get(position).getNovelPath();
String bookname = bookLists.get(position).getNovelName();
String bookname = bookLists.get(position).getName();
book.setNovelPath(bookpath);
book.setNovelName(bookname);
book.setName(bookname);
book.setLastReadPos(bookLists.get(position).getLastReadPos());
book.setLastReadChapt(bookLists.get(position).getLastReadChapt());
book.setCharset(bookLists.get(position).getCharset());

View File

@ -0,0 +1,22 @@
package com.novelbook.android.bean;
public class NovelSites {
private String novelId;
private Site[] sites;
public String getNovelId() {
return novelId;
}
public void setNovelId(String novelId) {
this.novelId = novelId;
}
public Site[] getSites() {
return sites;
}
public void setSites(Site[] sites) {
this.sites = sites;
}
}

View File

@ -0,0 +1,31 @@
package com.novelbook.android.bean;
public class Site {
private String domain;
private String muluUrl;
private Boolean selectedByDefault;
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public String getMuluUrl() {
return muluUrl;
}
public void setMuluUrl(String muluUrl) {
this.muluUrl = muluUrl;
}
public Boolean getSelectedByDefault() {
return selectedByDefault;
}
public void setSelectedByDefault(Boolean selectedByDefault) {
this.selectedByDefault = selectedByDefault;
}
}

View File

@ -11,7 +11,7 @@ public class BookMarks extends LitePalSupport {
// private int count;
private String text;
private String time;
private String bookpath;
private int novelId;
public int getId() {
return this.id;
}
@ -44,12 +44,12 @@ public class BookMarks extends LitePalSupport {
this.begin = begin;
}
public String getBookpath() {
return this.bookpath;
public int getNovelId() {
return this.novelId;
}
public void setBookpath(String bookpath) {
this.bookpath = bookpath;
public void setNovelId(int novelId ) {
this.novelId = novelId;
}
}

View File

@ -8,14 +8,22 @@ import java.io.Serializable;
public class Chapter extends LitePalSupport implements Serializable {
private int id;
private int novelId;
private String novelPath;
private int novelId;//本地小说id
private String novelPath; //离线导入小说文件地址
private String chapterName;
private long novelChapterStartPos;
private String chapterUrl;
private int length;
private String chapterPath;
private String chapterPath; //缓存地址
private String domain; //目标 site
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public int getId() {
return id;

View File

@ -1,6 +1,7 @@
package com.novelbook.android.db;
import org.litepal.annotation.Column;
import org.litepal.crud.LitePalSupport;
import java.io.Serializable;
@ -8,13 +9,14 @@ import java.io.Serializable;
public class Novel extends LitePalSupport implements Serializable{
private int id;
@Column(unique = true, nullable = true)
private String novelId;
private String novelName;
private String name;
private String domain;
private String muluUrl;
private String novelPath;
private long lastReadPos;
private long lastReadChapt=1;
private int lastReadChapt=1;
private String charset;
private String novelType;
private String novelType2;
@ -44,12 +46,12 @@ public class Novel extends LitePalSupport implements Serializable{
this.novelId = novelId;
}
public String getNovelName() {
return novelName;
public String getName() {
return name;
}
public void setNovelName(String novelName) {
this.novelName = novelName;
public void setName(String novelName) {
this.name = novelName;
}
public String getDomain() {
@ -84,11 +86,11 @@ public class Novel extends LitePalSupport implements Serializable{
this.lastReadPos = lastReadPos;
}
public long getLastReadChapt() {
public int getLastReadChapt() {
return lastReadChapt;
}
public void setLastReadChapt(long lastReadChapt) {
public void setLastReadChapt(int lastReadChapt) {
this.lastReadChapt = lastReadChapt;
}

View File

@ -0,0 +1,98 @@
package com.novelbook.android.db;
import org.litepal.annotation.Column;
import org.litepal.crud.LitePalSupport;
public class SiteRule extends LitePalSupport {
private int id;
private String name;
@Column(unique = true, nullable = false)
private String domain;
private String chapterUrlRegexOnMulu;
private String chapterContentDumpRegex;
private String chapterContentRegex;
private String muluUrlPattern;
private String chapterUrlPattern;
private long miniInterval4AccessChapter;
private String[] headers;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getChapterContentRegex() {
return chapterContentRegex;
}
public void setChapterContentRegex(String chapterContentRegex) {
this.chapterContentRegex = chapterContentRegex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public String getChapterUrlRegexOnMulu() {
return chapterUrlRegexOnMulu;
}
public void setChapterUrlRegexOnMulu(String chapterUrlRegexOnMulu) {
this.chapterUrlRegexOnMulu = chapterUrlRegexOnMulu;
}
public String getChapterContentDumpRegex() {
return chapterContentDumpRegex;
}
public void setChapterContentDumpRegex(String chapterContentDumpRegex) {
this.chapterContentDumpRegex = chapterContentDumpRegex;
}
public String getMuluUrlPattern() {
return muluUrlPattern;
}
public void setMuluUrlPattern(String muluUrlPattern) {
this.muluUrlPattern = muluUrlPattern;
}
public String getChapterUrlPattern() {
return chapterUrlPattern;
}
public void setChapterUrlPattern(String chapterUrlPattern) {
this.chapterUrlPattern = chapterUrlPattern;
}
public long getMiniInterval4AccessChapter() {
return miniInterval4AccessChapter;
}
public void setMiniInterval4AccessChapter(long miniInterval4AccessChapter) {
this.miniInterval4AccessChapter = miniInterval4AccessChapter;
}
public String[] getHeaders() {
return headers;
}
public void setHeaders(String[] headers) {
this.headers = headers;
}
}

View File

@ -289,7 +289,7 @@ public class DirectoryFragment extends Fragment implements View.OnClickListener
@Override
public void onResume() {
super.onResume();
bookLists = LitePal.findAll(Novel.class);
bookLists = LitePal.where("isOnShelf=?","1").find( Novel.class);
listAdapter.notifyDataSetChanged();
}
@ -321,8 +321,9 @@ public class DirectoryFragment extends Fragment implements View.OnClickListener
for (ListItem item : checkItems) {
Novel bookList = new Novel();
String bookName = FileUtils.getFileName(item.thumb);
bookList.setNovelName(bookName);
bookList.setName(bookName);
bookList.setNovelPath(item.thumb);
bookList.setOnShelf(true);
bookLists.add(bookList);
}
SaveBookToSqlLiteTask mSaveBookToSqlLiteTask = new SaveBookToSqlLiteTask();
@ -363,7 +364,7 @@ public class DirectoryFragment extends Fragment implements View.OnClickListener
protected Integer doInBackground(List<Novel>... params) {
List<Novel> bookLists = params[0];
for (Novel bookList : bookLists){
List<Novel> books = LitePal.where("bookpath = ?", bookList.getNovelPath()).find(Novel.class);
List<Novel> books = LitePal.where("novelPath = ?", bookList.getNovelPath()).find(Novel.class);
if (books.size() > 0){
repeatBookList = bookList;
return REPEAT;
@ -390,12 +391,12 @@ public class DirectoryFragment extends Fragment implements View.OnClickListener
case SUCCESS:
msg = "导入书本成功";
checkItems.clear();
bookLists = LitePal.findAll(Novel.class);
bookLists = LitePal.where("isOnShelf=?","1").find( Novel.class);//LitePal.findAll(Novel.class);
listAdapter.notifyDataSetChanged();
changgeCheckBookNum();
break;
case REPEAT:
msg = "书本" + repeatBookList.getNovelName() + "重复了";
msg = "书本" + repeatBookList.getName() + "重复了";
break;
}
@ -636,7 +637,7 @@ public class DirectoryFragment extends Fragment implements View.OnClickListener
public void onClick(DialogInterface dialog, int which) {
Novel bookList = new Novel();
String bookName = FileUtils.getFileName(path);
bookList.setNovelName(bookName);
bookList.setName(bookName);
bookList.setNovelPath(path);
boolean isSave = false;
@ -649,7 +650,7 @@ public class DirectoryFragment extends Fragment implements View.OnClickListener
if (!isSave){
bookList.save();
}
ReadActivity.openBook(bookList,null,getActivity());
ReadActivity.openBook(bookList, getActivity());
}
}).show();
}

View File

@ -34,13 +34,18 @@ public interface HttpApi {
@Streaming
Call<ResponseBody> downloadFile(@Url String fileUrl);
//http://xiaoshuofenxiang.com/api/g/
@GET("g")
Observable<ResponseBody> getMasterDomain();
//http://xiaoshuofenxiang.com/api/n/f2619820112625133c14dcb170f5e092.json
@GET("n/{id}.json")
Observable<ResponseBody> getNovel(@Path("id") String novelId);
//http://xiaoshuofenxiang.com/api/n/f2619820112625133c14dcb170f5e092.mulu-urls.json
@GET("n/{id}.mulu-urls.json")
Observable<ResponseBody> getNovelMulu(@Path("id") String novelId);
Observable<ResponseBody> getNovelSites(@Path("id") String novelId);
@GET("s/{siteName}.json")
Observable<ResponseBody> getNovelRegex(@Path("siteName") String siteName);
//http://xiaoshuofenxiang.com/api/s/www.qu.la.json
@GET("s/{siteDomain}.json")
Observable<ResponseBody> getNovelRule(@Path("siteDomain") String siteDomain);
}

View File

@ -22,9 +22,12 @@ public class BookSubscribe {
Observable<ResponseBody> observable = HttpMethods.getInstance().getHttpApi().getNovel(novelId);
HttpMethods.getInstance().toSubscribe(observable, subscriber);
}
public static void getNovelMulu(String novelId,DisposableObserver<ResponseBody> subscriber){
Observable<ResponseBody> observable = HttpMethods.getInstance().getHttpApi().getNovelMulu(novelId);
public static void getNovelSites(String novelId,DisposableObserver<ResponseBody> subscriber){
Observable<ResponseBody> observable = HttpMethods.getInstance().getHttpApi().getNovelSites(novelId);
HttpMethods.getInstance().toSubscribe(observable, subscriber);
}
public static void getSiteRule(String domain,DisposableObserver<ResponseBody> subscriber){
Observable<ResponseBody> observable = HttpMethods.getInstance().getHttpApi().getNovelRule(domain);
HttpMethods.getInstance().toSubscribe(observable, subscriber);
}
}

View File

@ -39,7 +39,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class HttpMethods {
public String TAG = "HttpMethods";
public static final String CACHE_NAME = "yourApkName";
public static final String CACHE_NAME = "ZHUIKE";
public static String BASE_URL = URLConstant.BASE_URL;
private static final int DEFAULT_CONNECT_TIMEOUT = 30;
private static final int DEFAULT_WRITE_TIMEOUT = 30;

View File

@ -8,6 +8,8 @@ import com.novelbook.android.MyApp;
import java.util.Random;
import okhttp3.Call;
/**
* Created by 眼神 on 2018/3/27.
@ -86,7 +88,19 @@ public class NetUtil {
return -1;
}
public static void cancelRequest(String tag) {
for (Call call : HttpMethods.getOkClient().dispatcher().queuedCalls()) {
if (call.request().tag().equals(tag))
call.cancel();
}
//B) go through the running calls and cancel if the tag matches:
for (Call call : HttpMethods.getOkClient().dispatcher().runningCalls()) {
if (call.request().tag().equals(tag))
call.cancel();
}
}
public static String getUserAgent(){
String[] uas = {

View File

@ -75,17 +75,17 @@ import butterknife.ButterKnife;
{
holder.tvCateName .setText(mDatas.get(0).getNovelType());
holder.tvTitle.setText(mDatas.get(0).getNovelName());
holder.tvTitle.setText(mDatas.get(0).getName());
holder.tvAuthor.setText(mDatas.get(0).getAuthor());
holder.tvCate.setText(mDatas.get(0).getNovelType());
holder.tvDesc.setText(mDatas.get(0).getDescription());
holder.tvTitle2.setText(mDatas.get(1).getNovelName());
holder.tvTitle2.setText(mDatas.get(1).getName());
holder.tvAuthor2.setText(mDatas.get(1).getAuthor());
holder.tvCate2.setText(mDatas.get(1).getNovelType());
holder.tvDesc2.setText(mDatas.get(1).getDescription());
holder.tvTitle3.setText(mDatas.get(2).getNovelName());
holder.tvTitle3.setText(mDatas.get(2).getName());
holder.tvAuthor3.setText(mDatas.get(2).getAuthor());
holder.tvCate3.setText(mDatas.get(2).getNovelType());
holder.tvDesc3.setText(mDatas.get(2).getDescription());

View File

@ -1,22 +1,36 @@
package com.novelbook.android.utils;
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.os.Environment;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.google.gson.Gson;
import com.novelbook.android.MyApp;
import com.novelbook.android.bean.Cache;
import com.novelbook.android.bean.NovelSites;
import com.novelbook.android.bean.Site;
import com.novelbook.android.db.SiteRule;
import com.novelbook.android.db.Chapter;
import com.novelbook.android.db.Novel;
import com.novelbook.android.netsubscribe.BookSubscribe;
import com.novelbook.android.netutils.HttpMethods;
import com.novelbook.android.netutils.OnSuccessAndFaultListener;
import com.novelbook.android.netutils.OnSuccessAndFaultSub;
import org.json.JSONException;
import org.json.JSONObject;
import org.litepal.LitePal;
import java.io.Console;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -25,20 +39,32 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class BookUtil {
public static final String TAG ="BookUtil";
private static final String storagePath = Environment.getExternalStorageDirectory() + "/zhuike";
private static final String storagePath = FileUtils.getDiskCacheDir(MyApp.applicationContext);//Environment.getExternalStorageDirectory() + "/zhuike";
private static final String cachedPath = storagePath + "/cache/";
private static final String chapterPath = storagePath + "/chapter/";
private static final String charachterType = "utf-8";//"UTF-16LE";
private Context mContext;
private ProgressDialog progressDialog;
private MuluStatus mMuluStatus; //目录是否下载完成
private Gson gson = new Gson();
public void setContext(Context context) {
this.mContext = context;
}
//存储的字符数
public static final int cachedSize = 30000;
// protected final ArrayList<WeakReference<char[]>> myArray = new ArrayList<>();
@ -47,6 +73,9 @@ public class BookUtil {
protected final ArrayList<Cache> myArray = new ArrayList<>();
//目录
private List<Chapter> mChapters = new ArrayList<>();
//当前章节
// private Chapter mCurrentChapter;
public List<Chapter> getChapters() {
return mChapters;
}
@ -69,14 +98,70 @@ public class BookUtil {
private long position;
private Novel mNovel;
private Chapter mChapter;
public Chapter getChapter() {
return mChapter;
public void setNovel(Novel novel) {
this.mNovel = novel;
}
public void setChapter(Chapter chapter) {
this.mChapter = chapter;
//当前目录网站列表
private NovelSites mNovelSites;
//当前目录网站
private Site mSite;
private SiteRule mSiteRule;
public void setNovelSites(NovelSites nvs) {
this.mNovelSites = nvs;
if(nvs.getSites().length ==0){
return;
}
mSite =nvs.getSites()[0];
if(nvs.getSites().length > 0)
for (Site site:nvs.getSites() ) {
if(site.getSelectedByDefault()){
mSite = site;
break;
}
}
File file = new File(getChapterPath() +mSite.getDomain());
if(!file.exists()){
file.mkdir();
}
getSiteRule();
}
private void getSiteRule() {
mSiteRule = null;
BookSubscribe.getSiteRule(mSite.getDomain(),new OnSuccessAndFaultSub(new OnSuccessAndFaultListener() {
@Override
public void onSuccess(String result) {
//成功
SiteRule sr = (SiteRule)gson.fromJson(result,SiteRule.class);
List<SiteRule> srs = LitePal.where("domain=?",sr.getDomain()).limit(1).find(SiteRule.class);
long id = srs.size()==1 ?srs.get(0).getId() :0;
if(id>0 ){
sr.update(id);
// mSiteRule =LitePal.find(SiteRule.class,id);
}else
{
sr.save();
}
mSiteRule =sr;
readChaptersAsync();
}
@Override
public void onFault(String errorMsg) {
//失败
}
},mContext));
}
public void setChapterNo(int chapterNo) {
@ -99,6 +184,26 @@ public class BookUtil {
checkAndCreateDir(chapterPath);
checkAndCreateDir(cachedPath);
}
enum MuluStatus{
isDownloading,
isDone,
failed
}
private void showProgressDialog() {
if ( null == progressDialog) {
progressDialog =new ProgressDialog(mContext);
}
progressDialog.show();
}
private void dismissProgressDialog() {
if ( null != progressDialog) {
progressDialog.dismiss();
}
}
private void checkAndCreateDir(String path){
@ -108,7 +213,7 @@ public class BookUtil {
}
}
public synchronized void openBook(Novel novel) throws IOException {
public synchronized void openBook(Novel novel) throws IOException, InterruptedException {
this.mNovel = novel;
//如果当前缓存不是要打开的书本就缓存书本同时删除缓存
@ -118,8 +223,6 @@ public class BookUtil {
boolean isOnShelf = isLocalImport || novel.isOnShelf();
boolean isLoadChaptsFromRemote = !isLocalImport ;// && !novel.isFinished() ; //是否从目标网站下载目录
if(isLocalImport) {
mChapters = LitePal.where("novelId=?", mNovel.getId() + "").find(Chapter.class);
@ -129,7 +232,7 @@ public class BookUtil {
}
chaptCache = new HashMap<Integer, Cache>();
if (mChapters.isEmpty()) { //1. 首次打开 本地导入的书 2. 首次打开 未缓存的在线小说
if (mChapters.isEmpty()) { //1. 首次打开 本地导入的书
if (bookPath == null || !bookPath.equals(mNovel.getNovelPath())) {
cleanCacheFile();
@ -138,11 +241,136 @@ public class BookUtil {
cacheBook();
}
}
}else{ //读取目录列表
MuluStatus m = mMuluStatus;
Log.d(TAG,String.format("mulu on Site %s download status %s",mSite.getDomain(),mMuluStatus));
while(mMuluStatus == MuluStatus.isDownloading){
Thread.sleep(50);
}
}
}
// String getMuluUrl() {
// return "https://www.qu.la/book/390/";
// }
/* void readChapters( String url){
Request request = getTagRequest(url);
ResponseBody body =null;
try {
long startTime= new Date().getTime();
Log.d(TAG,String.format("loadChaptContent----start download %s 目录 from %s", mNovel.getName() ,url ));
Response response = HttpMethods.getOkClient().newCall(request).execute();
Log.d(TAG,String.format("loadChaptContent----end download %s 目录, 目录数量 %s, cost %s", mNovel.getName() , mChapters.size(), new Date().getTime() -startTime ));
startTime= new Date().getTime();
body = response.body();
String bodyStr = body.string();
Log.d(TAG, "onResponse: " +bodyStr);
buildCharacters(bodyStr,url);
Log.d(TAG,String.format("loadChaptContent----end build %s 目录, 目录数量 %s, cost %s", mNovel.getName() , mChapters.size(), new Date().getTime() -startTime ));
} catch (IOException e) {
e.printStackTrace();
}finally {
if(body!=null){
body.close();;
}
}
}*/
void readChaptersAsync( ) {
String url = mSite.getMuluUrl();
Request request = getTagRequest(url);
mMuluStatus = MuluStatus.isDownloading;
long startTime= new Date().getTime();
Log.d(TAG,String.format("loadChaptContent----start download %s 目录 from %s", mNovel.getName() ,url ));
HttpMethods.getOkClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "onFailure: " + e.getMessage());
if( mNovelSites.getSites().length ==1){
mMuluStatus = MuluStatus.failed;
return;
}
//try next site
for(Site st : mNovelSites.getSites() ){
if(!st.getDomain().equals(mSite.getDomain())){
mSite =st;
break;
}
}
// readChaptersAsync();
getSiteRule();
}
@Override
public void onResponse(Call call, Response response){
ResponseBody body = response.body();
if (body != null) {
try {
String bodyStr = body.string();
Log.d(TAG, "onResponse: " +bodyStr);
Log.d(TAG,String.format("loadChaptContent----end download %s 目录, 目录数量 %s, cost %s", mNovel.getName() , mChapters.size(), new Date().getTime() -startTime ));
long startTime2= new Date().getTime();
buildCharacters(bodyStr,url);
Log.d(TAG,String.format("loadChaptContent----end download %s 目录, 目录数量 %s, cost %s", mNovel.getName() , mChapters.size(), new Date().getTime() -startTime2 ));
mMuluStatus = MuluStatus.isDone;
} catch (IOException e) {
e.printStackTrace();
}finally {
body.close();
}
}
}
});
}
void buildCharacters( String content ,String url){
try {
JSONObject siteJson = new JSONObject();
siteJson.put("chapterUrlPattern", mSiteRule.getChapterUrlPattern());
siteJson.put("chapterUrlRegexOnMulu", mSiteRule.getChapterUrlRegexOnMulu());//示例接口表达式有问题
siteJson.put("chapterUrlRegexOnMulu", "<dd> <a[^>]*href=\"(/book/[\\d]+/[\\d]+\\.html)\">([^<]+)</a></dd>");
mChapters = NovelParseUtil.getChapters(mSite.getDomain(),url, content, siteJson);
Log.d(TAG,String.format("mulu on Site %s download status %s",mSite.getDomain(),mMuluStatus));
/* if (mChapters != null) {
int lastReadChapt = mNovel.getLastReadChapt();
// int index =lastReadChapt*2-2;
lastReadChapt = lastReadChapt >=mChapters.size() ? mChapters.size() -1:lastReadChapt;
lastReadChapt = lastReadChapt <=0 ? 1:lastReadChapt;
mCurrentChapter =mChapters.get(lastReadChapt-1);
}*/
} catch (JSONException e) {
// } catch (JSONException | IOException e) {
Log.d(TAG,String.format("mulu on Site %s download status %s",mSite.getDomain(),mMuluStatus));
e.printStackTrace();
} finally {
// result.close();
// if (result2 != null) result2.close();
}
}
private void cleanCacheFile(){
@ -464,7 +692,7 @@ public class BookUtil {
}
public List<Chapter> getDirectoryList(){
public List<Chapter> getmChapters(){
return mChapters;
}
@ -473,14 +701,21 @@ public class BookUtil {
}
protected String fileName(int index) {
return cachedPath + bookName + index ;
return cachedPath + mNovel.getName() + index ;
}
protected String fileChapterName(int chaptId ) {
if(mSite!=null){
return getChapterPath() +mSite.getDomain()+"/"+ chaptId ;
}
return getChapterPath() + chaptId ;
}
String getChapterPath(){
File file = new File(chapterPath +mNovel.getId());
if(!file.exists()){
file.mkdir();
}
return chapterPath +mNovel.getId()+"/";
}
@ -517,6 +752,28 @@ public class BookUtil {
return block;
}
boolean isDownloadChapt =false;
void setDownloadFlag(boolean flag){
isDownloadChapt = flag;
}
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
int wt = msg.what;
if (msg.what == 123) {
isDownloadChapt =true;
}else if(msg.what==1){
Toast.makeText(mContext,"网络错误",Toast.LENGTH_LONG).show();
}
}
};
private Map<Integer,Cache> chaptCache = new HashMap<Integer,Cache>();
//获取chapter 缓存
@ -526,39 +783,37 @@ public class BookUtil {
block = chaptCache .get(index).getData().get();
}
if (block == null) {
// cleanCacheFile(); //to remove
try {
File file = new File(fileChapterName(index));
if(!file.exists()) {
/* 章节内容没有缓存在本地
1. 根据本地的章节网络地址信息读取章节内容到本地若读取失败则
2. 查询主服务器若有地址更新则更新本地信息并重复1若没有更新地址则地址无效返回章节内容正待手打
*/
Chapter chapter = mChapters.get(index);
String url = chapter.getChapterUrl();
Log.d(TAG,String.format("loadChaptContent----start %s" ,new Date().toString() ));
JSONObject siteJson = new JSONObject();
siteJson.put("chapterContentRegex", "<div id=\"content\">([\\s\\S]+?)</div>");
siteJson.put("chapterContentDumpRegex", "<script>chaptererror();</script>");
Request request = new Request.Builder()
.url(url)
// .header("User-Agent", "OkHttp Example")
.build();
Response response = HttpMethods.getOkClient().newCall(request).execute();
String content = NovelParseUtil.getChapterContent(response.body().string(), siteJson);
loadChaptContent(index);
char[] buf = content.toCharArray();
try {
file.createNewFile();
final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileChapterName(index)), "UTF-16LE"); // UTF-16LE utf-8 文件小
writer.write(buf);
writer.close();
} catch (IOException e) {
throw new RuntimeException("Error during writing " + fileName(index));
showProgressDialog();//why not show
int slepttime =0;
while(!isDownloadChapt){
Thread.sleep(50);
slepttime+=50;
}
dismissProgressDialog();
Log.d(TAG,String.format("loadChaptContent slept %s for downloading %s ",slepttime, mChapters.get(index -1).getChapterName() ));
}
if(!file.exists()) {
String error = "网络错误";
return error.toCharArray();
}
if(mChapters.size() > index ) {
File file2 = new File(fileChapterName(index+1));
if(!file2.exists()) {
loadChaptContent(index + 1);
}
}
@ -583,6 +838,8 @@ public class BookUtil {
throw new RuntimeException("Error during reading " + fileChapterName(index));
} catch (JSONException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
Cache cache = new Cache();
cache.setSize(block.length);
@ -592,7 +849,96 @@ public class BookUtil {
}
return block;
}
private void loadChaptContent(int index) throws JSONException, InterruptedException {
/* 章节内容没有缓存在本地
1. 根据本地的章节网络地址信息读取章节内容到本地若读取失败则
2. 查询主服务器若有地址更新则更新本地信息并重复1若没有更新地址则地址无效返回章节内容正待手打
*/
//
if(mChapters ==null || mChapters.size() ==0){
return;
}
Chapter chapter = mChapters.get(index -1);
String url = chapter.getChapterUrl();
if( TextUtils.isEmpty( url)){
return ;
}
long startTime= new Date().getTime();
Log.d(TAG,String.format("loadChaptContent----start download %s from %s", chapter.getChapterName() ,url ));
setDownloadFlag(false);
JSONObject siteJson = new JSONObject();
siteJson.put("chapterContentRegex", mSiteRule.getChapterContentRegex());
siteJson.put("chapterContentDumpRegex", mSiteRule.getChapterContentDumpRegex());
Request request = getTagRequest(url);
HttpMethods.getOkClient().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
handler.sendEmptyMessage(123);
setDownloadFlag(true);
Log.d(TAG, "loadChaptContent---- onFailure: " + e.getMessage());
throw new RuntimeException("Error during writing " + fileChapterName( index));
}
@Override
public void onResponse(Call call, Response response){
ResponseBody body = response.body();
if (body != null ) {
if(response.code()!=200){
setDownloadFlag(true);
handler.sendEmptyMessage(1);
return;
}
try {
String bodyStr = body.string();
String title = chapter.getChapterName();
String chapterContent = title+ "\n" + NovelParseUtil.getChapterContent(bodyStr, siteJson);
char[] buf = chapterContent.toCharArray();
File file = new File(fileChapterName(index));
file.createNewFile();
final OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(fileChapterName(index)), charachterType);//"UTF-16LE"); // UTF-16LE utf-8 文件小
writer.write(buf);
writer.close();
} catch (IOException | JSONException e) {
e.printStackTrace();
throw new RuntimeException("Error during writing " + fileChapterName( index));
}
finally {
body.close();
handler.sendEmptyMessage(123);
setDownloadFlag(true);
}
chapter.setNovelId(mNovel.getId());
chapter.setChapterPath(fileChapterName(index));
chapter.save();
Log.d(TAG,String.format("loadChaptContent---- finished download %s, cost time %s ,content path %s ", chapter.getChapterName(), new Date().getTime() -startTime ,chapter.getChapterPath() ));
}
}
});
}
private Request getTagRequest(String url) {
return new Request.Builder()
.tag(mNovel.getNovelId()) //标记 请求的tag,切换小说或离开小说界面(BookActivity) 取消未执行完毕的 此tag的所有请求
.url(url)
// .header("User-Agent", "OkHttp Example")
.build();
}
public boolean isChapterTitle(String line){
return (line.length() <= 30 && (line.matches(".*第.{1,8}章.*") || line.matches(".*第.{1,8}节.*"))) ;
}
}

View File

@ -0,0 +1,30 @@
package com.novelbook.android.utils;
import com.novelbook.android.db.Novel;
import org.json.JSONException;
import org.json.JSONObject;
public class GsonUtil {
public static Novel getNovel(String json){
Novel nv = new Novel();
try {
JSONObject jsonObject = new JSONObject(json);
nv.setNovelId(jsonObject.getString("novelId"));
nv.setLastUpateTime(jsonObject.getLong("lastUpateTime"));
nv.setAuthor(jsonObject.getString("author"));
nv.setName(jsonObject.getString("name"));
nv.setCover(jsonObject.getString("cover"));
nv.setNovelType(jsonObject.getString("novelType"));
nv.setNovelType2(jsonObject.getString("novelType2"));
nv.setLastestChapterName(jsonObject.getString("lastestChapterName"));
return nv;
} catch (JSONException e) {
e.printStackTrace();
}
return nv;
}
}

View File

@ -0,0 +1,22 @@
package com.novelbook.android.utils;
import com.novelbook.android.bean.NovelSites;
import com.novelbook.android.db.SiteRule;
import com.novelbook.android.db.Chapter;
import com.novelbook.android.db.Novel;
import java.util.List;
import java.util.Map;
public class NovelConstants {
public static Novel CURRENT_NOVEL;
public static NovelSites CURRENT_NOVEL_SITES;
public static List<Chapter> CURRENT_CHPATERS;
public static Map<String, SiteRule> SiteRules= new FIFOMap<String, SiteRule>(100);;
}

View File

@ -1,16 +1,55 @@
package com.novelbook.android.utils;
import com.novelbook.android.db.Chapter;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class NovelParseUtil {
private static final String A_Regex = "<a[^>]+href[\\s]*=[\\s]*['\"]?([^'\"]+)['\"\\s]?[^>]*>([^<]+)<";
public static String[] getChapters(String muluUrl, String html, JSONObject siteJson) throws JSONException {
public static String[] getChaptersArray(String muluUrl, String html, JSONObject siteJson) throws JSONException {
Map<String, String> muluMap = getChaptersMap(muluUrl, html, siteJson);
String[] values = new String[muluMap.size() * 2];
Set<Map.Entry<String, String>> es = muluMap.entrySet();
int pos = values.length - 2;
for (Map.Entry<String, String> e : es) {
values[pos] = e.getKey();
values[pos + 1] = e.getValue();
pos -= 2;
}
return values;
}
public static List<Chapter> getChapters(String domain,String muluUrl, String html, JSONObject siteJson) throws JSONException {
Map<String, String> muluMap = getChaptersMap(muluUrl, html, siteJson);
Chapter[] tmp = new Chapter[muluMap.size()];
Set<Map.Entry<String, String>> es = muluMap.entrySet();
int pos = tmp.length - 1;
for (Map.Entry<String, String> e : es) {
Chapter chapter = new Chapter();
chapter.setChapterUrl( e.getKey());
chapter.setChapterName( e.getValue());
chapter.setDomain(domain);
tmp[pos--] =chapter;
}
List<Chapter> values = new ArrayList<Chapter>(Arrays.asList(tmp));
return values;
}
public static Map<String, String> getChaptersMap(String muluUrl, String html, JSONObject siteJson) throws JSONException {
String chapterUrlRegexOnMulu = siteJson.getString("chapterUrlRegexOnMulu");
String chapterUrlPattern = siteJson.getString("chapterUrlPattern");
@ -34,16 +73,8 @@ public class NovelParseUtil {
muluMap.put(href, name);
}
String[] values = new String[muluMap.size() * 2];
return muluMap;
Set<Map.Entry<String, String>> es = muluMap.entrySet();
int pos = values.length - 2;
for (Map.Entry<String, String> e : es) {
values[pos] = e.getKey();
values[pos + 1] = e.getValue();
pos -= 2;
}
return values;
}
public static String getChapterContent(String html, JSONObject siteJson) throws JSONException {

View File

@ -13,6 +13,7 @@ import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
@ -20,8 +21,10 @@ import android.widget.Toast;
import com.novelbook.android.Config;
import com.novelbook.android.R;
import com.novelbook.android.bean.NovelSites;
import com.novelbook.android.db.Chapter;
import com.novelbook.android.db.Novel;
import com.novelbook.android.netutils.NetUtil;
import com.novelbook.android.view.PageWidget;
import org.litepal.LitePal;
@ -139,8 +142,12 @@ public class PageFactory {
private Novel mBook;
//书的目录列表
private List<Chapter> mChapters;
//当前章节
// private Chapter mCurrentChapter;
//书本章节
private int currentChapter = 0;
//当前电量
private int level = 0;
private BookUtil mBookUtil;
@ -218,7 +225,7 @@ public class PageFactory {
}
private PageFactory(Context context) {
mBookUtil = new BookUtil();
// mBookUtil = new BookUtil();
mContext = context.getApplicationContext();
config = Config.getInstance();
//获取屏幕宽高
@ -343,7 +350,7 @@ public class PageFactory {
}
public void onDraw(Bitmap bitmap,List<String> m_lines,Boolean updateChapter) {
if (getDirectoryList().size() > 0 && updateChapter) {
if (getChapters().size() > 0 && updateChapter) {
currentChapter = getCurrentCharter();
}
//更新数据库进度
@ -352,11 +359,11 @@ public class PageFactory {
@Override
public void run() {
super.run();
values.put("begin",currentPage.getBegin());
values.put("biginChapt",currentChapter);
Log.d(TAG,String.format("begin to update book %s chapter%s bigin %s ",mBook.getNovelName(),currentChapter, currentPage.getBegin() ) );
values.put("lastReadPos",currentPage.getBegin());
values.put("lastReadChapt",currentChapter);
Log.d(TAG,String.format("begin to update book %s chapter%s bigin %s ",mBook.getName(),currentChapter, currentPage.getBegin() ) );
int rows = LitePal.update(Novel.class,values,mBook.getId());
Log.d(TAG,String.format("update book %s chapter%s bigin %s, result %s",mBook.getNovelName(),currentChapter, currentPage.getBegin(),rows) );
Log.d(TAG,String.format("update book %s chapter%s bigin %s, result %s",mBook.getName(),currentChapter, currentPage.getBegin(),rows) );
}
}.start();
}
@ -439,13 +446,18 @@ public class PageFactory {
//画书名
c.drawText(CommonUtil.subString(bookName,12), marginWidth ,statusMarginBottom + mBatterryFontSize, mBatterryPaint);
//画章
if (getDirectoryList().size() > 0) {
String charterName = CommonUtil.subString(getDirectoryList().get(currentChapter-1).getChapterName(),16);
int nChaterWidth = (int) mBatterryPaint.measureText(charterName) + 1;
c.drawText(charterName, mWidth - marginWidth - nChaterWidth, statusMarginBottom + mBatterryFontSize, mBatterryPaint);
}
/* String chapterName ="";
if(mCurrentChapter!=null){
chapterName = mCurrentChapter.getChapterName();
}else*/
if (getChapters().size() > 0)
{
String chapterName = CommonUtil.subString(getChapters().get(currentChapter-1).getChapterName(),16);
int nChaterWidth = (int) mBatterryPaint.measureText(chapterName) + 1;
c.drawText(chapterName, mWidth - marginWidth - nChaterWidth, statusMarginBottom + mBatterryFontSize, mBatterryPaint);
mBookPageWidget.postInvalidate();
}
mBookPageWidget.postInvalidate();
}
//向前翻页
@ -473,7 +485,7 @@ public class PageFactory {
if (currentPage.getEnd() >= mBookUtil.getBookLen()) {
Log.d(TAG,"已经是本章最后一页了");
m_islastPage =currentChapter == mBookUtil.getDirectoryList().size();
m_islastPage =currentChapter == mBookUtil.getChapters().size();
if ( m_islastPage){
Toast.makeText(mContext, "已经是最后一页了", Toast.LENGTH_SHORT).show();
return;
@ -496,29 +508,51 @@ public class PageFactory {
currentPage = cancelPage;
}
public void prepareBook(Novel book,NovelSites nvs,Context context){
if(mBook!=null &&mBook.getNovelId() !=book.getNovelId()){ //取消未上本书完成的web请求待验证效果
NetUtil.cancelRequest(mBook.getNovelId());
}
mBookUtil = new BookUtil();
this.mBookUtil.setContext(context);
this.mBookUtil.setNovel(book);
this.mBookUtil.setNovelSites(nvs);
}
/**
* 打开书本
* @throws IOException
*/
public void openBook(Novel book , List<Chapter> chapters ) throws IOException {
public void openBook(Novel book ,Context context) throws IOException {
if(null ==mBookUtil || !TextUtils.isEmpty(book.getNovelPath()) ){ //离线书籍重新初始化加载mBookUtil
mBookUtil = new BookUtil();
mBookUtil.setContext(context);
}
//清空数据
currentChapter = 0;
// m_mbBufLen = 0;
initBg(config.getDayOrNight());
if(mBook!=null &&mBook.getNovelId() !=book.getNovelId()){ //取消未上本书完成的web请求
NetUtil.cancelRequest(mBook.getNovelId());
}
this.mBook = book ;
bookPath = mBook.getNovelPath();
bookName =mBook.getNovelName();// FileUtils.getFileName(bookPath);
this.mChapters = chapters;
bookName =mBook.getName();// FileUtils.getFileName(bookPath);
// this.mCurrentChapter = chapter;
mStatus = Status.OPENING;
drawStatus(mBookPageWidget.getCurPage());
drawStatus(mBookPageWidget.getNextPage());
if (bookTask != null && bookTask.getStatus() != AsyncTask.Status.FINISHED){
bookTask.cancel(true);
}
bookTask = new BookTask();
bookTask.execute(book.getLastReadChapt(),book .getLastReadPos());
bookTask.execute((long)book.getLastReadChapt(),book .getLastReadPos());
}
private class BookTask extends AsyncTask<Long,Void,Boolean>{
@ -535,9 +569,6 @@ public class PageFactory {
PageFactory.mStatus = PageFactory.Status.FINISH;
// m_mbBufLen = mBookUtil.getBookLen();
mBookUtil.setChapterNo((int)chapter);
if(mChapters!=null) {
mBookUtil.setChapters(mChapters);
}
currentChaptPages = loadCurrentChapt((int)chapter);
currentPage = getPageForBegin(begin) ;// currentChaptPages.get(0);
// currentPage = getPageForBegin(begin);
@ -805,7 +836,7 @@ public class PageFactory {
//上一章
public void preChapter(){
if (mBookUtil.getDirectoryList().size() > 0){
if (mBookUtil.getChapters().size() > 0){
int num = currentChapter;
if (num ==1){
num =getCurrentCharter();
@ -831,7 +862,7 @@ public class PageFactory {
num =getCurrentCharter();
}
num ++;
if (num <= getDirectoryList().size()){
if (num <= getChapters().size()){
preChaptPages =currentChaptPages;
currentChapter = num;
@ -1032,13 +1063,19 @@ public class PageFactory {
}
//获取书本的章
public List<Chapter> getDirectoryList(){
return mBookUtil.getDirectoryList();
public List<Chapter> getChapters(){
return mBookUtil.getChapters();
}
public String getBookPath(){
return bookPath;
}
public String getBookName(){
return mBook.getName();
}
public Novel getNovle(){
return mBook;
}
//是否是第一页
public boolean isfirstPage() {
return m_isfirstPage;

View File

@ -152,6 +152,7 @@
>
<Button
android:id="@+id/btnShelf"
android:text="加入书架"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -87,6 +87,7 @@
/>
<TextView
style="@style/TextViewTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -95,6 +96,7 @@
</LinearLayout>
<TextView
android:id="@+id/txtDesc2"
style="@style/TextViewDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -164,6 +166,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/txtLatestCate"
style="@style/TextViewDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -171,16 +174,8 @@
/>
<TextView
style="@style/TextViewDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第一万两千八百章 天冷好个秋" />
<TextView
style="@style/TextViewDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第一万两千八百零一章 天冷好个秋2" />
</LinearLayout>
<TextView

View File

@ -200,7 +200,7 @@
<style name="TextViewHead.bold">
<item name="android:textStyle">bold</item>
<item name="android:textSize">18sp</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />