diff --git a/.idea/misc.xml b/.idea/misc.xml
index e709b95..6f853a3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -21,7 +21,7 @@
-
+
@@ -41,7 +41,7 @@
-
+
@@ -52,6 +52,7 @@
+
diff --git a/README.md b/README.md
index 3ca9dd4..1e7f73b 100644
--- a/README.md
+++ b/README.md
@@ -22,9 +22,9 @@
* 官网:[https://reader.fycz.tk](https://reader.fycz.tk)
-* 反馈交流群:
+* 反馈交流:
* QQ群:[1085028304](https://jq.qq.com/?_wv=1027&k=6pcq8YBk)
- * Telegram:[https://t.me/FYReader](https://t.me/FYReader)
+ * QQ频道:[点击加入](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&inviteCode=2aP6ZQ&from=246610&biz=ka)
* [更新日志](./app/src/main/assets/updatelog.fy)
* [免责声明](./app/src/main/assets/disclaimer.fy)
diff --git a/app/release.md b/app/release.md
index e0f4764..e2efcf4 100644
--- a/app/release.md
+++ b/app/release.md
@@ -1,3 +1,5 @@
-* 1、开放用户登录\注册入口及同步书架(在v1.9.3版本关闭),感谢[@黑白人生]提供的服务器
-* 2、新增邮箱验证(用于找回密码),之前注册的帐号登录时需要绑定邮箱
-* 3、webdav同步书架改为从webdav恢复
\ No newline at end of file
+* 1、修复部分机型阅读界面切换日夜间闪退的问题
+* 2、优化搜索界面
+* 3、搜索界面新增书架书籍搜索
+* 4、修复朗读到章节最后一页停止的问题
+* 5、新增QQ频道,建议反馈请到QQ频道进行
\ No newline at end of file
diff --git a/app/src/main/assets/updatelog.fy b/app/src/main/assets/updatelog.fy
index 528d152..33539ec 100644
--- a/app/src/main/assets/updatelog.fy
+++ b/app/src/main/assets/updatelog.fy
@@ -1,7 +1,11 @@
-2022.01.07
+2022.01.17
风月读书v2.3.2
更新内容:
-1、修复MIUI13阅读界面切换日夜间闪退的问题
+1、修复部分机型阅读界面切换日夜间闪退的问题
+2、优化搜索界面
+3、搜索界面新增书架书籍搜索
+4、修复朗读到章节最后一页停止的问题
+5、新增QQ频道,建议反馈请到QQ频道进行
2021.12.11
风月读书v2.2.8
diff --git a/app/src/main/java/xyz/fycz/myreader/model/sourceAnalyzer/BookSourceManager.java b/app/src/main/java/xyz/fycz/myreader/model/sourceAnalyzer/BookSourceManager.java
index 6222116..8a0eac3 100644
--- a/app/src/main/java/xyz/fycz/myreader/model/sourceAnalyzer/BookSourceManager.java
+++ b/app/src/main/java/xyz/fycz/myreader/model/sourceAnalyzer/BookSourceManager.java
@@ -192,6 +192,10 @@ public class BookSourceManager {
DbManager.getDaoSession().getBookSourceDao().deleteInTx(sources);
}
+ public static boolean isBookSourceExist(BookSource source){
+ if (source == null) return false;
+ return DbManager.getDaoSession().getBookSourceDao().load(source.getSourceUrl()) != null;
+ }
public static String getBookSourceSort() {
switch (SharedPreUtils.getInstance().getInt("SourceSort", 0)) {
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java
index 60c44fe..7cc3fc7 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/AboutActivity.java
@@ -96,6 +96,12 @@ public class AboutActivity extends BaseActivity {
mClipboardManager.setPrimaryClip(mClipData);
ToastUtils.showSuccess("邮箱\"fengyuecanzhu@gmail.com\"已复制到剪切板");
});
+ binding.il.rlJoinQqChannel.setOnClickListener(v -> {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse("https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&inviteCode=2aP6ZQ&from=246610&biz=ka"));
+ startActivity(intent);
+ });
+
binding.il.rlShareLog.setOnClickListener(v -> DialogCreator.createCommonDialog(this, "分享崩溃日志",
"你是希望将日志上传到服务器,还是直接分享给他人?", true,
"上传服务器", "直接分享", (dialog, which) -> uploadCrashLog(), (dialog, which) -> shareCrashLog()));
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
index 7064b1e..f2e1ad5 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/ReadActivity.java
@@ -419,8 +419,10 @@ public class ReadActivity extends BaseActivity implements ColorPickerDialogListe
pagePos = pos;
saveLastChapterReadPosition();
}
- screenOffTimerStart();
- initMenu();
+ mHandler.post(()->{
+ screenOffTimerStart();
+ initMenu();
+ });
recordReadTime();
if (ReadAloudService.running) {
if (mPageLoader.hasChapterData(mChapters.get(mPageLoader.getChapterPos()))) {
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
index a424aa8..bbd443a 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/SearchBookActivity.java
@@ -49,6 +49,7 @@ import xyz.fycz.myreader.entity.Setting;
import xyz.fycz.myreader.greendao.entity.Book;
import xyz.fycz.myreader.greendao.entity.SearchHistory;
import xyz.fycz.myreader.greendao.entity.rule.BookSource;
+import xyz.fycz.myreader.greendao.service.BookService;
import xyz.fycz.myreader.greendao.service.SearchHistoryService;
import xyz.fycz.myreader.model.SearchEngine;
import xyz.fycz.myreader.model.mulvalmap.ConMVMap;
@@ -65,6 +66,7 @@ import xyz.fycz.myreader.util.utils.OkHttpUtils;
import xyz.fycz.myreader.util.utils.RxUtils;
import xyz.fycz.myreader.webapi.crawler.ReadCrawlerUtil;
import xyz.fycz.myreader.webapi.crawler.base.ReadCrawler;
+import xyz.fycz.myreader.widget.TagGroup;
/**
* @author fengyue
@@ -76,14 +78,17 @@ public class SearchBookActivity extends BaseActivity {
private SearchAdapter mSearchBookAdapter;
private String searchKey;//搜索关键字
- private List mSearchHistories = new ArrayList<>();
- private List mSuggestions = new ArrayList<>();
- private List mHotKeys = new ArrayList<>();
+ private List mSearchHistories;
+ private List mCurHistories = new ArrayList<>();
+ private final List mBookcase = new ArrayList<>();
+ private List mBooks;
+ private final List mSuggestions = new ArrayList<>();
+ private final List mHotKeys = new ArrayList<>();
+ private final List mBookcaseNames = new ArrayList<>();
+ private final List mHistoryNames = new ArrayList<>();
private SearchHistoryService mSearchHistoryService;
- private SearchHistoryAdapter mSearchHistoryAdapter;
-
private int allThreadCount;
private SearchEngine searchEngine;
@@ -96,13 +101,20 @@ public class SearchBookActivity extends BaseActivity {
private Disposable sugDis;
- private boolean showBooks;
+ private Disposable hisDis;
+ private Disposable caseDis;
+
+ private boolean showBooks;
private static String[] suggestion = {"第一序列", "大道朝天", "伏天氏", "终极斗罗", "我师兄实在太稳健了", "烂柯棋缘", "诡秘之主", "不朽凡人", "圣墟", "我是至尊", "龙王传说", "太古神王", "一念永恒", "雪鹰领主", "大主宰"};
private boolean showHot;
+ private boolean foldBookcase;
+ private boolean foldSuggest;
+ private boolean foldHistory;
+ private boolean needReGetHistory;
@Override
protected void bindView() {
@@ -120,7 +132,7 @@ public class SearchBookActivity extends BaseActivity {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
- if (hasFocus && binding.etSearchKey.getText().length() == 0){
+ if (hasFocus && binding.etSearchKey.getText().length() == 0) {
App.getHandler().postDelayed(() -> {
binding.etSearchKey.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
@@ -134,7 +146,12 @@ public class SearchBookActivity extends BaseActivity {
super.initData(savedInstanceState);
mSetting = SysManager.getSetting();
mSearchHistoryService = SearchHistoryService.getInstance();
+ mBooks = BookService.getInstance().getAllBooksNoHide();
showHot = !App.isDebug();
+ foldBookcase = SharedPreUtils.getInstance().getBoolean("foldBookcase");
+ foldSuggest = SharedPreUtils.getInstance().getBoolean("foldSuggest");
+ foldHistory = SharedPreUtils.getInstance().getBoolean("foldHistory");
+ needReGetHistory = true;
searchEngine = new SearchEngine();
searchEngine.setOnSearchListener(new SearchEngine.OnSearchListener() {
@Override
@@ -189,18 +206,13 @@ public class SearchBookActivity extends BaseActivity {
}
binding.rgSearchFilter.setOnCheckedChangeListener((group, checkedId) -> {
- int searchFilter;
- switch (checkedId) {
- case R.id.rb_all_search:
- default:
- searchFilter = 0;
- break;
- case R.id.rb_fuzzy_search:
- searchFilter = 1;
- break;
- case R.id.rb_precise_search:
- searchFilter = 2;
- break;
+ int searchFilter = 0;
+ if (checkedId == R.id.rb_all_search) {
+ searchFilter = 0;
+ } else if (checkedId == R.id.rb_fuzzy_search) {
+ searchFilter = 1;
+ } else if (checkedId == R.id.rb_precise_search) {
+ searchFilter = 2;
}
mSetting.setSearchFilter(searchFilter);
SysManager.saveSetting(mSetting);
@@ -225,6 +237,8 @@ public class SearchBookActivity extends BaseActivity {
if (StringHelper.isEmpty(searchKey)) {
search();
}
+ searchBookcase();
+ initHistoryList();
initSuggestionList();
}
@@ -237,6 +251,32 @@ public class SearchBookActivity extends BaseActivity {
stopSearch();
search();
});
+
+ binding.tgBookcase.setVisibility(foldBookcase ? View.GONE : View.VISIBLE);
+ binding.tvFlattenBookcase.setText(foldBookcase ? R.string.unfold : R.string.fold);
+ binding.tgSuggestBook.setVisibility(foldSuggest ? View.GONE : View.VISIBLE);
+ binding.tvFlattenSuggest.setText(foldSuggest ? R.string.unfold : R.string.fold);
+ binding.tgHistoryBooks.setVisibility(foldHistory ? View.GONE : View.VISIBLE);
+ binding.tvFlattenHistory.setText(foldHistory ? R.string.unfold : R.string.fold);
+
+ binding.rlBookcaseBar.setOnClickListener(v -> {
+ foldBookcase = !foldBookcase;
+ binding.tgBookcase.setVisibility(foldBookcase ? View.GONE : View.VISIBLE);
+ binding.tvFlattenBookcase.setText(foldBookcase ? R.string.unfold : R.string.fold);
+ SharedPreUtils.getInstance().putBoolean("foldBookcase", foldBookcase);
+ });
+ binding.rlSuggestBar.setOnClickListener(v -> {
+ foldSuggest = !foldSuggest;
+ binding.tgSuggestBook.setVisibility(foldSuggest ? View.GONE : View.VISIBLE);
+ binding.tvFlattenSuggest.setText(foldSuggest ? R.string.unfold : R.string.fold);
+ SharedPreUtils.getInstance().putBoolean("foldSuggest", foldSuggest);
+ });
+ binding.rlHistoryBar.setOnClickListener(v -> {
+ foldHistory = !foldHistory;
+ binding.tgHistoryBooks.setVisibility(foldHistory ? View.GONE : View.VISIBLE);
+ binding.tvFlattenHistory.setText(foldHistory ? R.string.unfold : R.string.fold);
+ SharedPreUtils.getInstance().putBoolean("foldHistory", foldHistory);
+ });
initHistoryList();
}
@@ -244,35 +284,43 @@ public class SearchBookActivity extends BaseActivity {
protected void initClick() {
super.initClick();
- //换一批点击事件
- binding.llRefreshSuggestBooks.setOnClickListener(new RenewSuggestionBook());
-
//搜索按钮点击事件
binding.tvSearchConform.setOnClickListener(view -> search());
//suggestion搜索事件
- binding.tgSuggestBook.setOnTagClickListener(tag -> {
+ binding.tgSuggestBook.setOnTagClickListener((tag, pos) -> {
binding.etSearchKey.setText(tag);
binding.etSearchKey.setSelection(tag.length());
search();
});
//历史记录搜索事件
- binding.lvHistoryList.setOnItemClickListener((parent, view, position, id) -> {
- binding.etSearchKey.setText(mSearchHistories.get(position).getContent());
- binding.etSearchKey.setSelection(mSearchHistories.get(position).getContent().length());
+ binding.tgHistoryBooks.setOnTagClickListener((tag, pos) -> {
+ binding.etSearchKey.setText(tag);
+ binding.etSearchKey.setSelection(tag.length());
search();
});
//清空历史记录
- binding.llClearHistory.setOnClickListener(v -> {
- mSearchHistoryService.clearHistory();
- initHistoryList();
+ binding.tvClearHistory.setOnClickListener(v -> {
+ DialogCreator.createCommonDialog(this, "清除搜索记录", "确定要清除全部搜索记录吗?",
+ true, (dialog, which) -> {
+ mSearchHistoryService.clearHistory();
+ initHistoryList();
+ }, null);
});
//清除单个历史记录
- binding.lvHistoryList.setOnItemLongClickListener((parent, view, position, id) -> {
- if (mSearchHistories.get(position) != null) {
- mSearchHistoryService.deleteHistory(mSearchHistories.get(position));
- initHistoryList();
+ binding.tgHistoryBooks.setLongClickDelete(true);
+ binding.tgHistoryBooks.setOnTagChangeListener(new TagGroup.OnTagChangeListener() {
+ @Override
+ public void onAppend(TagGroup tagGroup, String tag) {
+
+ }
+
+ @Override
+ public void onDelete(TagGroup tagGroup, String tag, int pos) {
+ if (mCurHistories.get(pos) != null) {
+ mSearchHistoryService.deleteHistory(mCurHistories.get(pos));
+ mSearchHistories.remove(mCurHistories.get(pos));
+ }
}
- return true;
});
binding.fabSearchStop.setOnClickListener(v -> {
@@ -445,7 +493,6 @@ public class SearchBookActivity extends BaseActivity {
initSuggestionList();
}
});
- ;
}
}
@@ -460,11 +507,10 @@ public class SearchBookActivity extends BaseActivity {
mSuggestions.clear();
if (StringHelper.isEmpty(searchKey)) {
if (mHotKeys.isEmpty()) {
- binding.llSuggestBooksView.setVisibility(View.GONE);
+ binding.llSuggestBook.setVisibility(View.GONE);
} else {
- binding.llSuggestBooksView.setVisibility(View.VISIBLE);
- binding.llRefreshSuggestBooks.setVisibility(View.VISIBLE);
- binding.tgSuggestBook.setTags2(mHotKeys.subList(0, mHotKeys.size() / 2));
+ binding.llSuggestBook.setVisibility(View.VISIBLE);
+ binding.tgSuggestBook.setTags2(mHotKeys);
}
} else {
String url = "https://newzxautocmp.reader.qq.com/BookSuggAll?key=" + searchKey;
@@ -480,13 +526,18 @@ public class SearchBookActivity extends BaseActivity {
@Override
public void onSuccess(@NotNull String s) {
parseSuggListByKey(s);
- binding.llRefreshSuggestBooks.setVisibility(View.GONE);
+ if (mSuggestions.isEmpty()) {
+ binding.llSuggestBook.setVisibility(View.GONE);
+ return;
+ } else {
+ binding.llSuggestBook.setVisibility(View.VISIBLE);
+ }
binding.tgSuggestBook.setTags2(mSuggestions);
}
@Override
public void onError(Throwable e) {
- binding.llSuggestBooksView.setVisibility(View.GONE);
+ binding.llSuggestBook.setVisibility(View.GONE);
}
});
}
@@ -527,32 +578,65 @@ public class SearchBookActivity extends BaseActivity {
}
}
- private class RenewSuggestionBook implements View.OnClickListener {
- @Override
- public void onClick(View v) {
- if (mHotKeys.size() > 0) {
- String[] s = binding.tgSuggestBook.getTags();
- if (s[0].equals(mHotKeys.get(0))) {
- binding.tgSuggestBook.setTags2(mHotKeys.subList(mHotKeys.size() / 2, mHotKeys.size()));
- } else {
- binding.tgSuggestBook.setTags2(mHotKeys.subList(0, mHotKeys.size() / 2));
- }
- }
- }
- }
-
/**
* 初始化历史列表
*/
private void initHistoryList() {
- mSearchHistories = mSearchHistoryService.findAllSearchHistory();
- if (mSearchHistories == null || mSearchHistories.size() == 0) {
- binding.llHistoryView.setVisibility(View.GONE);
- } else {
- mSearchHistoryAdapter = new SearchHistoryAdapter(this, R.layout.listview_search_history_item, mSearchHistories);
- binding.lvHistoryList.setAdapter(mSearchHistoryAdapter);
- binding.llHistoryView.setVisibility(View.VISIBLE);
+ if (hisDis != null) {
+ hisDis.dispose();
}
+ Single.create((SingleOnSubscribe) emitter -> {
+ mSearchHistories = mSearchHistoryService.findAllSearchHistory();
+ mHistoryNames.clear();
+ mCurHistories.clear();
+ if (mSearchHistories == null || mSearchHistories.size() == 0) {
+ emitter.onSuccess(false);
+ } else {
+ if (StringHelper.isEmpty(searchKey)) {
+ mCurHistories.addAll(mSearchHistories);
+ for (SearchHistory history : mSearchHistories) {
+ mHistoryNames.add(history.getContent());
+ }
+ } else {
+ for (SearchHistory history : mSearchHistories) {
+ String title = history.getContent();
+ int start = title.indexOf(searchKey);
+ if (start != -1) {
+ SpannableString spannableString = new SpannableString(title);
+ spannableString.setSpan(new ForegroundColorSpan(Color.RED),
+ start, start + searchKey.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ mHistoryNames.add(spannableString);
+ mCurHistories.add(history);
+ }
+ }
+ }
+ emitter.onSuccess(!mHistoryNames.isEmpty());
+ }
+ }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver() {
+
+ @Override
+ public void onSubscribe(Disposable d) {
+ addDisposable(d);
+ hisDis = d;
+ }
+
+ @Override
+ public void onSuccess(@NonNull Boolean aBoolean) {
+ if (aBoolean) {
+ binding.llHistoryBooks.setVisibility(View.VISIBLE);
+ binding.tgHistoryBooks.setTags2(mHistoryNames);
+ } else {
+ binding.llHistoryBooks.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ e.printStackTrace();
+ binding.llHistoryBooks.setVisibility(View.GONE);
+ }
+ });
+
}
/**
@@ -561,9 +645,67 @@ public class SearchBookActivity extends BaseActivity {
private void initSearchList() {
//initmBooksBean();
binding.rvSearchBooksList.setVisibility(View.VISIBLE);
- binding.llSuggestBooksView.setVisibility(View.GONE);
+ binding.llPreKeys.setVisibility(View.GONE);
}
+ /**
+ * 搜索书架
+ */
+ private void searchBookcase() {
+ if (caseDis != null) {
+ caseDis.dispose();
+ }
+ if (StringHelper.isEmpty(searchKey)) {
+ binding.llBookcase.setVisibility(View.GONE);
+ } else {
+ Single.create((SingleOnSubscribe) emitter -> {
+ mBookcase.clear();
+ mBookcaseNames.clear();
+ for (Book book : mBooks) {
+ String title = book.getName() + "-" + book.getAuthor();
+ if ("本地书籍".equals(book.getType())) {
+ title += "[本地]";
+ }
+ int start = title.indexOf(searchKey);
+ if (start != -1) {
+ SpannableString spannableString = new SpannableString(title);
+ spannableString.setSpan(new ForegroundColorSpan(Color.RED),
+ start, start + searchKey.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ mBookcaseNames.add(spannableString);
+ mBookcase.add(book);
+ }
+ }
+ emitter.onSuccess(!mBookcase.isEmpty());
+ }).compose(RxUtils::toSimpleSingle).subscribe(new MySingleObserver() {
+ @Override
+ public void onSubscribe(Disposable d) {
+ addDisposable(d);
+ caseDis = d;
+ }
+
+ @Override
+ public void onSuccess(@NonNull Boolean aBoolean) {
+ if (aBoolean) {
+ binding.llBookcase.setVisibility(View.VISIBLE);
+ binding.tgBookcase.setTags2(mBookcaseNames);
+ binding.tgBookcase.setOnTagClickListener((tag, pos) -> {
+ Intent intent = new Intent(SearchBookActivity.this, BookDetailedActivity.class);
+ BitIntentDataManager.getInstance().putData(intent, mBookcase.get(pos));
+ startActivity(intent);
+ });
+ } else {
+ binding.llBookcase.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ e.printStackTrace();
+ binding.llBookcase.setVisibility(View.GONE);
+ }
+ });
+ }
+ }
/**
* 获取搜索数据
@@ -595,7 +737,7 @@ public class SearchBookActivity extends BaseActivity {
stopSearch();
binding.rpb.setIsAutoLoading(false);
binding.rvSearchBooksList.setVisibility(View.GONE);
- binding.llSuggestBooksView.setVisibility(View.VISIBLE);
+ binding.llPreKeys.setVisibility(View.VISIBLE);
binding.fabSearchStop.setVisibility(View.GONE);
initHistoryList();
binding.rvSearchBooksList.setAdapter(null);
@@ -606,11 +748,11 @@ public class SearchBookActivity extends BaseActivity {
binding.rvSearchBooksList.setAdapter(mSearchBookAdapter);
binding.srlSearchBookList.setEnableRefresh(true);
binding.rvSearchBooksList.setVisibility(View.VISIBLE);
- binding.llSuggestBooksView.setVisibility(View.GONE);
- binding.llHistoryView.setVisibility(View.GONE);
+ binding.llPreKeys.setVisibility(View.GONE);
binding.fabSearchStop.setVisibility(View.VISIBLE);
getData();
mSearchHistoryService.addOrUpadteHistory(searchKey);
+ needReGetHistory = true;
//收起软键盘
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(binding.etSearchKey.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceEditActivity.java b/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceEditActivity.java
index b8681ef..018daf0 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceEditActivity.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/activity/SourceEditActivity.java
@@ -185,6 +185,15 @@ public class SourceEditActivity extends BaseActivity {
} else if (item.getItemId() == R.id.action_clear_cookie) {
DbManager.getDaoSession().getCookieBeanDao().deleteByKey(getSource().getSourceUrl());
ToastUtils.showSuccess("Cookie清除成功");
+ } else if (item.getItemId() == R.id.action_delete) {
+ if (BookSourceManager.isBookSourceExist(source)) {
+ BookSourceManager.removeBookSource(source);
+ setResult(Activity.RESULT_OK);
+ ToastUtils.showSuccess("书源删除成功");
+ finish();
+ } else {
+ ToastUtils.showWarring("当前书源暂未保存,无法删除");
+ }
}
return super.onOptionsItemSelected(item);
}
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java b/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
index ca4e67a..8989968 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/fragment/MineFragment.java
@@ -4,6 +4,7 @@ import static android.app.Activity.RESULT_OK;
import android.annotation.SuppressLint;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -389,8 +390,9 @@ public class MineFragment extends BaseFragment {
});
binding.mineRlFeedback.setOnClickListener(v -> {
- Intent intent = new Intent(getContext(), FeedbackActivity.class);
- getActivity().startActivity(intent);
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse("https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&inviteCode=2aP6ZQ&from=246610&biz=ka"));
+ startActivity(intent);
});
binding.mineRlDonate.setOnClickListener(v -> startActivity(new Intent(getActivity(), DonateActivity.class)));
diff --git a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
index 70d6079..1a7c4ed 100644
--- a/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
+++ b/app/src/main/java/xyz/fycz/myreader/ui/presenter/BookcasePresenter.java
@@ -999,12 +999,15 @@ public class BookcasePresenter implements BasePresenter {
if (!isAutoSyn) {
DialogCreator.createTipDialog(mMainActivity, "成功将书架同步至网络!");
}
+ } else if (result.getCode() == 216) {
+ ToastUtils.showWarring("当前应用版本过旧,同步书架失败,请更新应用后重试");
} else {
if (!isAutoSyn) {
DialogCreator.createTipDialog(mMainActivity, "同步失败,请重试!");
}
}
}
+
@Override
public void onError(Throwable e) {
if (!isAutoSyn) {
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/TagGroup.java b/app/src/main/java/xyz/fycz/myreader/widget/TagGroup.java
index 472d708..638ec6c 100644
--- a/app/src/main/java/xyz/fycz/myreader/widget/TagGroup.java
+++ b/app/src/main/java/xyz/fycz/myreader/widget/TagGroup.java
@@ -1,5 +1,6 @@
package xyz.fycz.myreader.widget;
+import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -32,6 +33,7 @@ import java.util.ArrayList;
import java.util.List;
import xyz.fycz.myreader.R;
+import xyz.fycz.myreader.widget.explosion_field.ExplosionField;
/**
* A TagGroup
is a special layout with a set of tags.
@@ -70,6 +72,9 @@ public class TagGroup extends ViewGroup {
private final float default_horizontal_padding;
private final float default_vertical_padding;
+ /** Indicate whether the tags are deleted by holding down. Default is false. */
+ private boolean isLongClickDelete;
+
/** Indicates whether this TagGroup is set up to APPEND mode or DISPLAY mode. Default is false. */
private boolean isAppendMode;
@@ -136,6 +141,8 @@ public class TagGroup extends ViewGroup {
/** Listener used to handle tag click event. */
private InternalTagClickListener mInternalTagClickListener = new InternalTagClickListener();
+ private ExplosionField mExplosionField;
+
public TagGroup(Context context) {
this(context, null);
}
@@ -156,6 +163,7 @@ public class TagGroup extends ViewGroup {
// Load styled attributes.
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TagGroup, defStyleAttr, R.style.TagGroup);
try {
+ isLongClickDelete = a.getBoolean(R.styleable.TagGroup_atg_isLongClickDelete, false);
isAppendMode = a.getBoolean(R.styleable.TagGroup_atg_isAppendMode, false);
inputHint = a.getText(R.styleable.TagGroup_atg_inputHint);
borderColor = a.getColor(R.styleable.TagGroup_atg_borderColor, default_border_color);
@@ -193,6 +201,18 @@ public class TagGroup extends ViewGroup {
}
}
+ @Override
+ protected void onAttachedToWindow() {
+ mExplosionField = ExplosionField.attach2Window((Activity) getContext());
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mExplosionField.clear();
+ super.onDetachedFromWindow();
+ }
+
/**
* Call this to submit the INPUT tag.
*/
@@ -492,8 +512,15 @@ public class TagGroup extends ViewGroup {
throw new IllegalStateException("Already has a INPUT tag in group.");
}
- final TagView newInputTag = new TagView(getContext(), TagView.STATE_INPUT, tag);
+ final TagView newInputTag = new TagView(getContext(), TagView.STATE_INPUT, tag, getChildCount());
newInputTag.setOnClickListener(mInternalTagClickListener);
+ newInputTag.setOnLongClickListener(v -> {
+ if (isLongClickDelete) {
+ deleteTag(newInputTag);
+ return true;
+ }
+ return false;
+ });
addView(newInputTag);
}
@@ -503,8 +530,15 @@ public class TagGroup extends ViewGroup {
* @param tag the tag to append.
*/
protected void appendTag(CharSequence tag) {
- final TagView newTag = new TagView(getContext(), TagView.STATE_NORMAL, tag);
+ final TagView newTag = new TagView(getContext(), TagView.STATE_NORMAL, tag, getChildCount());
newTag.setOnClickListener(mInternalTagClickListener);
+ newTag.setOnLongClickListener(v -> {
+ if (isLongClickDelete) {
+ deleteTag(newTag);
+ return true;
+ }
+ return false;
+ });
addView(newTag);
}
@@ -532,10 +566,19 @@ public class TagGroup extends ViewGroup {
mOnTagClickListener = l;
}
+ /**
+ * Set up whether the tags are deleted by holding down.
+ * @param longClickDelete
+ */
+ public void setLongClickDelete(boolean longClickDelete) {
+ isLongClickDelete = longClickDelete;
+ }
+
protected void deleteTag(TagView tagView) {
+ mExplosionField.explode(tagView);
removeView(tagView);
if (mOnTagChangeListener != null) {
- mOnTagChangeListener.onDelete(TagGroup.this, tagView.getText().toString());
+ mOnTagChangeListener.onDelete(TagGroup.this, tagView.getText().toString(), tagView.pos);
}
}
@@ -555,7 +598,7 @@ public class TagGroup extends ViewGroup {
*
* @param tag the deleted tag.
*/
- void onDelete(TagGroup tagGroup, String tag);
+ void onDelete(TagGroup tagGroup, String tag, int pos);
}
/**
@@ -567,7 +610,7 @@ public class TagGroup extends ViewGroup {
*
* @param tag The tag text of the tag that was clicked.
*/
- void onTagClick(String tag);
+ void onTagClick(String tag, int pos);
}
/**
@@ -630,6 +673,7 @@ public class TagGroup extends ViewGroup {
* The tag view click listener for internal use.
*/
class InternalTagClickListener implements OnClickListener {
+
@Override
public void onClick(View v) {
final TagView tag = (TagView) v;
@@ -656,7 +700,7 @@ public class TagGroup extends ViewGroup {
}
} else {
if (mOnTagClickListener != null) {
- mOnTagClickListener.onTagClick(tag.getText().toString());
+ mOnTagClickListener.onTagClick(tag.getText().toString(), tag.pos);
}
}
}
@@ -669,6 +713,9 @@ public class TagGroup extends ViewGroup {
public static final int STATE_NORMAL = 1;
public static final int STATE_INPUT = 2;
+ /** The position in the group. */
+ private int pos = -1;
+
/** The offset to the text. */
private static final int CHECKED_MARKER_OFFSET = 3;
@@ -724,8 +771,9 @@ public class TagGroup extends ViewGroup {
}
- public TagView(Context context, final int state, CharSequence text) {
+ public TagView(Context context, final int state, CharSequence text, int pos) {
super(context);
+ this.pos = pos;
setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT,
@@ -788,7 +836,7 @@ public class TagGroup extends ViewGroup {
if (lastNormalTagView.isChecked) {
removeView(lastNormalTagView);
if (mOnTagChangeListener != null) {
- mOnTagChangeListener.onDelete(TagGroup.this, lastNormalTagView.getText().toString());
+ mOnTagChangeListener.onDelete(TagGroup.this, lastNormalTagView.getText().toString(), pos);
}
} else {
final TagView checkedTagView = getCheckedTag();
@@ -1003,6 +1051,7 @@ public class TagGroup extends ViewGroup {
}
break;
}
+ case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
isPressed = false;
invalidatePaint();
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/ExplosionAnimator.java b/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/ExplosionAnimator.java
new file mode 100644
index 0000000..91da4d2
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/ExplosionAnimator.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 tyrantgit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package xyz.fycz.myreader.widget.explosion_field;
+
+import android.animation.ValueAnimator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import java.util.Random;
+
+public class ExplosionAnimator extends ValueAnimator {
+
+ static long DEFAULT_DURATION = 0x400;
+ private static final Interpolator DEFAULT_INTERPOLATOR = new AccelerateInterpolator(0.6f);
+ private static final float END_VALUE = 1.4f;
+ private static final float X = Utils.dp2Px(5);
+ private static final float Y = Utils.dp2Px(20);
+ private static final float V = Utils.dp2Px(2);
+ private static final float W = Utils.dp2Px(1);
+ private Paint mPaint;
+ private Particle[] mParticles;
+ private Rect mBound;
+ private View mContainer;
+
+ public ExplosionAnimator(View container, Bitmap bitmap, Rect bound) {
+ mPaint = new Paint();
+ mBound = new Rect(bound);
+ int partLen = 15;
+ mParticles = new Particle[partLen * partLen];
+ Random random = new Random(System.currentTimeMillis());
+ int w = bitmap.getWidth() / (partLen + 2);
+ int h = bitmap.getHeight() / (partLen + 2);
+ for (int i = 0; i < partLen; i++) {
+ for (int j = 0; j < partLen; j++) {
+ mParticles[(i * partLen) + j] = generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random);
+ }
+ }
+ mContainer = container;
+ setFloatValues(0f, END_VALUE);
+ setInterpolator(DEFAULT_INTERPOLATOR);
+ setDuration(DEFAULT_DURATION);
+ }
+
+ private Particle generateParticle(int color, Random random) {
+ Particle particle = new Particle();
+ particle.color = color;
+ particle.radius = V;
+ if (random.nextFloat() < 0.2f) {
+ particle.baseRadius = V + ((X - V) * random.nextFloat());
+ } else {
+ particle.baseRadius = W + ((V - W) * random.nextFloat());
+ }
+ float nextFloat = random.nextFloat();
+ particle.top = mBound.height() * ((0.18f * random.nextFloat()) + 0.2f);
+ particle.top = nextFloat < 0.2f ? particle.top : particle.top + ((particle.top * 0.2f) * random.nextFloat());
+ particle.bottom = (mBound.height() * (random.nextFloat() - 0.5f)) * 1.8f;
+ float f = nextFloat < 0.2f ? particle.bottom : nextFloat < 0.8f ? particle.bottom * 0.6f : particle.bottom * 0.3f;
+ particle.bottom = f;
+ particle.mag = 4.0f * particle.top / particle.bottom;
+ particle.neg = (-particle.mag) / particle.bottom;
+ f = mBound.centerX() + (Y * (random.nextFloat() - 0.5f));
+ particle.baseCx = f;
+ particle.cx = f;
+ f = mBound.centerY() + (Y * (random.nextFloat() - 0.5f));
+ particle.baseCy = f;
+ particle.cy = f;
+ particle.life = END_VALUE / 10 * random.nextFloat();
+ particle.overflow = 0.4f * random.nextFloat();
+ particle.alpha = 1f;
+ return particle;
+ }
+
+ public boolean draw(Canvas canvas) {
+ if (!isStarted()) {
+ return false;
+ }
+ for (Particle particle : mParticles) {
+ particle.advance((float) getAnimatedValue());
+ if (particle.alpha > 0f) {
+ mPaint.setColor(particle.color);
+ mPaint.setAlpha((int) (Color.alpha(particle.color) * particle.alpha));
+ canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint);
+ }
+ }
+ mContainer.invalidate();
+ return true;
+ }
+
+ @Override
+ public void start() {
+ super.start();
+ mContainer.invalidate(mBound);
+ }
+
+ private class Particle {
+ float alpha;
+ int color;
+ float cx;
+ float cy;
+ float radius;
+ float baseCx;
+ float baseCy;
+ float baseRadius;
+ float top;
+ float bottom;
+ float mag;
+ float neg;
+ float life;
+ float overflow;
+
+
+ public void advance(float factor) {
+ float f = 0f;
+ float normalization = factor / END_VALUE;
+ if (normalization < life || normalization > 1f - overflow) {
+ alpha = 0f;
+ return;
+ }
+ normalization = (normalization - life) / (1f - life - overflow);
+ float f2 = normalization * END_VALUE;
+ if (normalization >= 0.7f) {
+ f = (normalization - 0.7f) / 0.3f;
+ }
+ alpha = 1f - f;
+ f = bottom * f2;
+ cx = baseCx + f;
+ cy = (float) (baseCy - this.neg * Math.pow(f, 2.0)) - f * mag;
+ radius = V + (baseRadius - V) * f2;
+ }
+ }
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/ExplosionField.java b/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/ExplosionField.java
new file mode 100644
index 0000000..9597026
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/ExplosionField.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 tyrantgit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package xyz.fycz.myreader.widget.explosion_field;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.media.MediaPlayer;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+
+public class ExplosionField extends View {
+
+ private long customDuration = ExplosionAnimator.DEFAULT_DURATION;
+ private int idPlayAnimationEffect = 0;
+ private OnAnimatorListener mZAnimatorListener;
+ private OnClickListener mOnClickListener;
+
+ private List mExplosions = new ArrayList<>();
+ private int[] mExpandInset = new int[2];
+
+ public ExplosionField(Context context) {
+ super(context);
+ init();
+ }
+
+ public ExplosionField(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public ExplosionField(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+
+ Arrays.fill(mExpandInset, Utils.dp2Px(32));
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ for (ExplosionAnimator explosion : mExplosions) {
+ explosion.draw(canvas);
+ }
+ }
+
+ public void playSoundAnimationEffect(int id) {
+ this.idPlayAnimationEffect = id;
+ }
+
+ public void setCustomDuration(long customDuration) {
+ this.customDuration = customDuration;
+ }
+
+ public void addActionEvent(OnAnimatorListener ievents) {
+ this.mZAnimatorListener = ievents;
+ }
+
+
+ public void expandExplosionBound(int dx, int dy) {
+ mExpandInset[0] = dx;
+ mExpandInset[1] = dy;
+ }
+
+ public void explode(Bitmap bitmap, Rect bound, long startDelay) {
+ explode(bitmap, bound, startDelay, null);
+ }
+
+ public void explode(Bitmap bitmap, Rect bound, long startDelay, final View view) {
+ long currentDuration = customDuration;
+ final ExplosionAnimator explosion = new ExplosionAnimator(this, bitmap, bound);
+ explosion.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mExplosions.remove(animation);
+ if (view != null) {
+ view.setScaleX(1);
+ view.setScaleY(1);
+ view.setAlpha(1);
+ view.setOnClickListener(mOnClickListener);//set event
+
+ }
+ }
+ });
+ explosion.setStartDelay(startDelay);
+ explosion.setDuration(currentDuration);
+ mExplosions.add(explosion);
+ explosion.start();
+ }
+
+ public void explode(View view) {
+ explode(view, false);
+ }
+
+ public void explode(final View view, Boolean restartState) {
+
+ Rect r = new Rect();
+ view.getGlobalVisibleRect(r);
+ int[] location = new int[2];
+ getLocationOnScreen(location);
+// getLocationInWindow(location);
+// view.getLocationInWindow(location);
+ r.offset(-location[0], -location[1]);
+ r.inset(-mExpandInset[0], -mExpandInset[1]);
+ int startDelay = 100;
+ ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+
+ Random random = new Random();
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ view.setTranslationX((random.nextFloat() - 0.5f) * view.getWidth() * 0.05f);
+ view.setTranslationY((random.nextFloat() - 0.5f) * view.getHeight() * 0.05f);
+ }
+ });
+
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ if (idPlayAnimationEffect != 0)
+ MediaPlayer.create(getContext(), idPlayAnimationEffect).start();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (mZAnimatorListener != null) {
+ mZAnimatorListener.onAnimationEnd(animator, ExplosionField.this);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ Log.i("PRUEBA", "CANCEL");
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+ Log.i("PRUEBA", "REPEAT");
+ }
+ });
+
+ animator.start();
+ view.animate().setDuration(150).setStartDelay(startDelay).scaleX(0f).scaleY(0f).alpha(0f).start();
+ if (restartState)
+ explode(Utils.createBitmapFromView(view), r, startDelay, view);
+ else
+ explode(Utils.createBitmapFromView(view), r, startDelay);
+
+ }
+
+ public void clear() {
+ mExplosions.clear();
+ invalidate();
+ }
+
+ public static ExplosionField attach2Window(Activity activity) {
+ ViewGroup rootView = (ViewGroup) activity.findViewById(Window.ID_ANDROID_CONTENT);
+ ExplosionField explosionField = new ExplosionField(activity);
+ rootView.addView(explosionField, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ return explosionField;
+ }
+
+ public void setOnClickListener(OnClickListener mOnClickListener) {
+ this.mOnClickListener = mOnClickListener;
+ }
+
+
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/OnAnimatorListener.java b/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/OnAnimatorListener.java
new file mode 100644
index 0000000..571c2c9
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/OnAnimatorListener.java
@@ -0,0 +1,8 @@
+package xyz.fycz.myreader.widget.explosion_field;
+
+import android.animation.Animator;
+import android.view.View;
+
+public interface OnAnimatorListener {
+ void onAnimationEnd(Animator animator, View view);
+}
diff --git a/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/Utils.java b/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/Utils.java
new file mode 100644
index 0000000..f41bbb6
--- /dev/null
+++ b/app/src/main/java/xyz/fycz/myreader/widget/explosion_field/Utils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 tyrantgit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package xyz.fycz.myreader.widget.explosion_field;
+
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+
+public class Utils {
+
+ private Utils() {
+ }
+
+ private static final float DENSITY = Resources.getSystem().getDisplayMetrics().density;
+ private static final Canvas sCanvas = new Canvas();
+
+ public static int dp2Px(int dp) {
+ return Math.round(dp * DENSITY);
+ }
+
+ public static Bitmap createBitmapFromView(View view) {
+ if (view instanceof ImageView) {
+ Drawable drawable = ((ImageView) view).getDrawable();
+ if (drawable != null && drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+ }
+ view.clearFocus();
+ Bitmap bitmap = createBitmapSafely(view.getWidth(),
+ view.getHeight(), Bitmap.Config.ARGB_8888, 1);
+ if (bitmap != null) {
+ synchronized (sCanvas) {
+ Canvas canvas = sCanvas;
+ canvas.setBitmap(bitmap);
+ view.draw(canvas);
+ canvas.setBitmap(null);
+ }
+ }
+ return bitmap;
+ }
+
+ public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) {
+ try {
+ return Bitmap.createBitmap(width, height, config);
+ } catch (OutOfMemoryError e) {
+ e.printStackTrace();
+ if (retryCount > 0) {
+ System.gc();
+ return createBitmapSafely(width, height, config, retryCount - 1);
+ }
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_search_book.xml b/app/src/main/res/layout/activity_search_book.xml
index 41a1334..d9f2477 100644
--- a/app/src/main/res/layout/activity_search_book.xml
+++ b/app/src/main/res/layout/activity_search_book.xml
@@ -87,102 +87,168 @@
android:layout_height="3dp"
android:visibility="visible" />
-
-
-
+ android:layout_height="wrap_content">
+ android:orientation="vertical"
+ android:padding="5dp">
-
+ android:orientation="vertical"
+ android:paddingHorizontal="5dp"
+ android:visibility="gone">
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+ android:orientation="vertical"
+ android:paddingHorizontal="5dp"
+ android:visibility="gone">
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ android:orientation="vertical"
+ android:paddingHorizontal="5dp"
+ android:visibility="visible">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+ app:layout_anchorGravity="right|bottom" />
diff --git a/app/src/main/res/layout/layout_about_content.xml b/app/src/main/res/layout/layout_about_content.xml
index f33a1f2..bdaa5ed 100644
--- a/app/src/main/res/layout/layout_about_content.xml
+++ b/app/src/main/res/layout/layout_about_content.xml
@@ -15,8 +15,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorForeground"
- android:orientation="vertical"
- >
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
+
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index b22bd86..781aed0 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,6 +1,6 @@
- #FF2196F3
+ @color/blue_bar
#3F51B5
#90cbf6
@@ -102,6 +102,7 @@
#18188a
+ #FF2196F3
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d4b07bc..538ed73 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -108,6 +108,7 @@
https://github.com/fengyuecanzhu/FYReader
检查更新
致谢名单
+ 加入QQ频道
@@ -150,7 +151,7 @@
搜索
主题模式
加入QQ群
- 建议反馈
+ 建议反馈(QQ频道)
分组切换
分组管理
设置分组
@@ -519,6 +520,11 @@
清除搜索结果
发送验证码
重新发送%s
+ 推荐
+ 搜索记录(长按删除)
+ 清除搜索记录
+ 收起
+ 展开
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index d1db0a2..e71cad8 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -273,6 +273,7 @@
+
diff --git a/app/version_code.properties b/app/version_code.properties
index e314e56..a628e66 100644
--- a/app/version_code.properties
+++ b/app/version_code.properties
@@ -1,3 +1,3 @@
#Fri Jun 18 21:45:31 CST 2021
-VERSION_CODE=231
-NEED_CREATE_RELEASE=false
+VERSION_CODE=232
+NEED_CREATE_RELEASE=true