mirror of
https://github.com/gedoor/legado.git
synced 2024-09-01 09:34:25 +08:00
Merge remote-tracking branch 'origin/master' into desirepath
# Conflicts: # app/src/main/java/io/legado/app/base/BaseActivity.kt
This commit is contained in:
commit
8b1a462aff
@ -9,9 +9,6 @@ androidExtensions {
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
defaultConfig {
|
||||
applicationId "io.legado.app"
|
||||
minSdkVersion 21
|
||||
@ -85,6 +82,6 @@ dependencies {
|
||||
implementation 'com.github.bumptech.glide:glide:4.8.0'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:theme="@style/CAppTheme"
|
||||
tools:ignore="AllowBackup,GoogleAppIndexingWarning">
|
||||
<activity android:name=".ui.search.SearchActivity">
|
||||
</activity>
|
||||
@ -32,7 +32,7 @@
|
||||
<activity
|
||||
android:name=".ui.main.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
android:theme="@style/CAppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
|
16
app/src/main/assets/disclaimer.md
Normal file
16
app/src/main/assets/disclaimer.md
Normal file
@ -0,0 +1,16 @@
|
||||
# 免责声明(Disclaimer)
|
||||
|
||||
* 阅读是一款提供网络文学搜索的工具,为广大网络文学爱好者提供一种方便、快捷舒适的试读体验。
|
||||
* 当您搜索一本书的时,阅读会将该书的书名以关键词的形式提交到各个第三方网络文学网站。
|
||||
各第三方网站返回的内容与阅读无关,阅读对其概不负责,亦不承担任何法律责任。
|
||||
任何通过使用阅读而链接到的第三方网页均系他人制作或提供,您可能从第三方网页上获得其他服务,阅读对其合法性概不负责,亦不承担任何法律责任。
|
||||
第三方搜索引擎结果根据您提交的书名自动搜索获得并提供试读,不代表阅读赞成或被搜索链接到的第三方网页上的内容或立场。
|
||||
您应该对使用搜索引擎的结果自行承担风险。
|
||||
* 阅读不做任何形式的保证:不保证第三方搜索引擎的搜索结果满足您的要求,不保证搜索服务不中断,不保证搜索结果的安全性、正确性、及时性、合法性。
|
||||
因网络状况、通讯线路、第三方网站等任何原因而导致您不能正常使用阅读,阅读不承担任何法律责任。
|
||||
阅读尊重并保护所有使用阅读用户的个人隐私权,您注册的用户名、电子邮件地址等个人资料,非经您亲自许可或根据相关法律、法规的强制性规定,阅读不会主动地泄露给第三方。
|
||||
* 阅读致力于最大程度地减少网络文学阅读者在自行搜寻过程中的无意义的时间浪费,通过专业搜索展示不同网站中网络文学的最新章节。
|
||||
阅读在为广大小说爱好者提供方便、快捷舒适的试读体验的同时,也使优秀网络文学得以迅速、更广泛的传播,从而达到了在一定程度促进网络文学充分繁荣发展之目的。
|
||||
阅读鼓励广大小说爱好者通过阅读发现优秀网络小说及其提供商,并建议阅读正版图书。
|
||||
任何单位或个人认为通过阅读搜索链接到的第三方网页内容可能涉嫌侵犯其信息网络传播权,应该及时向阅读提出书面权力通知,并提供身份证明、权属证明及详细侵权情况证明。
|
||||
阅读在收到上述法律文件后,将会依法尽快断开相关链接内容。
|
BIN
app/src/main/assets/number.ttf
Normal file
BIN
app/src/main/assets/number.ttf
Normal file
Binary file not shown.
38
app/src/main/assets/txtChapterRule.json
Normal file
38
app/src/main/assets/txtChapterRule.json
Normal file
@ -0,0 +1,38 @@
|
||||
[
|
||||
{
|
||||
"enable": true,
|
||||
"name": "默认正则1",
|
||||
"rule": "^(.{0,8})(第)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([章节卷集部篇回场])(.{0,30})$",
|
||||
"serialNumber": 0
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "默认正则2",
|
||||
"rule": "^([0-9]{1,5})([\\,\\.,-])(.{1,20})$",
|
||||
"serialNumber": 1
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "默认正则3",
|
||||
"rule": "^(\\s{0,4})([\\(【《]?(卷)?)([0-9零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10})([\\.:: \f\t])(.{0,30})$",
|
||||
"serialNumber": 2
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "默认正则4",
|
||||
"rule": "^(\\s{0,4})([\\((【《])(.{0,30})([\\))】》])(\\s{0,2})$",
|
||||
"serialNumber": 3
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "默认正则5",
|
||||
"rule": "^(\\s{0,4})(正文)(.{0,20})$",
|
||||
"serialNumber": 4
|
||||
},
|
||||
{
|
||||
"enable": true,
|
||||
"name": "默认正则6",
|
||||
"rule": "^(.{0,4})(Chapter|chapter)(\\s{0,4})([0-9]{1,4})(.{0,30})$",
|
||||
"serialNumber": 5
|
||||
}
|
||||
]
|
5
app/src/main/assets/updateLog.md
Normal file
5
app/src/main/assets/updateLog.md
Normal file
@ -0,0 +1,5 @@
|
||||
## 本软件为开源软件,没有上架Google Play,请不要在任何地方购买!
|
||||
### 关注公众号[开源阅读软件]可获取书源,点击广告可支持作者!
|
||||
### 捐赠里点击红包搜索码可开启高级功能!
|
||||
## 更新日志
|
||||
|
176
app/src/main/assets/web/bookshelf.css
Normal file
176
app/src/main/assets/web/bookshelf.css
Normal file
@ -0,0 +1,176 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.top, .showchapter, .hidebooks {
|
||||
width: 60px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 30px;
|
||||
color: black;
|
||||
font-size: 28px;
|
||||
background-color: #ddd;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.top {
|
||||
bottom: 150px;
|
||||
}
|
||||
|
||||
.showchapter {
|
||||
bottom: 90px;
|
||||
bottom: 90px;
|
||||
}
|
||||
|
||||
.address {
|
||||
width: 270px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
border-bottom: solid 1px #ccc;
|
||||
}
|
||||
|
||||
input, button {
|
||||
width: 110px;
|
||||
line-height: 34px;
|
||||
background-color: #eee;
|
||||
color: #555;
|
||||
border: none;
|
||||
margin: 10px 5px;
|
||||
font-weight: 500;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0 10px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
input:hover, button:hover {
|
||||
border-color: #aaa;
|
||||
background-color: #efefef;
|
||||
color: #222;
|
||||
outline: solid 1px #ccc;
|
||||
}
|
||||
|
||||
.allcontent {
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.allscreen {
|
||||
height: 100%
|
||||
}
|
||||
|
||||
.books > div {
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
vertical-align: top;
|
||||
border: solid 1px #ddd;
|
||||
}
|
||||
|
||||
.read > .books {
|
||||
width: 420px;
|
||||
float: left;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
border-right: solid 1px #ccc;
|
||||
}
|
||||
|
||||
.read > .books > div {
|
||||
margin-right: 0;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
|
||||
.more {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.read .more {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.books > div > img {
|
||||
width: 120px;
|
||||
height: 180px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 10px 20px 0 20px;
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.info > img {
|
||||
width: 600px;
|
||||
height: 900px;
|
||||
}
|
||||
|
||||
.info p {
|
||||
line-height: 1.5;
|
||||
text-align: justify;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.books tr:nth-child(n+2) td {
|
||||
border-top: solid 1px #999;
|
||||
}
|
||||
|
||||
.books td:nth-child(1) {
|
||||
vertical-align: top;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.books td:nth-child(2) {
|
||||
vertical-align: top;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin: 10px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
border-top: solid 1px #333;
|
||||
border-bottom: solid 1px #333;
|
||||
}
|
||||
|
||||
.chapter button {
|
||||
width: 230px;
|
||||
text-align: left;
|
||||
text-indent: 14px;
|
||||
margin: 10px 4px;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
padding: 20px;
|
||||
text-align: justify;
|
||||
min-height: 1000px;
|
||||
padding-bottom: 200px;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
font-family: "Microsoft YaHei",微软雅黑,"MicrosoftJhengHei",华文细黑,STHeiti,MingLiu;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
font-size: 40px;
|
||||
margin: 0;
|
||||
}
|
32
app/src/main/assets/web/bookshelf.html
Normal file
32
app/src/main/assets/web/bookshelf.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>阅读书架</title>
|
||||
<link href="bookshelf.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<button id="top" class="top">↑</button>
|
||||
<button id="showchapter" class="showchapter">≡</button>
|
||||
<button id="hidebooks" class="hidebooks">❑</button>
|
||||
<div class="nav">
|
||||
<button id="back">返回</button>
|
||||
<button id="type">所有书籍 ▼</button>
|
||||
<button id="sort">手动排序 ▼</button>
|
||||
<button id="setting">阅读设置</button>
|
||||
<input type="text" class="address" id="address" title="阅读APP地址或IP" value=""/>
|
||||
<button id="refresh">重新加载</button>
|
||||
</div>
|
||||
|
||||
<div class="allcontent" id="allcontent">
|
||||
<div id="books" class="books"></div>
|
||||
<div id="more" class="more">
|
||||
<div id="info" class="info"></div>
|
||||
<div class="clear"></div>
|
||||
<div id="chapter" class="chapter"></div>
|
||||
<div id="content" class="content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="bookshelf.js"></script>
|
||||
</body>
|
||||
</html>
|
161
app/src/main/assets/web/bookshelf.js
Normal file
161
app/src/main/assets/web/bookshelf.js
Normal file
@ -0,0 +1,161 @@
|
||||
var $ = document.querySelector.bind(document)
|
||||
, $$ = document.querySelectorAll.bind(document)
|
||||
, $c = document.createElement.bind(document)
|
||||
, randomImg = "http://acg.bakayun.cn/randbg.php?t=dfzh"
|
||||
, randomImg2 = "http://img.xjh.me/random_img.php"
|
||||
, books
|
||||
;
|
||||
|
||||
var formatTime = value => {
|
||||
return new Date(value).toLocaleString('zh-CN', {
|
||||
hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"
|
||||
}).replace(/\//g, "-");
|
||||
};
|
||||
|
||||
var apiMap = {
|
||||
getBookshelf: "/getBookshelf",
|
||||
getChapterList: "/getChapterList",
|
||||
getBookContent: "/getBookContent",
|
||||
saveBook: "/saveBook"
|
||||
};
|
||||
|
||||
var apiAddress = (apiName, url) => {
|
||||
let address = $('#address').value || window.location.host;
|
||||
if (!(/^http|^\/\//).test(address)) {
|
||||
address = "//" + address;
|
||||
}
|
||||
if (!(/:\d{4,}/).test(address.split("//")[1].split("/")[0])) {
|
||||
address += ":1122";
|
||||
}
|
||||
localStorage.setItem('address', address);
|
||||
return address + apiMap[apiName] + (url ? "?url=" + encodeURIComponent(url) : "");
|
||||
};
|
||||
|
||||
var init = () => {
|
||||
$('#allcontent').classList.remove("read");
|
||||
$('#books').innerHTML = "";
|
||||
fetch(apiAddress("getBookshelf"), { mode: "cors" })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!data.isSuccess) {
|
||||
alert(getBookshelf.errorMsg);
|
||||
return;
|
||||
}
|
||||
books = data.data.sort((book1, book2) => book1.serialNumber - book2.serialNumber);
|
||||
books.forEach(book => {
|
||||
let bookDiv = $c("div");
|
||||
let img = $c("img");
|
||||
img.src = book.bookInfoBean.coverUrl || randomImg;
|
||||
img.setAttribute("data-series-num", book.serialNumber);
|
||||
bookDiv.appendChild(img);
|
||||
bookDiv.innerHTML += `<table><tbody>
|
||||
<tr><td>书名:</td><td>${book.bookInfoBean.name}</td></tr>
|
||||
<tr><td>作者:</td><td>${book.bookInfoBean.author}</td></tr>
|
||||
<tr><td>阅读:</td><td>${book.durChapterName}<br>${formatTime(book.finalDate)}</td></tr>
|
||||
<tr><td>更新:</td><td>${book.lastChapterName}<br>${formatTime(book.finalRefreshData)}</td></tr>
|
||||
<tr><td>来源:</td><td>${book.bookInfoBean.origin}</td></tr>
|
||||
</tbody></table>`;
|
||||
$('#books').appendChild(bookDiv);
|
||||
});
|
||||
$$('#books img').forEach(bookImg =>
|
||||
bookImg.addEventListener("click", () => {
|
||||
$('#allcontent').classList.add("read");
|
||||
var book = books[bookImg.getAttribute("data-series-num")];
|
||||
$("#info").innerHTML = `<img src="${bookImg.src}">
|
||||
<p> 来源:${book.bookInfoBean.origin}</p>
|
||||
<p> 书名:${book.bookInfoBean.name}</p>
|
||||
<p> 作者:${book.bookInfoBean.author}</p>
|
||||
<p>阅读章节:${book.durChapterName}</p>
|
||||
<p>阅读时间:${formatTime(book.finalDate)}</p>
|
||||
<p>最新章节:${book.lastChapterName}</p>
|
||||
<p>检查时间:${formatTime(book.finalRefreshData)}</p>
|
||||
<p> 简介:${book.bookInfoBean.introduce.trim().replace(/\n/g, "<br>")}</p>`;
|
||||
window.location.hash = "";
|
||||
window.location.hash = "#info";
|
||||
$("#content").innerHTML = "章节列表加载中...";
|
||||
$("#chapter").innerHTML = "";
|
||||
fetch(apiAddress("getChapterList", book.noteUrl), { mode: "cors" })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!data.isSuccess) {
|
||||
alert(data.errorMsg);
|
||||
$("#content").innerHTML = "章节列表加载失败!";
|
||||
return;
|
||||
}
|
||||
|
||||
data.data.forEach(chapter => {
|
||||
let ch = $c("button");
|
||||
ch.setAttribute("data-url", chapter.durChapterUrl);
|
||||
ch.setAttribute("title", chapter.durChapterName);
|
||||
ch.innerHTML = chapter.durChapterName.length > 15 ? chapter.durChapterName.substring(0, 14) + "..." : chapter.durChapterName;
|
||||
$("#chapter").appendChild(ch);
|
||||
});
|
||||
$('#chapter').scrollTop = 0;
|
||||
$("#content").innerHTML = "章节列表加载完成!";
|
||||
});
|
||||
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
$("#back").addEventListener("click", () => {
|
||||
if (window.location.hash === "#content") {
|
||||
window.location.hash = "#chapter";
|
||||
} else if (window.location.hash === "#chapter") {
|
||||
window.location.hash = "#info";
|
||||
} else {
|
||||
$('#allcontent').classList.remove("read");
|
||||
}
|
||||
});
|
||||
|
||||
$("#refresh").addEventListener("click", init);
|
||||
|
||||
$('#hidebooks').addEventListener("click", () => {
|
||||
$("#books").classList.toggle("hide");
|
||||
$(".nav").classList.toggle("hide");
|
||||
$("#allcontent").classList.toggle("allscreen");
|
||||
});
|
||||
|
||||
$('#top').addEventListener("click", () => {
|
||||
window.location.hash = "";
|
||||
window.location.hash = "#info";
|
||||
});
|
||||
|
||||
$('#showchapter').addEventListener("click", () => {
|
||||
window.location.hash = "";
|
||||
window.location.hash = "#chapter";
|
||||
});
|
||||
|
||||
$('#chapter').addEventListener("click", (e) => {
|
||||
if (e.target.tagName === "BUTTON") {
|
||||
var url = e.target.getAttribute("data-url");
|
||||
var name = e.target.getAttribute("title");
|
||||
if (!url) {
|
||||
alert("未取得章节地址");
|
||||
}
|
||||
$("#content").innerHTML = "<p>" + name + " 加载中...</p>";
|
||||
fetch(apiAddress("getBookContent", url), { mode: "cors" })
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (!data.isSuccess) {
|
||||
alert(data.errorMsg);
|
||||
$("#content").innerHTML = "<p>" + name + " 加载失败!</p>";
|
||||
return;
|
||||
}
|
||||
var content = data.data.trim().split("\n\n");
|
||||
if (content.length === 2) {
|
||||
$("#content").innerHTML = `<h2>${content[0]}</h2> (全文 ${content[1].length} 字)<br><br> ` + content[1].trim().replace(/\n/g, "<br><br>");
|
||||
} else {
|
||||
$("#content").innerHTML = `<h2>${name || e.target.innerHTML}</h2> (全文 ${data.data.length} 字)<br><br> ` + data.data.trim().replace(/\n/g, "<br><br>");
|
||||
}
|
||||
window.location.hash = "";
|
||||
window.location.hash = "#content";
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#address').setAttribute("placeholder", "阅读APP地址或IP:" + window.location.host);
|
||||
if (!$('#address').value && typeof localStorage && localStorage.getItem('address')) {
|
||||
$('#address').value = localStorage.getItem('address');
|
||||
}
|
||||
init();
|
BIN
app/src/main/assets/web/favicon.ico
Normal file
BIN
app/src/main/assets/web/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
148
app/src/main/assets/web/index.css
Normal file
148
app/src/main/assets/web/index.css
Normal file
@ -0,0 +1,148 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.editor {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
.setbox,
|
||||
.menu,
|
||||
.outbox {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.menu {
|
||||
justify-content: center;
|
||||
max-width: 90px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.menu .button {
|
||||
width: 90px;
|
||||
height: 30px;
|
||||
min-height: 30px;
|
||||
margin: 5px 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@keyframes stroker {
|
||||
0% {
|
||||
stroke-dashoffset: 0
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: -240
|
||||
}
|
||||
}
|
||||
.button rect {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: transparent;
|
||||
stroke: #666;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.button rect.busy {
|
||||
stroke: #fD1850;
|
||||
stroke-dasharray: 30 90;
|
||||
animation: stroker 1s linear infinite;
|
||||
}
|
||||
.button text {
|
||||
text-anchor: middle;
|
||||
dominant-baseline: middle;
|
||||
}
|
||||
.setbox {
|
||||
min-width: 40em;
|
||||
}
|
||||
.rules,
|
||||
.tabbox {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
.rules>* {
|
||||
display: flex;
|
||||
margin: 2px 0;
|
||||
}
|
||||
.rules textarea {
|
||||
flex: 1;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.rules>*,
|
||||
.rules>*>div,
|
||||
.rules textarea {
|
||||
min-height: 1em;
|
||||
}
|
||||
textarea {
|
||||
word-break: break-all;
|
||||
}
|
||||
.tabtitle {
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.tabtitle>div {
|
||||
cursor: pointer;
|
||||
padding: 1px 10px 0 10px;
|
||||
border-bottom: 3px solid transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
.tabtitle>.this {
|
||||
color: #4f9da6;
|
||||
border-bottom-color: #4EBBE4;
|
||||
}
|
||||
.tabbody {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
margin-top: -1px;
|
||||
border: 1px solid #A9A9A9;
|
||||
height: 0;
|
||||
}
|
||||
.tabbody>* {
|
||||
flex: 1;
|
||||
flex-flow: column;
|
||||
display: none;
|
||||
}
|
||||
.tabbody>.this {
|
||||
display: flex;
|
||||
}
|
||||
.tabbody>*>.titlebar{
|
||||
display: flex;
|
||||
}
|
||||
.tabbody>*>.titlebar>*{
|
||||
flex: 1;
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.tabbody>*>.context {
|
||||
flex: 1;
|
||||
flex-flow: column;
|
||||
border: 0;
|
||||
padding: 5px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.tabbody>*>.inputbox{
|
||||
border: 0;
|
||||
border-bottom: #A9A9A9 solid 1px;
|
||||
height: 15px;
|
||||
text-align:center;
|
||||
}
|
||||
.link>* {
|
||||
display: flex;
|
||||
margin: 5px;
|
||||
border-bottom: 1px solid;
|
||||
text-decoration: none;
|
||||
}
|
||||
#RuleList>label>* {
|
||||
background: #eee;
|
||||
padding-left: 3px;
|
||||
margin: 2px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
#RuleList input[type=radio] {
|
||||
display: none;
|
||||
}
|
||||
#RuleList input[type="radio"]:checked+* {
|
||||
background: #15cda8;
|
||||
}
|
||||
.isError {
|
||||
color: #FF0000;
|
||||
}
|
306
app/src/main/assets/web/index.html
Normal file
306
app/src/main/assets/web/index.html
Normal file
@ -0,0 +1,306 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>书源编辑器v3.8</title>
|
||||
<link rel="stylesheet" type="text/css" href="/index.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="editor">
|
||||
<div class="setbox">
|
||||
<div class="rules">
|
||||
<div><b>书源基础信息</b></div>
|
||||
<div>
|
||||
<div>书源名称:</div>
|
||||
<textarea rows="1" id="bookSourceName" placeholder="书源名称(bookSourceName) | 会显示在书源列表"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书源分组:</div>
|
||||
<textarea rows="1" id="bookSourceGroup" placeholder="书源分组(bookSourceGroup) | 描述书源的特征信息"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书源域名:</div>
|
||||
<textarea rows="1" id="bookSourceUrl"
|
||||
placeholder="书源URL(bookSourceUrl) | 通常填写网站主页(标头不可省略),例: https://www.qidian.com"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>登录网页:</div>
|
||||
<textarea rows="1" id="loginUrl" placeholder="登录URL(loginUrl) | 填写网站登录网址,仅在需要登录的书源有用"></textarea>
|
||||
</div>
|
||||
<div><b>书籍发现规则</b></div>
|
||||
<div>
|
||||
<div>发现菜单:</div>
|
||||
<textarea rows="5" id="ruleFindUrl"
|
||||
placeholder="发现分类菜单规则(ruleFindUrl),将显示在发现菜单 每行一条发现分类(网址域名可省略): 名称1::网址(Url)1 名称2::网址(Url)2 ..."></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>结果列表:</div>
|
||||
<textarea rows="1" id="ruleFindList"
|
||||
placeholder="发现页列表规则(ruleFindList) | 选择书籍节点 (规则结果为List<Element>)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍名称:</div>
|
||||
<textarea rows="1" id="ruleFindName"
|
||||
placeholder="发现页书名规则(ruleFindName) | 选择节点书名 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍作者:</div>
|
||||
<textarea rows="1" id="ruleFindAuthor"
|
||||
placeholder="发现页作者规则(ruleFindAuthor) | 选择节点作者 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍分类:</div>
|
||||
<textarea rows="1" id="ruleFindKind"
|
||||
placeholder="发现页分类规则(ruleFindKind) | 选择节点分类信息 (规则结果为List<String>)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>最新章节:</div>
|
||||
<textarea rows="1" id="ruleFindLastChapter"
|
||||
placeholder="发现页最新章节规则(ruleFindLastChapter) | 选择节点最新章节 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>简介内容:</div>
|
||||
<textarea rows="1" id="ruleFindIntroduce"
|
||||
placeholder="发现页简介规则(ruleFindIntroduce) | 选择节点书籍简介 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>封面链接:</div>
|
||||
<textarea rows="1" id="ruleFindCoverUrl"
|
||||
placeholder="发现页封面规则(ruleFindCoverUrl) | 选择节点书籍封面 (规则结果为Url)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>详情链接:</div>
|
||||
<textarea rows="1" id="ruleFindNoteUrl"
|
||||
placeholder="发现页详情规则(ruleFindNoteUrl) | 选择书籍详情页网址 (规则结果为Url)"></textarea>
|
||||
</div>
|
||||
<div><b>书籍搜索规则</b></div>
|
||||
<div>
|
||||
<div>搜索网址:</div>
|
||||
<textarea rows="1" id="ruleSearchUrl"
|
||||
placeholder="搜索网址(ruleSearchUrl) | [域名可省略]/search.php@kw=searchKey|char=utf-8"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>结果验证:</div>
|
||||
<textarea rows="1" id="ruleBookUrlPattern"
|
||||
placeholder="搜索页URL验证(ruleBookUrlPattern) | 正则验证URL是否为详情页,成功则跳过搜索页解析"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>结果列表:</div>
|
||||
<textarea rows="1" id="ruleSearchList"
|
||||
placeholder="搜索页列表规则(ruleSearchList) | 选择书籍节点 (规则结果为List<Element>)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍名称:</div>
|
||||
<textarea rows="1" id="ruleSearchName"
|
||||
placeholder="搜索页书名规则(ruleSearchName) | 选择节点书名 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍作者:</div>
|
||||
<textarea rows="1" id="ruleSearchAuthor"
|
||||
placeholder="搜索页作者规则(ruleSearchAuthor) | 选择节点作者 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍分类:</div>
|
||||
<textarea rows="1" id="ruleSearchKind"
|
||||
placeholder="搜索页分类规则(ruleSearchKind) | 选择节点分类信息 (规则结果为List<String>)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>最新章节:</div>
|
||||
<textarea rows="1" id="ruleSearchLastChapter"
|
||||
placeholder="搜索页最新章节规则(ruleSearchLastChapter) | 选择节点最新章节 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>简介内容:</div>
|
||||
<textarea rows="1" id="ruleSearchIntroduce"
|
||||
placeholder="搜索页简介规则(ruleSearchIntroduce) | 选择节点书籍简介 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>封面链接:</div>
|
||||
<textarea rows="1" id="ruleSearchCoverUrl"
|
||||
placeholder="搜索页封面规则(ruleSearchCoverUrl) | 选择节点书籍封面 (规则结果为Url)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>详情链接:</div>
|
||||
<textarea rows="1" id="ruleSearchNoteUrl"
|
||||
placeholder="搜索页详情规则(ruleSearchNoteUrl) | 选择书籍详情页网址 (规则结果为Url)"></textarea>
|
||||
</div>
|
||||
<div><b>书籍详情规则</b></div>
|
||||
<div>
|
||||
<div>页面处理:</div>
|
||||
<textarea rows="1" id="ruleBookInfoInit"
|
||||
placeholder="详情页信息预处理(ruleBookInfoInit) | 用于加速详情信息检索"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍名称:</div>
|
||||
<textarea rows="1" id="ruleBookName"
|
||||
placeholder="书名规则(ruleBookName) | 选择详情页书名 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍作者:</div>
|
||||
<textarea rows="1" id="ruleBookAuthor"
|
||||
placeholder="作者规则(ruleBookAuthor) | 选择详情页作者 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>书籍分类:</div>
|
||||
<textarea rows="1" id="ruleBookKind"
|
||||
placeholder="分类规则(ruleBookKind) | 选择详情页分类信息 (规则结果为List<String>)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>最新章节:</div>
|
||||
<textarea rows="1" id="ruleBookLastChapter"
|
||||
placeholder="最新章节规则(ruleBookLastChapter) | 选择详情页最新章节 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>简介内容:</div>
|
||||
<textarea rows="1" id="ruleIntroduce"
|
||||
placeholder="简介规则(ruleIntroduce) | 选择详情页书籍简介 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>封面链接:</div>
|
||||
<textarea rows="1" id="ruleCoverUrl" placeholder="封面规则(ruleCoverUrl) | 选择详情页书籍封面 (规则结果为Url)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>目录链接:</div>
|
||||
<textarea rows="1" id="ruleChapterUrl"
|
||||
placeholder="目录URL规则(ruleChapterUrl) | 选择目录页网址 (规则结果为Url, 与详情页相同时可省略)"></textarea>
|
||||
</div>
|
||||
<div><b>目录列表规则</b></div>
|
||||
<div>
|
||||
<div>目录翻页:</div>
|
||||
<textarea rows="1" id="ruleChapterUrlNext"
|
||||
placeholder="目录下一页规则(ruleChapterUrlNext) | 选择目录下一页链接 (规则结果为List<Url>)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>目录列表:</div>
|
||||
<textarea rows="1" id="ruleChapterList"
|
||||
placeholder="目录列表规则(ruleChapterList) | 选择目录列表的章节节点 (规则结果为List<Element>)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>章节名称:</div>
|
||||
<textarea rows="1" id="ruleChapterName"
|
||||
placeholder="章节名称规则(ruleChapterName) | 选择章节名称 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>章节链接:</div>
|
||||
<textarea rows="1" id="ruleContentUrl"
|
||||
placeholder="章节URL规则(ruleContentUrl) | 选择章节链接 (规则结果为Url)"></textarea>
|
||||
</div>
|
||||
<div><b>正文阅读规则</b></div>
|
||||
<div>
|
||||
<div>章节正文:</div>
|
||||
<textarea rows="1" id="ruleBookContent"
|
||||
placeholder="正文规则(ruleBookContent) | 选择正文内容 (规则结果为String)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>正文翻页:</div>
|
||||
<textarea rows="1" id="ruleContentUrlNext"
|
||||
placeholder="正文翻页URL规则(ruleContentUrlNext) | 选择下一分页(不是下一章)链接 (规则结果为Url)"></textarea>
|
||||
</div>
|
||||
<div><b>其它规则</b></div>
|
||||
<div>
|
||||
<div>浏览标识:</div>
|
||||
<textarea rows="1" id="httpUserAgent"
|
||||
placeholder="浏览器UA(HttpUserAgent) | 浏览器标识:User-Agent (可选)"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>排序编号:</div>
|
||||
<textarea rows="1" id="serialNumber" placeholder="整数: 0~N (可选,默认0) | 数字越小越靠前"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>搜索权重:</div>
|
||||
<textarea rows="1" id="weight" placeholder="整数: 0~N (可选,默认0) | 数字越大越靠前"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div>是否启用:</div>
|
||||
<textarea rows="1" id="enable" placeholder="默认启用=true,手动启用=false (可选,默认true)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu">
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">⇈推送书源</text>
|
||||
<rect id="push"></rect>
|
||||
</svg>
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">⇊拉取书源</text>
|
||||
<rect id="pull"></rect>
|
||||
</svg>
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">⋘编辑书源</text>
|
||||
<rect id="editor"></rect>
|
||||
</svg>
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">⋙生成书源</text>
|
||||
<rect id="conver"></rect>
|
||||
</svg>
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">✗清空表单</text>
|
||||
<rect id="initial"></rect>
|
||||
</svg>
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">↶撤销操作</text>
|
||||
<rect id="undo"></rect>
|
||||
</svg>
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">↷重做操作</text>
|
||||
<rect id="redo"></rect>
|
||||
</svg>
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">⇏调试书源</text>
|
||||
<rect id="debug"></rect>
|
||||
</svg>
|
||||
<svg class="button">
|
||||
<text x="50%" y="55%">✓保存书源</text>
|
||||
<rect id="accept"></rect>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="outbox">
|
||||
<div class="tabbox">
|
||||
<div class="tabtitle">
|
||||
<div name="编辑书源" class="tab1 this">编辑书源</div>
|
||||
<div name="调试书源" class="tab2">调试书源</div>
|
||||
<div name="书源列表" class="tab3">书源列表</div>
|
||||
<div name="帮助信息" class="tab4">帮助信息</div>
|
||||
</div>
|
||||
<div class="tabbody">
|
||||
<div class="tab1 this">
|
||||
<textarea class="context" id="RuleJsonString" placeholder="这里输出序列化的JSON数据,可直接导入'阅读'APP"></textarea>
|
||||
</div>
|
||||
<div class="tab2">
|
||||
<input type="text" class="inputbox" id="DebugKey" placeholder="我的">
|
||||
<textarea class="context" id="DebugConsole" placeholder="这里用于输出调试信息"></textarea>
|
||||
</div>
|
||||
<div class="tab3">
|
||||
<div class="titlebar">
|
||||
<button id="Import">导入书源文件</button>
|
||||
<button id="Export">导出书源文件</button>
|
||||
<button id="Delete">删除选中书源</button>
|
||||
<button id="ClrAll">清空当前列表</button>
|
||||
</div>
|
||||
<div class="context" id="RuleList"></div>
|
||||
</div>
|
||||
<div class="tab4">
|
||||
<div class="context link">
|
||||
<a target="_blank" href="https://gedoor.github.io/MyBookshelf/sourcerule.html">官方书源教程</a>
|
||||
<a target="_blank" href="https://zhuanlan.zhihu.com/p/29436838">Xpath基础教程</a>
|
||||
<a target="_blank" href="https://zhuanlan.zhihu.com/p/32187820">Xpath高级教程</a>
|
||||
<a target="_blank" href="https://www.w3cschool.cn/regex_rmjc/?">正则表达式教程</a>
|
||||
<a target="_blank" href="https://regexr.com/">正则表达式在线验证工具</a>
|
||||
<div>^$()[]{}.?+*| 这些是Java正则特殊符号,匹配需转义
|
||||
<br>(?s) 前缀表示跨行解析
|
||||
<br>(?m) 前缀表示逐行匹配
|
||||
<br>(?i) 前缀表示忽略大小写
|
||||
</div>
|
||||
<a target="_blank" href="https://www.beta.browxy.com/">代码在线运行工具</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/index.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
362
app/src/main/assets/web/index.js
Normal file
362
app/src/main/assets/web/index.js
Normal file
@ -0,0 +1,362 @@
|
||||
// 简化js原生选择器
|
||||
function $(selector) { return document.querySelector(selector); }
|
||||
function $$(selector) { return document.querySelectorAll(selector); }
|
||||
// 读写Hash值(val未赋值时为读取)
|
||||
function hashParam(key, val) {
|
||||
let hashstr = decodeURIComponent(window.location.hash);
|
||||
let regKey = new RegExp(`${key}=([^&]*)`);
|
||||
let getVal = regKey.test(hashstr) ? hashstr.match(regKey)[1] : null;
|
||||
if (val == undefined) return getVal;
|
||||
if (hashstr == '' || hashstr == '#') {
|
||||
window.location.hash = `#${key}=${val}`;
|
||||
}
|
||||
else {
|
||||
if (getVal) window.location.hash = hashstr.replace(getVal, val);
|
||||
else {
|
||||
window.location.hash = hashstr.indexOf(key) > -1 ? hashstr.replace(regKey, `${key}=${val}`) : `${hashstr}&${key}=${val}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 创建书源规则容器对象
|
||||
const RuleJSON = (() => {
|
||||
let ruleJson = {};
|
||||
$$('.rules textarea').forEach(item => ruleJson[item.id] = '');
|
||||
// for (let item of $$('.rules textarea')) ruleJson[item.id] = '';
|
||||
ruleJson.serialNumber = 0;
|
||||
ruleJson.weight = 0;
|
||||
ruleJson.enable = true;
|
||||
return ruleJson;
|
||||
})();
|
||||
// 选项卡Tab切换事件处理
|
||||
function showTab(tabName) {
|
||||
$$('.tabtitle>*').forEach(node => { node.className = node.className.replace(' this', ''); });
|
||||
$$('.tabbody>*').forEach(node => { node.className = node.className.replace(' this', ''); });
|
||||
$(`.tabbody>.${$(`.tabtitle>*[name=${tabName}]`).className}`).className += ' this';
|
||||
$(`.tabtitle>*[name=${tabName}]`).className += ' this';
|
||||
hashParam('tab', tabName);
|
||||
}
|
||||
// 书源列表列表标签构造函数
|
||||
function newRule(rule) {
|
||||
return `<label for="${rule.bookSourceUrl}"><input type="radio" name="rule" id="${rule.bookSourceUrl}"><div>${rule.bookSourceName}<br>${rule.bookSourceUrl}</div></label>`;
|
||||
}
|
||||
// 缓存规则列表
|
||||
var RuleSources = [];
|
||||
if (localStorage.getItem('RuleSources')) {
|
||||
RuleSources = JSON.parse(localStorage.getItem('RuleSources'));
|
||||
RuleSources.forEach(item => $('#RuleList').innerHTML += newRule(item));
|
||||
}
|
||||
// 页面加载完成事件
|
||||
window.onload = () => {
|
||||
$$('.tabtitle>*').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
showTab(item.innerHTML);
|
||||
});
|
||||
});
|
||||
if (hashParam('tab')) showTab(hashParam('tab'));
|
||||
}
|
||||
// 获取数据
|
||||
function HttpGet(url) {
|
||||
return fetch(hashParam('domain') ? hashParam('domain') + url : url)
|
||||
.then(res => res.json()).catch(err => console.error('Error:', err));
|
||||
}
|
||||
// 提交数据
|
||||
function HttpPost(url, data) {
|
||||
return fetch(hashParam('domain') ? hashParam('domain') + url : url, {
|
||||
body: JSON.stringify(data),
|
||||
method: 'POST',
|
||||
mode: "cors",
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
})
|
||||
}).then(res => res.json()).catch(err => console.error('Error:', err));
|
||||
}
|
||||
// 将书源表单转化为书源对象
|
||||
function rule2json() {
|
||||
Object.keys(RuleJSON).forEach((key) => RuleJSON[key] = $('#' + key).value);
|
||||
RuleJSON.serialNumber = RuleJSON.serialNumber == '' ? 0 : parseInt(RuleJSON.serialNumber);
|
||||
RuleJSON.weight = RuleJSON.weight == '' ? 0 : parseInt(RuleJSON.weight);
|
||||
RuleJSON.enable = RuleJSON.enable == '' || RuleJSON.enable.toLocaleLowerCase().replace(/^\s*|\s*$/g, '') == 'true';
|
||||
return RuleJSON;
|
||||
}
|
||||
// 将书源对象填充到书源表单
|
||||
function json2rule(RuleEditor) {
|
||||
Object.keys(RuleJSON).forEach((key) => $("#" + key).value = RuleEditor[key] ? RuleEditor[key] : '');
|
||||
}
|
||||
// 记录操作过程
|
||||
var course = { "old": [], "now": {}, "new": [] };
|
||||
if (localStorage.getItem('course')) {
|
||||
course = JSON.parse(localStorage.getItem('course'));
|
||||
json2rule(course.now);
|
||||
}
|
||||
else {
|
||||
course.now = rule2json();
|
||||
window.localStorage.setItem('course', JSON.stringify(course));
|
||||
}
|
||||
function todo() {
|
||||
course.old.push(Object.assign({}, course.now));
|
||||
course.now = rule2json();
|
||||
course.new = [];
|
||||
if (course.old.length > 50) course.old.shift(); // 限制历史记录堆栈大小
|
||||
localStorage.setItem('course', JSON.stringify(course));
|
||||
}
|
||||
function undo() {
|
||||
course = JSON.parse(localStorage.getItem('course'));
|
||||
if (course.old.length > 0) {
|
||||
course.new.push(course.now);
|
||||
course.now = course.old.pop();
|
||||
localStorage.setItem('course', JSON.stringify(course));
|
||||
json2rule(course.now);
|
||||
}
|
||||
}
|
||||
function redo() {
|
||||
course = JSON.parse(localStorage.getItem('course'));
|
||||
if (course.new.length > 0) {
|
||||
course.old.push(course.now);
|
||||
course.now = course.new.pop();
|
||||
localStorage.setItem('course', JSON.stringify(course));
|
||||
json2rule(course.now);
|
||||
}
|
||||
}
|
||||
function setRule(editRule) {
|
||||
let checkRule = RuleSources.find(x => x.bookSourceUrl == editRule.bookSourceUrl);
|
||||
if ($(`input[id="${editRule.bookSourceUrl}"]`)) {
|
||||
Object.keys(checkRule).forEach(key => { checkRule[key] = editRule[key]; });
|
||||
$(`input[id="${editRule.bookSourceUrl}"]+*`).innerHTML = `${editRule.bookSourceName}<br>${editRule.bookSourceUrl}`;
|
||||
} else {
|
||||
RuleSources.push(editRule);
|
||||
$('#RuleList').innerHTML += newRule(editRule);
|
||||
}
|
||||
}
|
||||
$$('input').forEach((item) => { item.addEventListener('change', () => { todo() }) });
|
||||
$$('textarea').forEach((item) => { item.addEventListener('change', () => { todo() }) });
|
||||
// 处理按钮点击事件
|
||||
$('.menu').addEventListener('click', e => {
|
||||
let thisNode = e.target;
|
||||
thisNode = thisNode.parentNode.nodeName == 'svg' ? thisNode.parentNode.querySelector('rect') :
|
||||
thisNode.nodeName == 'svg' ? thisNode.querySelector('rect') : null;
|
||||
if (!thisNode) return;
|
||||
if (thisNode.getAttribute('class') == 'busy') return;
|
||||
thisNode.setAttribute('class', 'busy');
|
||||
switch (thisNode.id) {
|
||||
case 'push':
|
||||
$$('#RuleList>label>div').forEach(item => { item.className = ''; });
|
||||
(async () => {
|
||||
await HttpPost(`/saveSources`, RuleSources).then(json => {
|
||||
if (json.isSuccess) {
|
||||
let okData = json.data;
|
||||
if (Array.isArray(okData)) {
|
||||
let failMsg = ``;
|
||||
if (RuleSources.length > okData.length) {
|
||||
RuleSources.forEach(item => {
|
||||
if (okData.find(x => x.bookSourceUrl == item.bookSourceUrl)) { }
|
||||
else { $(`#RuleList #${item.bookSourceUrl}+*`).className += 'isError'; }
|
||||
});
|
||||
failMsg = '\n推送失败的书源将用红色字体标注!';
|
||||
}
|
||||
alert(`批量推送书源到「阅读APP」\n共计: ${RuleSources.length} 条\n成功: ${okData.length} 条\n失败: ${RuleSources.length - okData.length} 条${failMsg}`);
|
||||
}
|
||||
else {
|
||||
alert(`批量推送书源到「阅读APP」成功!\n共计: ${RuleSources.length} 条`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
alert(`批量推送书源失败!\nErrorMsg: ${json.errorMsg}`);
|
||||
}
|
||||
}).catch(err => { alert(`批量推送书源失败,无法连接到「阅读APP」!\n${err}`); });
|
||||
thisNode.setAttribute('class', '');
|
||||
})();
|
||||
return;
|
||||
case 'pull':
|
||||
showTab('书源列表');
|
||||
(async () => {
|
||||
await HttpGet(`/getSources`).then(json => {
|
||||
if (json.isSuccess) {
|
||||
$('#RuleList').innerHTML = ''
|
||||
localStorage.setItem('RuleSources', JSON.stringify(RuleSources = json.data));
|
||||
RuleSources.forEach(item => {
|
||||
$('#RuleList').innerHTML += newRule(item);
|
||||
});
|
||||
alert(`成功拉取 ${RuleSources.length} 条书源`);
|
||||
}
|
||||
else {
|
||||
alert(`批量拉取书源失败!\nErrorMsg: ${json.errorMsg}`);
|
||||
}
|
||||
}).catch(err => { alert(`批量拉取书源失败,无法连接到「阅读APP」!\n${err}`); });
|
||||
thisNode.setAttribute('class', '');
|
||||
})();
|
||||
return;
|
||||
case 'editor':
|
||||
if ($('#RuleJsonString').value == '') break;
|
||||
try {
|
||||
json2rule(JSON.parse($('#RuleJsonString').value));
|
||||
todo();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
alert(error);
|
||||
}
|
||||
break;
|
||||
case 'conver':
|
||||
showTab('编辑书源');
|
||||
$('#RuleJsonString').value = JSON.stringify(rule2json(), null, 4);
|
||||
break;
|
||||
case 'initial':
|
||||
$$('.rules textarea').forEach(item => { item.value = '' });
|
||||
todo();
|
||||
break;
|
||||
case 'undo':
|
||||
undo()
|
||||
break;
|
||||
case 'redo':
|
||||
redo()
|
||||
break;
|
||||
case 'debug':
|
||||
showTab('调试书源');
|
||||
let wsOrigin = (hashParam('domain') || location.origin).replace(/^.*?:/, 'ws:').replace(/\d+$/, (port) => (parseInt(port) + 1));
|
||||
let DebugInfos = $('#DebugConsole');
|
||||
function DebugPrint(msg) { DebugInfos.value += `\n${msg}`; DebugInfos.scrollTop = DebugInfos.scrollHeight; }
|
||||
let saveRule = [rule2json()];
|
||||
HttpPost(`/saveSources`, saveRule).then(sResult => {
|
||||
if (sResult.isSuccess) {
|
||||
let sKey = DebugKey.value ? DebugKey.value : '我的';
|
||||
$('#DebugConsole').value = `书源《${saveRule[0].bookSourceName}》保存成功!使用搜索关键字“${sKey}”开始调试...`;
|
||||
let ws = new WebSocket(`${wsOrigin}/sourceDebug`);
|
||||
ws.onopen = () => {
|
||||
ws.send(`{"tag":"${saveRule[0].bookSourceUrl}", "key":"${sKey}"}`);
|
||||
};
|
||||
ws.onmessage = (msg) => {
|
||||
DebugPrint(msg.data == 'finish' ? `\n[${Date().split(' ')[4]}] 调试任务已完成!` : msg.data);
|
||||
if (msg.data == 'finish') setRule(saveRule[0]);
|
||||
};
|
||||
ws.onerror = (err) => {
|
||||
throw `${err.data}`;
|
||||
}
|
||||
ws.onclose = () => {
|
||||
thisNode.setAttribute('class', '');
|
||||
DebugPrint(`[${Date().split(' ')[4]}] 调试服务已关闭!`);
|
||||
}
|
||||
} else throw `${sResult.errorMsg}`;
|
||||
}).catch(err => {
|
||||
DebugPrint(`调试过程意外中止,以下是详细错误信息:\n${err}`);
|
||||
thisNode.setAttribute('class', '');
|
||||
});
|
||||
return;
|
||||
case 'accept':
|
||||
(async () => {
|
||||
let saveRule = [rule2json()];
|
||||
await HttpPost(`/saveSources`, saveRule).then(json => {
|
||||
alert(json.isSuccess ? `书源《${saveRule[0].bookSourceName}》已成功保存到「阅读APP」` : `书源《${saveRule[0].bookSourceName}》保存失败!\nErrorMsg: ${json.errorMsg}`);
|
||||
setRule(saveRule[0]);
|
||||
}).catch(err => { alert(`保存书源失败,无法连接到「阅读APP」!\n${err}`); });
|
||||
thisNode.setAttribute('class', '');
|
||||
})();
|
||||
return;
|
||||
default:
|
||||
}
|
||||
setTimeout(() => { thisNode.setAttribute('class', ''); }, 500);
|
||||
});
|
||||
$('#DebugKey').addEventListener('keydown', e => {
|
||||
if (e.keyCode == 13) {
|
||||
let clickEvent = document.createEvent('MouseEvents');
|
||||
clickEvent.initEvent("click", true, false);
|
||||
$('#debug').dispatchEvent(clickEvent);
|
||||
}
|
||||
});
|
||||
|
||||
// 列表规则更改事件
|
||||
$('#RuleList').addEventListener('click', e => {
|
||||
let editRule = null;
|
||||
if (e.target && e.target.getAttribute('name') == 'rule') {
|
||||
editRule = rule2json();
|
||||
json2rule(RuleSources.find(x => x.bookSourceUrl == e.target.id));
|
||||
} else return;
|
||||
if (editRule.bookSourceUrl == '') return;
|
||||
if (editRule.bookSourceName == '') editRule.bookSourceName = editRule.bookSourceUrl.replace(/.*?\/\/|\/.*/g, '');
|
||||
setRule(editRule);
|
||||
localStorage.setItem('RuleSources', JSON.stringify(RuleSources));
|
||||
});
|
||||
// 处理列表按钮事件
|
||||
$('.tab3>.titlebar').addEventListener('click', e => {
|
||||
let thisNode = e.target;
|
||||
if (thisNode.nodeName != 'BUTTON') return;
|
||||
switch (thisNode.id) {
|
||||
case 'Import':
|
||||
let fileImport = document.createElement('input');
|
||||
fileImport.type = 'file';
|
||||
fileImport.accept = '.json';
|
||||
fileImport.addEventListener('change', () => {
|
||||
let file = fileImport.files[0];
|
||||
let reader = new FileReader();
|
||||
reader.onloadend = function (evt) {
|
||||
if (evt.target.readyState == FileReader.DONE) {
|
||||
let fileText = evt.target.result;
|
||||
try {
|
||||
let fileJson = JSON.parse(fileText);
|
||||
let newSources = [];
|
||||
newSources.push(...fileJson);
|
||||
if (window.confirm(`如何处理导入的书源?\n"确定": 覆盖当前列表(不会删除APP源)\n"取消": 插入列表尾部(自动忽略重复源)`)) {
|
||||
localStorage.setItem('RuleSources', JSON.stringify(RuleSources = newSources));
|
||||
$('#RuleList').innerHTML = ''
|
||||
RuleSources.forEach(item => {
|
||||
$('#RuleList').innerHTML += newRule(item);
|
||||
});
|
||||
}
|
||||
else {
|
||||
newSources = newSources.filter(item => !JSON.stringify(RuleSources).includes(item.bookSourceUrl));
|
||||
RuleSources.push(...newSources);
|
||||
localStorage.setItem('RuleSources', JSON.stringify(RuleSources));
|
||||
newSources.forEach(item => {
|
||||
$('#RuleList').innerHTML += newRule(item);
|
||||
});
|
||||
}
|
||||
alert(`成功导入 ${newSources.length} 条书源`);
|
||||
}
|
||||
catch (err) {
|
||||
alert(`导入书源文件失败!\n${err}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}, false);
|
||||
fileImport.click();
|
||||
break;
|
||||
case 'Export':
|
||||
let fileExport = document.createElement('a');
|
||||
fileExport.download = `Rules${Date().replace(/.*?\s(\d+)\s(\d+)\s(\d+:\d+:\d+).*/, '$2$1$3').replace(/:/g, '')}.json`;
|
||||
let myBlob = new Blob([JSON.stringify(RuleSources, null, 4)], { type: "application/json" });
|
||||
fileExport.href = window.URL.createObjectURL(myBlob);
|
||||
fileExport.click();
|
||||
break;
|
||||
case 'Delete':
|
||||
let selectRule = $('#RuleList input:checked');
|
||||
if (!selectRule) {
|
||||
alert(`没有书源被选中!`);
|
||||
return;
|
||||
}
|
||||
if (confirm(`确定要删除选定书源吗?\n(同时删除APP内书源)`)) {
|
||||
let selectRuleUrl = selectRule.id;
|
||||
let deleteSources = RuleSources.filter(item => item.bookSourceUrl == selectRuleUrl); // 提取待删除的书源
|
||||
let laveSources = RuleSources.filter(item => !(item.bookSourceUrl == selectRuleUrl)); // 提取待留下的书源
|
||||
HttpPost(`/deleteSources`, deleteSources).then(json => {
|
||||
if (json.isSuccess) {
|
||||
let selectNode = document.getElementById(selectRuleUrl).parentNode;
|
||||
selectNode.parentNode.removeChild(selectNode);
|
||||
localStorage.setItem('RuleSources', JSON.stringify(RuleSources = laveSources));
|
||||
if ($('#bookSourceUrl').value == selectRuleUrl) {
|
||||
$$('.rules textarea').forEach(item => { item.value = '' });
|
||||
todo();
|
||||
}
|
||||
console.log(deleteSources);
|
||||
console.log(`以上书源已删除!`)
|
||||
}
|
||||
}).catch(err => { alert(`删除书源失败,无法连接到「阅读APP」!\n${err}`); });
|
||||
}
|
||||
break;
|
||||
case 'ClrAll':
|
||||
if (confirm(`确定要清空当前书源列表吗?\n(不会删除APP内书源)`)) {
|
||||
localStorage.setItem('RuleSources', JSON.stringify(RuleSources = []));
|
||||
$('#RuleList').innerHTML = ''
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
@ -1,7 +1,21 @@
|
||||
package io.legado.app
|
||||
|
||||
import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import io.legado.app.constant.AppConst.channelIdDownload
|
||||
import io.legado.app.constant.AppConst.channelIdReadAloud
|
||||
import io.legado.app.constant.AppConst.channelIdWeb
|
||||
import io.legado.app.data.AppDatabase
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import io.legado.app.utils.getPrefBoolean
|
||||
import io.legado.app.utils.getPrefInt
|
||||
import java.util.*
|
||||
|
||||
class App : Application() {
|
||||
|
||||
@ -15,9 +29,94 @@ class App : Application() {
|
||||
private set
|
||||
}
|
||||
|
||||
private var versionCode = 0
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
INSTANCE = this
|
||||
db = AppDatabase.createDatabase(INSTANCE)
|
||||
versionCode = try {
|
||||
packageManager.getPackageInfo(packageName, 0).versionCode
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
0
|
||||
}
|
||||
initNightTheme()
|
||||
if (!ThemeStore.isConfigured(this, versionCode)) {
|
||||
upThemeStore()
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createChannelId()
|
||||
}
|
||||
}
|
||||
|
||||
fun initNightTheme() {
|
||||
if (getPrefBoolean("isNightTheme", false)) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新主题
|
||||
*/
|
||||
fun upThemeStore() {
|
||||
if (getPrefBoolean("isNightTheme", false)) {
|
||||
ThemeStore.editTheme(this)
|
||||
.primaryColor(getPrefInt("colorPrimaryNight", resources.getColor(R.color.md_grey_800)))
|
||||
.accentColor(getPrefInt("colorAccentNight", resources.getColor(R.color.md_pink_800)))
|
||||
.backgroundColor(getPrefInt("colorBackgroundNight", resources.getColor(R.color.md_grey_800)))
|
||||
.apply()
|
||||
} else {
|
||||
ThemeStore.editTheme(this)
|
||||
.primaryColor(getPrefInt("colorPrimary", resources.getColor(R.color.md_grey_100)))
|
||||
.accentColor(getPrefInt("colorAccent", resources.getColor(R.color.md_pink_600)))
|
||||
.backgroundColor(getPrefInt("colorBackground", resources.getColor(R.color.md_grey_100)))
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建通知ID
|
||||
*/
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createChannelId() {
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
//用唯一的ID创建渠道对象
|
||||
val downloadChannel = NotificationChannel(
|
||||
channelIdDownload,
|
||||
getString(R.string.download_offline),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
//初始化channel
|
||||
downloadChannel.enableLights(false)
|
||||
downloadChannel.enableVibration(false)
|
||||
downloadChannel.setSound(null, null)
|
||||
|
||||
//用唯一的ID创建渠道对象
|
||||
val readAloudChannel = NotificationChannel(
|
||||
channelIdReadAloud,
|
||||
getString(R.string.read_aloud),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
//初始化channel
|
||||
readAloudChannel.enableLights(false)
|
||||
readAloudChannel.enableVibration(false)
|
||||
readAloudChannel.setSound(null, null)
|
||||
|
||||
//用唯一的ID创建渠道对象
|
||||
val webChannel = NotificationChannel(
|
||||
channelIdWeb,
|
||||
getString(R.string.web_service),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
//初始化channel
|
||||
webChannel.enableLights(false)
|
||||
webChannel.enableVibration(false)
|
||||
webChannel.setSound(null, null)
|
||||
|
||||
//向notification manager 提交channel
|
||||
notificationManager.createNotificationChannels(Arrays.asList(downloadChannel, readAloudChannel, webChannel))
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,28 @@
|
||||
package io.legado.app.base
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import io.legado.app.R
|
||||
import io.legado.app.lib.theme.ColorUtil
|
||||
import io.legado.app.lib.theme.MaterialValueHelper
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import java.util.*
|
||||
|
||||
abstract class BaseActivity<BD : ViewDataBinding, VM : ViewModel?> : AppCompatActivity() {
|
||||
|
||||
protected lateinit var dataBinding: BD
|
||||
private set
|
||||
abstract class BaseActivity<VM : ViewModel> : AppCompatActivity() {
|
||||
|
||||
protected abstract val viewModel: VM
|
||||
|
||||
protected abstract val layoutID: Int
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
initTheme()
|
||||
super.onCreate(savedInstanceState)
|
||||
dataBinding = DataBindingUtil.setContentView(this, layoutID)
|
||||
setContentView(layoutID)
|
||||
onViewModelCreated(viewModel, savedInstanceState)
|
||||
}
|
||||
|
||||
@ -27,6 +30,52 @@ abstract class BaseActivity<BD : ViewDataBinding, VM : ViewModel?> : AppCompatAc
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置MENU图标颜色
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val primaryTextColor = MaterialValueHelper
|
||||
.getPrimaryTextColor(this, ColorUtil.isColorLight(ThemeStore.primaryColor(this)))
|
||||
for (i in 0 until menu.size()) {
|
||||
val drawable = menu.getItem(i).icon
|
||||
if (drawable != null) {
|
||||
drawable.mutate()
|
||||
drawable.setColorFilter(primaryTextColor, PorterDuff.Mode.SRC_ATOP)
|
||||
}
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
override fun onMenuOpened(featureId: Int, menu: Menu?): Boolean {
|
||||
if (menu != null) {
|
||||
//展开菜单显示图标
|
||||
if (menu.javaClass.simpleName.equals("MenuBuilder", ignoreCase = true)) {
|
||||
try {
|
||||
var method = menu.javaClass.getDeclaredMethod("setOptionalIconsVisible", java.lang.Boolean.TYPE)
|
||||
method.isAccessible = true
|
||||
method.invoke(menu, true)
|
||||
method = menu.javaClass.getDeclaredMethod("getNonActionItems")
|
||||
val menuItems = method.invoke(menu) as ArrayList<MenuItem>
|
||||
if (menuItems.isNotEmpty()) {
|
||||
for (menuItem in menuItems) {
|
||||
val drawable = menuItem.icon
|
||||
if (drawable != null) {
|
||||
drawable.mutate()
|
||||
drawable.setColorFilter(
|
||||
resources.getColor(R.color.tv_text_default),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onMenuOpened(featureId, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
item?.let {
|
||||
if (it.itemId == android.R.id.home) {
|
||||
@ -40,4 +89,12 @@ abstract class BaseActivity<BD : ViewDataBinding, VM : ViewModel?> : AppCompatAc
|
||||
open fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
protected fun initTheme() {
|
||||
if (ColorUtil.isColorLight(ThemeStore.primaryColor(this))) {
|
||||
setTheme(R.style.CAppTheme)
|
||||
} else {
|
||||
setTheme(R.style.CAppThemeBarDark)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public final class ATH {
|
||||
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
public static boolean didThemeValuesChange(@NonNull Context context, long since) {
|
||||
return ThemeStore.isConfigured(context) && ThemeStore.prefs(context).getLong(ThemeStore.VALUES_CHANGED, -1) > since;
|
||||
}
|
||||
|
||||
public static void setStatusbarColorAuto(Activity activity) {
|
||||
setStatusbarColor(activity, ThemeStore.statusBarColor(activity));
|
||||
}
|
||||
|
||||
public static void setStatusbarColor(Activity activity, int color) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.getWindow().setStatusBarColor(color);
|
||||
setLightStatusbarAuto(activity, color);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLightStatusbarAuto(Activity activity, int bgColor) {
|
||||
setLightStatusbar(activity, ColorUtil.isColorLight(bgColor));
|
||||
}
|
||||
|
||||
public static void setLightStatusbar(Activity activity, boolean enabled) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
final View decorView = activity.getWindow().getDecorView();
|
||||
final int systemUiVisibility = decorView.getSystemUiVisibility();
|
||||
if (enabled) {
|
||||
decorView.setSystemUiVisibility(systemUiVisibility | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
} else {
|
||||
decorView.setSystemUiVisibility(systemUiVisibility & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLightNavigationbar(Activity activity, boolean enabled) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
final View decorView = activity.getWindow().getDecorView();
|
||||
int systemUiVisibility = decorView.getSystemUiVisibility();
|
||||
if (enabled) {
|
||||
systemUiVisibility |= SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
} else {
|
||||
systemUiVisibility &= ~SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||
}
|
||||
decorView.setSystemUiVisibility(systemUiVisibility);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLightNavigationbarAuto(Activity activity, int bgColor) {
|
||||
setLightNavigationbar(activity, ColorUtil.isColorLight(bgColor));
|
||||
}
|
||||
|
||||
public static void setNavigationbarColorAuto(Activity activity) {
|
||||
setNavigationbarColor(activity, ThemeStore.navigationBarColor(activity));
|
||||
}
|
||||
|
||||
public static void setNavigationbarColor(Activity activity, int color) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.getWindow().setNavigationBarColor(color);
|
||||
setLightNavigationbarAuto(activity, color);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTaskDescriptionColorAuto(@NonNull Activity activity) {
|
||||
setTaskDescriptionColor(activity, ThemeStore.primaryColor(activity));
|
||||
}
|
||||
|
||||
public static void setTaskDescriptionColor(@NonNull Activity activity, @ColorInt int color) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Task description requires fully opaque color
|
||||
color = ColorUtil.stripAlpha(color);
|
||||
// Sets color of entry in the system recents page
|
||||
activity.setTaskDescription(new ActivityManager.TaskDescription((String) activity.getTitle(), null, color));
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull View view, @ColorInt int color) {
|
||||
TintHelper.setTintAuto(view, color, false);
|
||||
}
|
||||
|
||||
public static void setBackgroundTint(@NonNull View view, @ColorInt int color) {
|
||||
TintHelper.setTintAuto(view, color, true);
|
||||
}
|
||||
|
||||
public static void setAlertDialogTint(@NonNull AlertDialog dialog) {
|
||||
ColorStateList colorStateList = Selector.colorBuild()
|
||||
.setDefaultColor(ThemeStore.accentColor(dialog.getContext()))
|
||||
.setPressedColor(ColorUtil.darkenColor(ThemeStore.accentColor(dialog.getContext())))
|
||||
.create();
|
||||
if (dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE) != null) {
|
||||
dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEGATIVE).setTextColor(colorStateList);
|
||||
}
|
||||
if (dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE) != null) {
|
||||
dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setTextColor(colorStateList);
|
||||
}
|
||||
}
|
||||
|
||||
private ATH() {
|
||||
}
|
||||
}
|
116
app/src/main/java/io/legado/app/lib/theme/ATH.kt
Normal file
116
app/src/main/java/io/legado/app/lib/theme/ATH.kt
Normal file
@ -0,0 +1,116 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
object ATH {
|
||||
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
fun didThemeValuesChange(context: Context, since: Long): Boolean {
|
||||
return ThemeStore.isConfigured(context) && ThemeStore.prefs(context).getLong(
|
||||
ThemeStore.VALUES_CHANGED,
|
||||
-1
|
||||
) > since
|
||||
}
|
||||
|
||||
fun setStatusbarColorAuto(activity: Activity) {
|
||||
setStatusbarColor(activity, ThemeStore.statusBarColor(activity))
|
||||
}
|
||||
|
||||
fun setStatusbarColor(activity: Activity, color: Int) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.statusBarColor = color
|
||||
setLightStatusbarAuto(activity, color)
|
||||
}
|
||||
}
|
||||
|
||||
fun setLightStatusbarAuto(activity: Activity, bgColor: Int) {
|
||||
setLightStatusbar(activity, ColorUtil.isColorLight(bgColor))
|
||||
}
|
||||
|
||||
fun setLightStatusbar(activity: Activity, enabled: Boolean) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val decorView = activity.window.decorView
|
||||
val systemUiVisibility = decorView.systemUiVisibility
|
||||
if (enabled) {
|
||||
decorView.systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
} else {
|
||||
decorView.systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setLightNavigationbar(activity: Activity, enabled: Boolean) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val decorView = activity.window.decorView
|
||||
var systemUiVisibility = decorView.systemUiVisibility
|
||||
if (enabled) {
|
||||
systemUiVisibility = systemUiVisibility or SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||
} else {
|
||||
systemUiVisibility = systemUiVisibility and SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
|
||||
}
|
||||
decorView.systemUiVisibility = systemUiVisibility
|
||||
}
|
||||
}
|
||||
|
||||
fun setLightNavigationbarAuto(activity: Activity, bgColor: Int) {
|
||||
setLightNavigationbar(activity, ColorUtil.isColorLight(bgColor))
|
||||
}
|
||||
|
||||
fun setNavigationbarColorAuto(activity: Activity) {
|
||||
setNavigationbarColor(activity, ThemeStore.navigationBarColor(activity))
|
||||
}
|
||||
|
||||
fun setNavigationbarColor(activity: Activity, color: Int) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
activity.window.navigationBarColor = color
|
||||
setLightNavigationbarAuto(activity, color)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTaskDescriptionColorAuto(activity: Activity) {
|
||||
setTaskDescriptionColor(activity, ThemeStore.primaryColor(activity))
|
||||
}
|
||||
|
||||
fun setTaskDescriptionColor(activity: Activity, @ColorInt color: Int) {
|
||||
val color1: Int
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Task description requires fully opaque color
|
||||
color1 = ColorUtil.stripAlpha(color)
|
||||
// Sets color of entry in the system recents page
|
||||
activity.setTaskDescription(ActivityManager.TaskDescription(activity.title as String, null, color1))
|
||||
}
|
||||
}
|
||||
|
||||
fun setTint(view: View, @ColorInt color: Int) {
|
||||
TintHelper.setTintAuto(view, color, false)
|
||||
}
|
||||
|
||||
fun setBackgroundTint(view: View, @ColorInt color: Int) {
|
||||
TintHelper.setTintAuto(view, color, true)
|
||||
}
|
||||
|
||||
fun setAlertDialogTint(dialog: AlertDialog): AlertDialog {
|
||||
val colorStateList = Selector.colorBuild()
|
||||
.setDefaultColor(ThemeStore.accentColor(dialog.context))
|
||||
.setPressedColor(ColorUtil.darkenColor(ThemeStore.accentColor(dialog.context)))
|
||||
.create()
|
||||
if (dialog.getButton(AlertDialog.BUTTON_NEGATIVE) != null) {
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(colorStateList)
|
||||
}
|
||||
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(colorStateList)
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import androidx.annotation.AttrRes;
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
public final class ATHUtil {
|
||||
|
||||
public static boolean isWindowBackgroundDark(Context context) {
|
||||
return !ColorUtil.isColorLight(ATHUtil.resolveColor(context, android.R.attr.windowBackground));
|
||||
}
|
||||
|
||||
public static int resolveColor(Context context, @AttrRes int attr) {
|
||||
return resolveColor(context, attr, 0);
|
||||
}
|
||||
|
||||
public static int resolveColor(Context context, @AttrRes int attr, int fallback) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr});
|
||||
try {
|
||||
return a.getColor(0, fallback);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
private ATHUtil() {
|
||||
}
|
||||
}
|
24
app/src/main/java/io/legado/app/lib/theme/ATHUtil.kt
Normal file
24
app/src/main/java/io/legado/app/lib/theme/ATHUtil.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.AttrRes
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
object ATHUtil {
|
||||
|
||||
fun isWindowBackgroundDark(context: Context): Boolean {
|
||||
return !ColorUtil.isColorLight(ATHUtil.resolveColor(context, android.R.attr.windowBackground))
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun resolveColor(context: Context, @AttrRes attr: Int, fallback: Int = 0): Int {
|
||||
val a = context.theme.obtainStyledAttributes(intArrayOf(attr))
|
||||
try {
|
||||
return a.getColor(0, fallback)
|
||||
} finally {
|
||||
a.recycle()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.graphics.Color;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.FloatRange;
|
||||
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
public class ColorUtil {
|
||||
|
||||
public static String intToString(int intColor) {
|
||||
return String.format("#%06X", 0xFFFFFF & intColor);
|
||||
}
|
||||
|
||||
|
||||
public static int stripAlpha(@ColorInt int color) {
|
||||
return 0xff000000 | color;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int shiftColor(@ColorInt int color, @FloatRange(from = 0.0f, to = 2.0f) float by) {
|
||||
if (by == 1f) return color;
|
||||
int alpha = Color.alpha(color);
|
||||
float[] hsv = new float[3];
|
||||
Color.colorToHSV(color, hsv);
|
||||
hsv[2] *= by; // value component
|
||||
return (alpha << 24) + (0x00ffffff & Color.HSVToColor(hsv));
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int darkenColor(@ColorInt int color) {
|
||||
return shiftColor(color, 0.9f);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int lightenColor(@ColorInt int color) {
|
||||
return shiftColor(color, 1.1f);
|
||||
}
|
||||
|
||||
public static boolean isColorLight(@ColorInt int color) {
|
||||
final double darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
|
||||
return darkness < 0.4;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int invertColor(@ColorInt int color) {
|
||||
final int r = 255 - Color.red(color);
|
||||
final int g = 255 - Color.green(color);
|
||||
final int b = 255 - Color.blue(color);
|
||||
return Color.argb(Color.alpha(color), r, g, b);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int adjustAlpha(@ColorInt int color, @FloatRange(from = 0.0, to = 1.0) float factor) {
|
||||
int alpha = Math.round(Color.alpha(color) * factor);
|
||||
int red = Color.red(color);
|
||||
int green = Color.green(color);
|
||||
int blue = Color.blue(color);
|
||||
return Color.argb(alpha, red, green, blue);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int withAlpha(@ColorInt int baseColor, @FloatRange(from = 0.0, to = 1.0) float alpha) {
|
||||
int a = Math.min(255, Math.max(0, (int) (alpha * 255))) << 24;
|
||||
int rgb = 0x00ffffff & baseColor;
|
||||
return a + rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from CollapsingToolbarLayout's CollapsingTextHelper class.
|
||||
*/
|
||||
public static int blendColors(int color1, int color2, @FloatRange(from = 0.0, to = 1.0) float ratio) {
|
||||
final float inverseRatio = 1f - ratio;
|
||||
float a = (Color.alpha(color1) * inverseRatio) + (Color.alpha(color2) * ratio);
|
||||
float r = (Color.red(color1) * inverseRatio) + (Color.red(color2) * ratio);
|
||||
float g = (Color.green(color1) * inverseRatio) + (Color.green(color2) * ratio);
|
||||
float b = (Color.blue(color1) * inverseRatio) + (Color.blue(color2) * ratio);
|
||||
return Color.argb((int) a, (int) r, (int) g, (int) b);
|
||||
}
|
||||
|
||||
}
|
79
app/src/main/java/io/legado/app/lib/theme/ColorUtil.kt
Normal file
79
app/src/main/java/io/legado/app/lib/theme/ColorUtil.kt
Normal file
@ -0,0 +1,79 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.FloatRange
|
||||
|
||||
object ColorUtil {
|
||||
|
||||
fun intToString(intColor: Int): String {
|
||||
return String.format("#%06X", 0xFFFFFF and intColor)
|
||||
}
|
||||
|
||||
|
||||
fun stripAlpha(@ColorInt color: Int): Int {
|
||||
return -0x1000000 or color
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun shiftColor(@ColorInt color: Int, @FloatRange(from = 0.0, to = 2.0) by: Float): Int {
|
||||
if (by == 1f) return color
|
||||
val alpha = Color.alpha(color)
|
||||
val hsv = FloatArray(3)
|
||||
Color.colorToHSV(color, hsv)
|
||||
hsv[2] *= by // value component
|
||||
return (alpha shl 24) + (0x00ffffff and Color.HSVToColor(hsv))
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun darkenColor(@ColorInt color: Int): Int {
|
||||
return shiftColor(color, 0.9f)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun lightenColor(@ColorInt color: Int): Int {
|
||||
return shiftColor(color, 1.1f)
|
||||
}
|
||||
|
||||
fun isColorLight(@ColorInt color: Int): Boolean {
|
||||
val darkness = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255
|
||||
return darkness < 0.4
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun invertColor(@ColorInt color: Int): Int {
|
||||
val r = 255 - Color.red(color)
|
||||
val g = 255 - Color.green(color)
|
||||
val b = 255 - Color.blue(color)
|
||||
return Color.argb(Color.alpha(color), r, g, b)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun adjustAlpha(@ColorInt color: Int, @FloatRange(from = 0.0, to = 1.0) factor: Float): Int {
|
||||
val alpha = Math.round(Color.alpha(color) * factor)
|
||||
val red = Color.red(color)
|
||||
val green = Color.green(color)
|
||||
val blue = Color.blue(color)
|
||||
return Color.argb(alpha, red, green, blue)
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun withAlpha(@ColorInt baseColor: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float): Int {
|
||||
val a = Math.min(255, Math.max(0, (alpha * 255).toInt())) shl 24
|
||||
val rgb = 0x00ffffff and baseColor
|
||||
return a + rgb
|
||||
}
|
||||
|
||||
/**
|
||||
* Taken from CollapsingToolbarLayout's CollapsingTextHelper class.
|
||||
*/
|
||||
fun blendColors(color1: Int, color2: Int, @FloatRange(from = 0.0, to = 1.0) ratio: Float): Int {
|
||||
val inverseRatio = 1f - ratio
|
||||
val a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio
|
||||
val r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio
|
||||
val g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio
|
||||
val b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio
|
||||
return Color.argb(a.toInt(), r.toInt(), g.toInt(), b.toInt())
|
||||
}
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import androidx.annotation.ColorInt;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public final class DrawableUtil {
|
||||
|
||||
public static TransitionDrawable createTransitionDrawable(@ColorInt int startColor, @ColorInt int endColor) {
|
||||
return createTransitionDrawable(new ColorDrawable(startColor), new ColorDrawable(endColor));
|
||||
}
|
||||
|
||||
public static TransitionDrawable createTransitionDrawable(Drawable start, Drawable end) {
|
||||
final Drawable[] drawables = new Drawable[2];
|
||||
|
||||
drawables[0] = start;
|
||||
drawables[1] = end;
|
||||
|
||||
return new TransitionDrawable(drawables);
|
||||
}
|
||||
|
||||
private DrawableUtil() {
|
||||
}
|
||||
}
|
25
app/src/main/java/io/legado/app/lib/theme/DrawableUtil.kt
Normal file
25
app/src/main/java/io/legado/app/lib/theme/DrawableUtil.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.TransitionDrawable
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
object DrawableUtil {
|
||||
|
||||
fun createTransitionDrawable(@ColorInt startColor: Int, @ColorInt endColor: Int): TransitionDrawable {
|
||||
return createTransitionDrawable(ColorDrawable(startColor), ColorDrawable(endColor))
|
||||
}
|
||||
|
||||
fun createTransitionDrawable(start: Drawable, end: Drawable): TransitionDrawable {
|
||||
val drawables = arrayOfNulls<Drawable>(2)
|
||||
|
||||
drawables[0] = start
|
||||
drawables[1] = end
|
||||
|
||||
return TransitionDrawable(drawables)
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import io.legado.app.R;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public final class MaterialValueHelper {
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
public static int getPrimaryTextColor(final Context context, boolean dark) {
|
||||
if (dark) {
|
||||
return ContextCompat.getColor(context, R.color.primary_text_default_material_light);
|
||||
}
|
||||
return ContextCompat.getColor(context, R.color.primary_text_default_material_dark);
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
public static int getSecondaryTextColor(final Context context, boolean dark) {
|
||||
if (dark) {
|
||||
return ContextCompat.getColor(context, R.color.secondary_text_default_material_light);
|
||||
}
|
||||
return ContextCompat.getColor(context, R.color.secondary_text_default_material_dark);
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
public static int getPrimaryDisabledTextColor(final Context context, boolean dark) {
|
||||
if (dark) {
|
||||
return ContextCompat.getColor(context, R.color.primary_text_disabled_material_light);
|
||||
}
|
||||
return ContextCompat.getColor(context, R.color.primary_text_disabled_material_dark);
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
public static int getSecondaryDisabledTextColor(final Context context, boolean dark) {
|
||||
if (dark) {
|
||||
return ContextCompat.getColor(context, R.color.secondary_text_disabled_material_light);
|
||||
}
|
||||
return ContextCompat.getColor(context, R.color.secondary_text_disabled_material_dark);
|
||||
}
|
||||
|
||||
private MaterialValueHelper() {
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.legado.app.R
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
object MaterialValueHelper {
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
fun getPrimaryTextColor(context: Context, dark: Boolean): Int {
|
||||
return if (dark) {
|
||||
ContextCompat.getColor(context, R.color.primary_text_default_material_light)
|
||||
} else ContextCompat.getColor(context, R.color.primary_text_default_material_dark)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
fun getSecondaryTextColor(context: Context, dark: Boolean): Int {
|
||||
return if (dark) {
|
||||
ContextCompat.getColor(context, R.color.secondary_text_default_material_light)
|
||||
} else ContextCompat.getColor(context, R.color.secondary_text_default_material_dark)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
fun getPrimaryDisabledTextColor(context: Context, dark: Boolean): Int {
|
||||
return if (dark) {
|
||||
ContextCompat.getColor(context, R.color.primary_text_disabled_material_light)
|
||||
} else ContextCompat.getColor(context, R.color.primary_text_disabled_material_dark)
|
||||
}
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
fun getSecondaryDisabledTextColor(context: Context, dark: Boolean): Int {
|
||||
return if (dark) {
|
||||
ContextCompat.getColor(context, R.color.secondary_text_disabled_material_light)
|
||||
} else ContextCompat.getColor(context, R.color.secondary_text_disabled_material_dark)
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.android.material.internal.NavigationMenuView;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public final class NavigationViewUtil {
|
||||
|
||||
public static void setItemIconColors(@NonNull NavigationView navigationView, @ColorInt int normalColor, @ColorInt int selectedColor) {
|
||||
final ColorStateList iconSl = new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{-android.R.attr.state_checked},
|
||||
new int[]{android.R.attr.state_checked}
|
||||
},
|
||||
new int[]{
|
||||
normalColor,
|
||||
selectedColor
|
||||
});
|
||||
navigationView.setItemIconTintList(iconSl);
|
||||
}
|
||||
|
||||
public static void setItemTextColors(@NonNull NavigationView navigationView, @ColorInt int normalColor, @ColorInt int selectedColor) {
|
||||
final ColorStateList textSl = new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{-android.R.attr.state_checked},
|
||||
new int[]{android.R.attr.state_checked}
|
||||
},
|
||||
new int[]{
|
||||
normalColor,
|
||||
selectedColor
|
||||
});
|
||||
navigationView.setItemTextColor(textSl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 去掉navigationView的滚动条
|
||||
* @param navigationView NavigationView
|
||||
*/
|
||||
public static void disableScrollbar(NavigationView navigationView) {
|
||||
if (navigationView != null) {
|
||||
NavigationMenuView navigationMenuView = (NavigationMenuView) navigationView.getChildAt(0);
|
||||
if (navigationMenuView != null) {
|
||||
navigationMenuView.setVerticalScrollBarEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NavigationViewUtil() {
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import androidx.annotation.ColorInt
|
||||
import com.google.android.material.internal.NavigationMenuView
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
object NavigationViewUtil {
|
||||
|
||||
fun setItemIconColors(navigationView: NavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) {
|
||||
val iconSl = ColorStateList(
|
||||
arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)),
|
||||
intArrayOf(normalColor, selectedColor)
|
||||
)
|
||||
navigationView.itemIconTintList = iconSl
|
||||
}
|
||||
|
||||
fun setItemTextColors(navigationView: NavigationView, @ColorInt normalColor: Int, @ColorInt selectedColor: Int) {
|
||||
val textSl = ColorStateList(
|
||||
arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked)),
|
||||
intArrayOf(normalColor, selectedColor)
|
||||
)
|
||||
navigationView.itemTextColor = textSl
|
||||
}
|
||||
|
||||
/**
|
||||
* 去掉navigationView的滚动条
|
||||
* @param navigationView NavigationView
|
||||
*/
|
||||
fun disableScrollbar(navigationView: NavigationView?) {
|
||||
navigationView ?: return
|
||||
val navigationMenuView = navigationView.getChildAt(0) as? NavigationMenuView
|
||||
navigationMenuView?.isVerticalScrollBarEnabled = false
|
||||
}
|
||||
}
|
@ -1,430 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.Dimension;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class Selector {
|
||||
public static ShapeSelector shapeBuild() {
|
||||
return new ShapeSelector();
|
||||
}
|
||||
|
||||
public static ColorSelector colorBuild() {
|
||||
return new ColorSelector();
|
||||
}
|
||||
|
||||
public static DrawableSelector drawableBuild() {
|
||||
return new DrawableSelector();
|
||||
}
|
||||
|
||||
/**
|
||||
* 形状ShapeSelector
|
||||
*
|
||||
* @author hjy
|
||||
* created at 2017/12/11 22:26
|
||||
*/
|
||||
public static final class ShapeSelector {
|
||||
@IntDef({GradientDrawable.RECTANGLE, GradientDrawable.OVAL,
|
||||
GradientDrawable.LINE, GradientDrawable.RING})
|
||||
private @interface Shape {
|
||||
}
|
||||
|
||||
private int mShape; //the shape of background
|
||||
private int mDefaultBgColor; //default background color
|
||||
private int mDisabledBgColor; //state_enabled = false
|
||||
private int mPressedBgColor; //state_pressed = true
|
||||
private int mSelectedBgColor; //state_selected = true
|
||||
private int mFocusedBgColor; //state_focused = true
|
||||
private int mCheckedBgColor; //state_checked = true
|
||||
private int mStrokeWidth; //stroke width in pixel
|
||||
private int mDefaultStrokeColor; //default stroke color
|
||||
private int mDisabledStrokeColor; //state_enabled = false
|
||||
private int mPressedStrokeColor; //state_pressed = true
|
||||
private int mSelectedStrokeColor; //state_selected = true
|
||||
private int mFocusedStrokeColor; //state_focused = true
|
||||
private int mCheckedStrokeColor; //state_checked = true
|
||||
private int mCornerRadius; //corner radius
|
||||
|
||||
private boolean hasSetDisabledBgColor = false;
|
||||
private boolean hasSetPressedBgColor = false;
|
||||
private boolean hasSetSelectedBgColor = false;
|
||||
private boolean hasSetFocusedBgColor = false;
|
||||
private boolean hasSetCheckedBgColor = false;
|
||||
|
||||
private boolean hasSetDisabledStrokeColor = false;
|
||||
private boolean hasSetPressedStrokeColor = false;
|
||||
private boolean hasSetSelectedStrokeColor = false;
|
||||
private boolean hasSetFocusedStrokeColor = false;
|
||||
private boolean hasSetCheckedStrokeColor = false;
|
||||
|
||||
public ShapeSelector() {
|
||||
//initialize default values
|
||||
mShape = GradientDrawable.RECTANGLE;
|
||||
mDefaultBgColor = Color.TRANSPARENT;
|
||||
mDisabledBgColor = Color.TRANSPARENT;
|
||||
mPressedBgColor = Color.TRANSPARENT;
|
||||
mSelectedBgColor = Color.TRANSPARENT;
|
||||
mFocusedBgColor = Color.TRANSPARENT;
|
||||
mStrokeWidth = 0;
|
||||
mDefaultStrokeColor = Color.TRANSPARENT;
|
||||
mDisabledStrokeColor = Color.TRANSPARENT;
|
||||
mPressedStrokeColor = Color.TRANSPARENT;
|
||||
mSelectedStrokeColor = Color.TRANSPARENT;
|
||||
mFocusedStrokeColor = Color.TRANSPARENT;
|
||||
mCornerRadius = 0;
|
||||
}
|
||||
|
||||
public ShapeSelector setShape(@Shape int shape) {
|
||||
mShape = shape;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setDefaultBgColor(@ColorInt int color) {
|
||||
mDefaultBgColor = color;
|
||||
if (!hasSetDisabledBgColor)
|
||||
mDisabledBgColor = color;
|
||||
if (!hasSetPressedBgColor)
|
||||
mPressedBgColor = color;
|
||||
if (!hasSetSelectedBgColor)
|
||||
mSelectedBgColor = color;
|
||||
if (!hasSetFocusedBgColor)
|
||||
mFocusedBgColor = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setDisabledBgColor(@ColorInt int color) {
|
||||
mDisabledBgColor = color;
|
||||
hasSetDisabledBgColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setPressedBgColor(@ColorInt int color) {
|
||||
mPressedBgColor = color;
|
||||
hasSetPressedBgColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setSelectedBgColor(@ColorInt int color) {
|
||||
mSelectedBgColor = color;
|
||||
hasSetSelectedBgColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setFocusedBgColor(@ColorInt int color) {
|
||||
mFocusedBgColor = color;
|
||||
hasSetPressedBgColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setCheckedBgColor(@ColorInt int color) {
|
||||
mCheckedBgColor = color;
|
||||
hasSetCheckedBgColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setStrokeWidth(@Dimension int width) {
|
||||
mStrokeWidth = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setDefaultStrokeColor(@ColorInt int color) {
|
||||
mDefaultStrokeColor = color;
|
||||
if (!hasSetDisabledStrokeColor)
|
||||
mDisabledStrokeColor = color;
|
||||
if (!hasSetPressedStrokeColor)
|
||||
mPressedStrokeColor = color;
|
||||
if (!hasSetSelectedStrokeColor)
|
||||
mSelectedStrokeColor = color;
|
||||
if (!hasSetFocusedStrokeColor)
|
||||
mFocusedStrokeColor = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setDisabledStrokeColor(@ColorInt int color) {
|
||||
mDisabledStrokeColor = color;
|
||||
hasSetDisabledStrokeColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setPressedStrokeColor(@ColorInt int color) {
|
||||
mPressedStrokeColor = color;
|
||||
hasSetPressedStrokeColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setSelectedStrokeColor(@ColorInt int color) {
|
||||
mSelectedStrokeColor = color;
|
||||
hasSetSelectedStrokeColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setCheckedStrokeColor(@ColorInt int color) {
|
||||
mCheckedStrokeColor = color;
|
||||
hasSetCheckedStrokeColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setFocusedStrokeColor(@ColorInt int color) {
|
||||
mFocusedStrokeColor = color;
|
||||
hasSetFocusedStrokeColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeSelector setCornerRadius(@Dimension int radius) {
|
||||
mCornerRadius = radius;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StateListDrawable create() {
|
||||
StateListDrawable selector = new StateListDrawable();
|
||||
|
||||
//enabled = false
|
||||
if (hasSetDisabledBgColor || hasSetDisabledStrokeColor) {
|
||||
GradientDrawable disabledShape = getItemShape(mShape, mCornerRadius,
|
||||
mDisabledBgColor, mStrokeWidth, mDisabledStrokeColor);
|
||||
selector.addState(new int[]{-android.R.attr.state_enabled}, disabledShape);
|
||||
}
|
||||
|
||||
//pressed = true
|
||||
if (hasSetPressedBgColor || hasSetPressedStrokeColor) {
|
||||
GradientDrawable pressedShape = getItemShape(mShape, mCornerRadius,
|
||||
mPressedBgColor, mStrokeWidth, mPressedStrokeColor);
|
||||
selector.addState(new int[]{android.R.attr.state_pressed}, pressedShape);
|
||||
}
|
||||
|
||||
//selected = true
|
||||
if (hasSetSelectedBgColor || hasSetSelectedStrokeColor) {
|
||||
GradientDrawable selectedShape = getItemShape(mShape, mCornerRadius,
|
||||
mSelectedBgColor, mStrokeWidth, mSelectedStrokeColor);
|
||||
selector.addState(new int[]{android.R.attr.state_selected}, selectedShape);
|
||||
}
|
||||
|
||||
//focused = true
|
||||
if (hasSetFocusedBgColor || hasSetFocusedStrokeColor) {
|
||||
GradientDrawable focusedShape = getItemShape(mShape, mCornerRadius,
|
||||
mFocusedBgColor, mStrokeWidth, mFocusedStrokeColor);
|
||||
selector.addState(new int[]{android.R.attr.state_focused}, focusedShape);
|
||||
}
|
||||
|
||||
//checked = true
|
||||
if (hasSetCheckedBgColor || hasSetCheckedStrokeColor) {
|
||||
GradientDrawable checkedShape = getItemShape(mShape, mCornerRadius,
|
||||
mCheckedBgColor, mStrokeWidth, mCheckedStrokeColor);
|
||||
selector.addState(new int[]{android.R.attr.state_checked}, checkedShape);
|
||||
}
|
||||
|
||||
//default
|
||||
GradientDrawable defaultShape = getItemShape(mShape, mCornerRadius,
|
||||
mDefaultBgColor, mStrokeWidth, mDefaultStrokeColor);
|
||||
selector.addState(new int[]{}, defaultShape);
|
||||
|
||||
return selector;
|
||||
}
|
||||
|
||||
private GradientDrawable getItemShape(int shape, int cornerRadius,
|
||||
int solidColor, int strokeWidth, int strokeColor) {
|
||||
GradientDrawable drawable = new GradientDrawable();
|
||||
drawable.setShape(shape);
|
||||
drawable.setStroke(strokeWidth, strokeColor);
|
||||
drawable.setCornerRadius(cornerRadius);
|
||||
drawable.setColor(solidColor);
|
||||
return drawable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源DrawableSelector
|
||||
*
|
||||
* @author hjy
|
||||
* created at 2017/12/11 22:34
|
||||
*/
|
||||
public static final class DrawableSelector {
|
||||
|
||||
private Drawable mDefaultDrawable;
|
||||
private Drawable mDisabledDrawable;
|
||||
private Drawable mPressedDrawable;
|
||||
private Drawable mSelectedDrawable;
|
||||
private Drawable mFocusedDrawable;
|
||||
|
||||
private boolean hasSetDisabledDrawable = false;
|
||||
private boolean hasSetPressedDrawable = false;
|
||||
private boolean hasSetSelectedDrawable = false;
|
||||
private boolean hasSetFocusedDrawable = false;
|
||||
|
||||
private DrawableSelector() {
|
||||
mDefaultDrawable = new ColorDrawable(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
public DrawableSelector setDefaultDrawable(Drawable drawable) {
|
||||
mDefaultDrawable = drawable;
|
||||
if (!hasSetDisabledDrawable)
|
||||
mDisabledDrawable = drawable;
|
||||
if (!hasSetPressedDrawable)
|
||||
mPressedDrawable = drawable;
|
||||
if (!hasSetSelectedDrawable)
|
||||
mSelectedDrawable = drawable;
|
||||
if (!hasSetFocusedDrawable)
|
||||
mFocusedDrawable = drawable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DrawableSelector setDisabledDrawable(Drawable drawable) {
|
||||
mDisabledDrawable = drawable;
|
||||
hasSetDisabledDrawable = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DrawableSelector setPressedDrawable(Drawable drawable) {
|
||||
mPressedDrawable = drawable;
|
||||
hasSetPressedDrawable = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DrawableSelector setSelectedDrawable(Drawable drawable) {
|
||||
mSelectedDrawable = drawable;
|
||||
hasSetSelectedDrawable = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DrawableSelector setFocusedDrawable(Drawable drawable) {
|
||||
mFocusedDrawable = drawable;
|
||||
hasSetFocusedDrawable = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StateListDrawable create() {
|
||||
StateListDrawable selector = new StateListDrawable();
|
||||
if (hasSetDisabledDrawable)
|
||||
selector.addState(new int[]{-android.R.attr.state_enabled}, mDisabledDrawable);
|
||||
if (hasSetPressedDrawable)
|
||||
selector.addState(new int[]{android.R.attr.state_pressed}, mPressedDrawable);
|
||||
if (hasSetSelectedDrawable)
|
||||
selector.addState(new int[]{android.R.attr.state_selected}, mSelectedDrawable);
|
||||
if (hasSetFocusedDrawable)
|
||||
selector.addState(new int[]{android.R.attr.state_focused}, mFocusedDrawable);
|
||||
selector.addState(new int[]{}, mDefaultDrawable);
|
||||
return selector;
|
||||
}
|
||||
|
||||
public DrawableSelector setDefaultDrawable(Context context, @DrawableRes int drawableRes) {
|
||||
return setDefaultDrawable(ContextCompat.getDrawable(context, drawableRes));
|
||||
}
|
||||
|
||||
public DrawableSelector setDisabledDrawable(Context context, @DrawableRes int drawableRes) {
|
||||
return setDisabledDrawable(ContextCompat.getDrawable(context, drawableRes));
|
||||
}
|
||||
|
||||
public DrawableSelector setPressedDrawable(Context context, @DrawableRes int drawableRes) {
|
||||
return setPressedDrawable(ContextCompat.getDrawable(context, drawableRes));
|
||||
}
|
||||
|
||||
public DrawableSelector setSelectedDrawable(Context context, @DrawableRes int drawableRes) {
|
||||
return setSelectedDrawable(ContextCompat.getDrawable(context, drawableRes));
|
||||
}
|
||||
|
||||
public DrawableSelector setFocusedDrawable(Context context, @DrawableRes int drawableRes) {
|
||||
return setFocusedDrawable(ContextCompat.getDrawable(context, drawableRes));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色ColorSelector
|
||||
*
|
||||
* @author hjy
|
||||
* created at 2017/12/11 22:26
|
||||
*/
|
||||
public static final class ColorSelector {
|
||||
|
||||
private int mDefaultColor;
|
||||
private int mDisabledColor;
|
||||
private int mPressedColor;
|
||||
private int mSelectedColor;
|
||||
private int mFocusedColor;
|
||||
private int mCheckedColor;
|
||||
|
||||
private boolean hasSetDisabledColor = false;
|
||||
private boolean hasSetPressedColor = false;
|
||||
private boolean hasSetSelectedColor = false;
|
||||
private boolean hasSetFocusedColor = false;
|
||||
private boolean hasSetCheckedColor = false;
|
||||
|
||||
private ColorSelector() {
|
||||
mDefaultColor = Color.BLACK;
|
||||
mDisabledColor = Color.GRAY;
|
||||
mPressedColor = Color.BLACK;
|
||||
mSelectedColor = Color.BLACK;
|
||||
mFocusedColor = Color.BLACK;
|
||||
}
|
||||
|
||||
public ColorSelector setDefaultColor(@ColorInt int color) {
|
||||
mDefaultColor = color;
|
||||
if (!hasSetDisabledColor)
|
||||
mDisabledColor = color;
|
||||
if (!hasSetPressedColor)
|
||||
mPressedColor = color;
|
||||
if (!hasSetSelectedColor)
|
||||
mSelectedColor = color;
|
||||
if (!hasSetFocusedColor)
|
||||
mFocusedColor = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorSelector setDisabledColor(@ColorInt int color) {
|
||||
mDisabledColor = color;
|
||||
hasSetDisabledColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorSelector setPressedColor(@ColorInt int color) {
|
||||
mPressedColor = color;
|
||||
hasSetPressedColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorSelector setSelectedColor(@ColorInt int color) {
|
||||
mSelectedColor = color;
|
||||
hasSetSelectedColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorSelector setFocusedColor(@ColorInt int color) {
|
||||
mFocusedColor = color;
|
||||
hasSetFocusedColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorSelector setCheckedColor(@ColorInt int color) {
|
||||
mCheckedColor = color;
|
||||
hasSetCheckedColor = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ColorStateList create() {
|
||||
int[] colors = new int[]{
|
||||
hasSetDisabledColor ? mDisabledColor : mDefaultColor,
|
||||
hasSetPressedColor ? mPressedColor : mDefaultColor,
|
||||
hasSetSelectedColor ? mSelectedColor : mDefaultColor,
|
||||
hasSetFocusedColor ? mFocusedColor : mDefaultColor,
|
||||
hasSetCheckedColor ? mCheckedColor : mDefaultColor,
|
||||
mDefaultColor
|
||||
};
|
||||
int[][] states = new int[6][];
|
||||
states[0] = new int[]{-android.R.attr.state_enabled};
|
||||
states[1] = new int[]{android.R.attr.state_pressed};
|
||||
states[2] = new int[]{android.R.attr.state_selected};
|
||||
states[3] = new int[]{android.R.attr.state_focused};
|
||||
states[4] = new int[]{android.R.attr.state_checked};
|
||||
states[5] = new int[]{};
|
||||
return new ColorStateList(states, colors);
|
||||
}
|
||||
}
|
||||
}
|
443
app/src/main/java/io/legado/app/lib/theme/Selector.kt
Normal file
443
app/src/main/java/io/legado/app/lib/theme/Selector.kt
Normal file
@ -0,0 +1,443 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.graphics.drawable.StateListDrawable
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.Dimension
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
object Selector {
|
||||
fun shapeBuild(): ShapeSelector {
|
||||
return ShapeSelector()
|
||||
}
|
||||
|
||||
fun colorBuild(): ColorSelector {
|
||||
return ColorSelector()
|
||||
}
|
||||
|
||||
fun drawableBuild(): DrawableSelector {
|
||||
return DrawableSelector()
|
||||
}
|
||||
|
||||
/**
|
||||
* 形状ShapeSelector
|
||||
*
|
||||
* @author hjy
|
||||
* created at 2017/12/11 22:26
|
||||
*/
|
||||
class ShapeSelector {
|
||||
|
||||
private var mShape: Int = 0 //the shape of background
|
||||
private var mDefaultBgColor: Int = 0 //default background color
|
||||
private var mDisabledBgColor: Int = 0 //state_enabled = false
|
||||
private var mPressedBgColor: Int = 0 //state_pressed = true
|
||||
private var mSelectedBgColor: Int = 0 //state_selected = true
|
||||
private var mFocusedBgColor: Int = 0 //state_focused = true
|
||||
private var mCheckedBgColor: Int = 0 //state_checked = true
|
||||
private var mStrokeWidth: Int = 0 //stroke width in pixel
|
||||
private var mDefaultStrokeColor: Int = 0 //default stroke color
|
||||
private var mDisabledStrokeColor: Int = 0 //state_enabled = false
|
||||
private var mPressedStrokeColor: Int = 0 //state_pressed = true
|
||||
private var mSelectedStrokeColor: Int = 0 //state_selected = true
|
||||
private var mFocusedStrokeColor: Int = 0 //state_focused = true
|
||||
private var mCheckedStrokeColor: Int = 0 //state_checked = true
|
||||
private var mCornerRadius: Int = 0 //corner radius
|
||||
|
||||
private var hasSetDisabledBgColor = false
|
||||
private var hasSetPressedBgColor = false
|
||||
private var hasSetSelectedBgColor = false
|
||||
private val hasSetFocusedBgColor = false
|
||||
private var hasSetCheckedBgColor = false
|
||||
|
||||
private var hasSetDisabledStrokeColor = false
|
||||
private var hasSetPressedStrokeColor = false
|
||||
private var hasSetSelectedStrokeColor = false
|
||||
private var hasSetFocusedStrokeColor = false
|
||||
private var hasSetCheckedStrokeColor = false
|
||||
|
||||
@IntDef(GradientDrawable.RECTANGLE, GradientDrawable.OVAL, GradientDrawable.LINE, GradientDrawable.RING)
|
||||
private annotation class Shape
|
||||
|
||||
init {
|
||||
//initialize default values
|
||||
mShape = GradientDrawable.RECTANGLE
|
||||
mDefaultBgColor = Color.TRANSPARENT
|
||||
mDisabledBgColor = Color.TRANSPARENT
|
||||
mPressedBgColor = Color.TRANSPARENT
|
||||
mSelectedBgColor = Color.TRANSPARENT
|
||||
mFocusedBgColor = Color.TRANSPARENT
|
||||
mStrokeWidth = 0
|
||||
mDefaultStrokeColor = Color.TRANSPARENT
|
||||
mDisabledStrokeColor = Color.TRANSPARENT
|
||||
mPressedStrokeColor = Color.TRANSPARENT
|
||||
mSelectedStrokeColor = Color.TRANSPARENT
|
||||
mFocusedStrokeColor = Color.TRANSPARENT
|
||||
mCornerRadius = 0
|
||||
}
|
||||
|
||||
fun setShape(@Shape shape: Int): ShapeSelector {
|
||||
mShape = shape
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDefaultBgColor(@ColorInt color: Int): ShapeSelector {
|
||||
mDefaultBgColor = color
|
||||
if (!hasSetDisabledBgColor)
|
||||
mDisabledBgColor = color
|
||||
if (!hasSetPressedBgColor)
|
||||
mPressedBgColor = color
|
||||
if (!hasSetSelectedBgColor)
|
||||
mSelectedBgColor = color
|
||||
if (!hasSetFocusedBgColor)
|
||||
mFocusedBgColor = color
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDisabledBgColor(@ColorInt color: Int): ShapeSelector {
|
||||
mDisabledBgColor = color
|
||||
hasSetDisabledBgColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPressedBgColor(@ColorInt color: Int): ShapeSelector {
|
||||
mPressedBgColor = color
|
||||
hasSetPressedBgColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSelectedBgColor(@ColorInt color: Int): ShapeSelector {
|
||||
mSelectedBgColor = color
|
||||
hasSetSelectedBgColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFocusedBgColor(@ColorInt color: Int): ShapeSelector {
|
||||
mFocusedBgColor = color
|
||||
hasSetPressedBgColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setCheckedBgColor(@ColorInt color: Int): ShapeSelector {
|
||||
mCheckedBgColor = color
|
||||
hasSetCheckedBgColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setStrokeWidth(@Dimension width: Int): ShapeSelector {
|
||||
mStrokeWidth = width
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDefaultStrokeColor(@ColorInt color: Int): ShapeSelector {
|
||||
mDefaultStrokeColor = color
|
||||
if (!hasSetDisabledStrokeColor)
|
||||
mDisabledStrokeColor = color
|
||||
if (!hasSetPressedStrokeColor)
|
||||
mPressedStrokeColor = color
|
||||
if (!hasSetSelectedStrokeColor)
|
||||
mSelectedStrokeColor = color
|
||||
if (!hasSetFocusedStrokeColor)
|
||||
mFocusedStrokeColor = color
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDisabledStrokeColor(@ColorInt color: Int): ShapeSelector {
|
||||
mDisabledStrokeColor = color
|
||||
hasSetDisabledStrokeColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPressedStrokeColor(@ColorInt color: Int): ShapeSelector {
|
||||
mPressedStrokeColor = color
|
||||
hasSetPressedStrokeColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSelectedStrokeColor(@ColorInt color: Int): ShapeSelector {
|
||||
mSelectedStrokeColor = color
|
||||
hasSetSelectedStrokeColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setCheckedStrokeColor(@ColorInt color: Int): ShapeSelector {
|
||||
mCheckedStrokeColor = color
|
||||
hasSetCheckedStrokeColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFocusedStrokeColor(@ColorInt color: Int): ShapeSelector {
|
||||
mFocusedStrokeColor = color
|
||||
hasSetFocusedStrokeColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setCornerRadius(@Dimension radius: Int): ShapeSelector {
|
||||
mCornerRadius = radius
|
||||
return this
|
||||
}
|
||||
|
||||
fun create(): StateListDrawable {
|
||||
val selector = StateListDrawable()
|
||||
|
||||
//enabled = false
|
||||
if (hasSetDisabledBgColor || hasSetDisabledStrokeColor) {
|
||||
val disabledShape = getItemShape(
|
||||
mShape, mCornerRadius,
|
||||
mDisabledBgColor, mStrokeWidth, mDisabledStrokeColor
|
||||
)
|
||||
selector.addState(intArrayOf(-android.R.attr.state_enabled), disabledShape)
|
||||
}
|
||||
|
||||
//pressed = true
|
||||
if (hasSetPressedBgColor || hasSetPressedStrokeColor) {
|
||||
val pressedShape = getItemShape(
|
||||
mShape, mCornerRadius,
|
||||
mPressedBgColor, mStrokeWidth, mPressedStrokeColor
|
||||
)
|
||||
selector.addState(intArrayOf(android.R.attr.state_pressed), pressedShape)
|
||||
}
|
||||
|
||||
//selected = true
|
||||
if (hasSetSelectedBgColor || hasSetSelectedStrokeColor) {
|
||||
val selectedShape = getItemShape(
|
||||
mShape, mCornerRadius,
|
||||
mSelectedBgColor, mStrokeWidth, mSelectedStrokeColor
|
||||
)
|
||||
selector.addState(intArrayOf(android.R.attr.state_selected), selectedShape)
|
||||
}
|
||||
|
||||
//focused = true
|
||||
if (hasSetFocusedBgColor || hasSetFocusedStrokeColor) {
|
||||
val focusedShape = getItemShape(
|
||||
mShape, mCornerRadius,
|
||||
mFocusedBgColor, mStrokeWidth, mFocusedStrokeColor
|
||||
)
|
||||
selector.addState(intArrayOf(android.R.attr.state_focused), focusedShape)
|
||||
}
|
||||
|
||||
//checked = true
|
||||
if (hasSetCheckedBgColor || hasSetCheckedStrokeColor) {
|
||||
val checkedShape = getItemShape(
|
||||
mShape, mCornerRadius,
|
||||
mCheckedBgColor, mStrokeWidth, mCheckedStrokeColor
|
||||
)
|
||||
selector.addState(intArrayOf(android.R.attr.state_checked), checkedShape)
|
||||
}
|
||||
|
||||
//default
|
||||
val defaultShape = getItemShape(
|
||||
mShape, mCornerRadius,
|
||||
mDefaultBgColor, mStrokeWidth, mDefaultStrokeColor
|
||||
)
|
||||
selector.addState(intArrayOf(), defaultShape)
|
||||
|
||||
return selector
|
||||
}
|
||||
|
||||
private fun getItemShape(
|
||||
shape: Int, cornerRadius: Int,
|
||||
solidColor: Int, strokeWidth: Int, strokeColor: Int
|
||||
): GradientDrawable {
|
||||
val drawable = GradientDrawable()
|
||||
drawable.shape = shape
|
||||
drawable.setStroke(strokeWidth, strokeColor)
|
||||
drawable.cornerRadius = cornerRadius.toFloat()
|
||||
drawable.setColor(solidColor)
|
||||
return drawable
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源DrawableSelector
|
||||
*
|
||||
* @author hjy
|
||||
* created at 2017/12/11 22:34
|
||||
*/
|
||||
class DrawableSelector constructor() {
|
||||
|
||||
private var mDefaultDrawable: Drawable? = null
|
||||
private var mDisabledDrawable: Drawable? = null
|
||||
private var mPressedDrawable: Drawable? = null
|
||||
private var mSelectedDrawable: Drawable? = null
|
||||
private var mFocusedDrawable: Drawable? = null
|
||||
|
||||
private var hasSetDisabledDrawable = false
|
||||
private var hasSetPressedDrawable = false
|
||||
private var hasSetSelectedDrawable = false
|
||||
private var hasSetFocusedDrawable = false
|
||||
|
||||
init {
|
||||
mDefaultDrawable = ColorDrawable(Color.TRANSPARENT)
|
||||
}
|
||||
|
||||
fun setDefaultDrawable(drawable: Drawable?): DrawableSelector {
|
||||
mDefaultDrawable = drawable
|
||||
if (!hasSetDisabledDrawable)
|
||||
mDisabledDrawable = drawable
|
||||
if (!hasSetPressedDrawable)
|
||||
mPressedDrawable = drawable
|
||||
if (!hasSetSelectedDrawable)
|
||||
mSelectedDrawable = drawable
|
||||
if (!hasSetFocusedDrawable)
|
||||
mFocusedDrawable = drawable
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDisabledDrawable(drawable: Drawable?): DrawableSelector {
|
||||
mDisabledDrawable = drawable
|
||||
hasSetDisabledDrawable = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPressedDrawable(drawable: Drawable?): DrawableSelector {
|
||||
mPressedDrawable = drawable
|
||||
hasSetPressedDrawable = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSelectedDrawable(drawable: Drawable?): DrawableSelector {
|
||||
mSelectedDrawable = drawable
|
||||
hasSetSelectedDrawable = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFocusedDrawable(drawable: Drawable?): DrawableSelector {
|
||||
mFocusedDrawable = drawable
|
||||
hasSetFocusedDrawable = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun create(): StateListDrawable {
|
||||
val selector = StateListDrawable()
|
||||
if (hasSetDisabledDrawable)
|
||||
selector.addState(intArrayOf(-android.R.attr.state_enabled), mDisabledDrawable)
|
||||
if (hasSetPressedDrawable)
|
||||
selector.addState(intArrayOf(android.R.attr.state_pressed), mPressedDrawable)
|
||||
if (hasSetSelectedDrawable)
|
||||
selector.addState(intArrayOf(android.R.attr.state_selected), mSelectedDrawable)
|
||||
if (hasSetFocusedDrawable)
|
||||
selector.addState(intArrayOf(android.R.attr.state_focused), mFocusedDrawable)
|
||||
selector.addState(intArrayOf(), mDefaultDrawable)
|
||||
return selector
|
||||
}
|
||||
|
||||
fun setDefaultDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {
|
||||
return setDefaultDrawable(ContextCompat.getDrawable(context, drawableRes))
|
||||
}
|
||||
|
||||
fun setDisabledDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {
|
||||
return setDisabledDrawable(ContextCompat.getDrawable(context, drawableRes))
|
||||
}
|
||||
|
||||
fun setPressedDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {
|
||||
return setPressedDrawable(ContextCompat.getDrawable(context, drawableRes))
|
||||
}
|
||||
|
||||
fun setSelectedDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {
|
||||
return setSelectedDrawable(ContextCompat.getDrawable(context, drawableRes))
|
||||
}
|
||||
|
||||
fun setFocusedDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {
|
||||
return setFocusedDrawable(ContextCompat.getDrawable(context, drawableRes))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色ColorSelector
|
||||
*
|
||||
* @author hjy
|
||||
* created at 2017/12/11 22:26
|
||||
*/
|
||||
class ColorSelector constructor() {
|
||||
|
||||
private var mDefaultColor: Int = 0
|
||||
private var mDisabledColor: Int = 0
|
||||
private var mPressedColor: Int = 0
|
||||
private var mSelectedColor: Int = 0
|
||||
private var mFocusedColor: Int = 0
|
||||
private var mCheckedColor: Int = 0
|
||||
|
||||
private var hasSetDisabledColor = false
|
||||
private var hasSetPressedColor = false
|
||||
private var hasSetSelectedColor = false
|
||||
private var hasSetFocusedColor = false
|
||||
private var hasSetCheckedColor = false
|
||||
|
||||
init {
|
||||
mDefaultColor = Color.BLACK
|
||||
mDisabledColor = Color.GRAY
|
||||
mPressedColor = Color.BLACK
|
||||
mSelectedColor = Color.BLACK
|
||||
mFocusedColor = Color.BLACK
|
||||
}
|
||||
|
||||
fun setDefaultColor(@ColorInt color: Int): ColorSelector {
|
||||
mDefaultColor = color
|
||||
if (!hasSetDisabledColor)
|
||||
mDisabledColor = color
|
||||
if (!hasSetPressedColor)
|
||||
mPressedColor = color
|
||||
if (!hasSetSelectedColor)
|
||||
mSelectedColor = color
|
||||
if (!hasSetFocusedColor)
|
||||
mFocusedColor = color
|
||||
return this
|
||||
}
|
||||
|
||||
fun setDisabledColor(@ColorInt color: Int): ColorSelector {
|
||||
mDisabledColor = color
|
||||
hasSetDisabledColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPressedColor(@ColorInt color: Int): ColorSelector {
|
||||
mPressedColor = color
|
||||
hasSetPressedColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSelectedColor(@ColorInt color: Int): ColorSelector {
|
||||
mSelectedColor = color
|
||||
hasSetSelectedColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFocusedColor(@ColorInt color: Int): ColorSelector {
|
||||
mFocusedColor = color
|
||||
hasSetFocusedColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun setCheckedColor(@ColorInt color: Int): ColorSelector {
|
||||
mCheckedColor = color
|
||||
hasSetCheckedColor = true
|
||||
return this
|
||||
}
|
||||
|
||||
fun create(): ColorStateList {
|
||||
val colors = intArrayOf(
|
||||
if (hasSetDisabledColor) mDisabledColor else mDefaultColor,
|
||||
if (hasSetPressedColor) mPressedColor else mDefaultColor,
|
||||
if (hasSetSelectedColor) mSelectedColor else mDefaultColor,
|
||||
if (hasSetFocusedColor) mFocusedColor else mDefaultColor,
|
||||
if (hasSetCheckedColor) mCheckedColor else mDefaultColor,
|
||||
mDefaultColor
|
||||
)
|
||||
val states = arrayOfNulls<IntArray>(6)
|
||||
states[0] = intArrayOf(-android.R.attr.state_enabled)
|
||||
states[1] = intArrayOf(android.R.attr.state_pressed)
|
||||
states[2] = intArrayOf(android.R.attr.state_selected)
|
||||
states[3] = intArrayOf(android.R.attr.state_focused)
|
||||
states[4] = intArrayOf(android.R.attr.state_checked)
|
||||
states[5] = intArrayOf()
|
||||
return ColorStateList(states, colors)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,318 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import androidx.annotation.*;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import io.legado.app.R;
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public final class ThemeStore implements ThemeStorePrefKeys, ThemeStoreInterface {
|
||||
|
||||
private final Context mContext;
|
||||
private final SharedPreferences.Editor mEditor;
|
||||
|
||||
public static ThemeStore editTheme(@NonNull Context context) {
|
||||
return new ThemeStore(context);
|
||||
}
|
||||
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
private ThemeStore(@NonNull Context context) {
|
||||
mContext = context;
|
||||
mEditor = prefs(context).edit();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ThemeStore primaryColor(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_PRIMARY_COLOR, color);
|
||||
if (autoGeneratePrimaryDark(mContext))
|
||||
primaryColorDark(ColorUtil.darkenColor(color));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore primaryColorRes(@ColorRes int colorRes) {
|
||||
return primaryColor(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore primaryColorAttr(@AttrRes int colorAttr) {
|
||||
return primaryColor(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore primaryColorDark(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_PRIMARY_COLOR_DARK, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore primaryColorDarkRes(@ColorRes int colorRes) {
|
||||
return primaryColorDark(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore primaryColorDarkAttr(@AttrRes int colorAttr) {
|
||||
return primaryColorDark(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore accentColor(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_ACCENT_COLOR, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore accentColorRes(@ColorRes int colorRes) {
|
||||
return accentColor(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore accentColorAttr(@AttrRes int colorAttr) {
|
||||
return accentColor(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore statusBarColor(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_STATUS_BAR_COLOR, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore statusBarColorRes(@ColorRes int colorRes) {
|
||||
return statusBarColor(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore statusBarColorAttr(@AttrRes int colorAttr) {
|
||||
return statusBarColor(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore navigationBarColor(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_NAVIGATION_BAR_COLOR, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore navigationBarColorRes(@ColorRes int colorRes) {
|
||||
return navigationBarColor(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore navigationBarColorAttr(@AttrRes int colorAttr) {
|
||||
return navigationBarColor(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorPrimary(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_TEXT_COLOR_PRIMARY, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorPrimaryRes(@ColorRes int colorRes) {
|
||||
return textColorPrimary(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorPrimaryAttr(@AttrRes int colorAttr) {
|
||||
return textColorPrimary(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorPrimaryInverse(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_TEXT_COLOR_PRIMARY_INVERSE, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorPrimaryInverseRes(@ColorRes int colorRes) {
|
||||
return textColorPrimaryInverse(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorPrimaryInverseAttr(@AttrRes int colorAttr) {
|
||||
return textColorPrimaryInverse(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorSecondary(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_TEXT_COLOR_SECONDARY, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorSecondaryRes(@ColorRes int colorRes) {
|
||||
return textColorSecondary(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorSecondaryAttr(@AttrRes int colorAttr) {
|
||||
return textColorSecondary(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorSecondaryInverse(@ColorInt int color) {
|
||||
mEditor.putInt(KEY_TEXT_COLOR_SECONDARY_INVERSE, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorSecondaryInverseRes(@ColorRes int colorRes) {
|
||||
return textColorSecondaryInverse(ContextCompat.getColor(mContext, colorRes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore textColorSecondaryInverseAttr(@AttrRes int colorAttr) {
|
||||
return textColorSecondaryInverse(ATHUtil.resolveColor(mContext, colorAttr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore backgroundColor(int color) {
|
||||
mEditor.putInt(KEY_BACKGROUND_COLOR, color);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore coloredStatusBar(boolean colored) {
|
||||
mEditor.putBoolean(KEY_APPLY_PRIMARYDARK_STATUSBAR, colored);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore coloredNavigationBar(boolean applyToNavBar) {
|
||||
mEditor.putBoolean(KEY_APPLY_PRIMARY_NAVBAR, applyToNavBar);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeStore autoGeneratePrimaryDark(boolean autoGenerate) {
|
||||
mEditor.putBoolean(KEY_AUTO_GENERATE_PRIMARYDARK, autoGenerate);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Commit method
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void apply() {
|
||||
mEditor.putLong(VALUES_CHANGED, System.currentTimeMillis())
|
||||
.putBoolean(IS_CONFIGURED_KEY, true)
|
||||
.apply();
|
||||
}
|
||||
|
||||
// Static getters
|
||||
|
||||
@CheckResult
|
||||
@NonNull
|
||||
protected static SharedPreferences prefs(@NonNull Context context) {
|
||||
return context.getSharedPreferences(CONFIG_PREFS_KEY_DEFAULT, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static void markChanged(@NonNull Context context) {
|
||||
new ThemeStore(context).apply();
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int primaryColor(@NonNull Context context) {
|
||||
return prefs(context).getInt(KEY_PRIMARY_COLOR, ATHUtil.resolveColor(context, R.attr.colorPrimary, Color.parseColor("#455A64")));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int primaryColorDark(@NonNull Context context) {
|
||||
return prefs(context).getInt(KEY_PRIMARY_COLOR_DARK, ATHUtil.resolveColor(context, R.attr.colorPrimaryDark, Color.parseColor("#37474F")));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int accentColor(@NonNull Context context) {
|
||||
return prefs(context).getInt(KEY_ACCENT_COLOR, ATHUtil.resolveColor(context, R.attr.colorAccent, Color.parseColor("#263238")));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int statusBarColor(@NonNull Context context) {
|
||||
if (!coloredStatusBar(context)) {
|
||||
return Color.BLACK;
|
||||
}
|
||||
return prefs(context).getInt(KEY_STATUS_BAR_COLOR, primaryColorDark(context));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int navigationBarColor(@NonNull Context context) {
|
||||
if (!coloredNavigationBar(context)) {
|
||||
return Color.BLACK;
|
||||
}
|
||||
return prefs(context).getInt(KEY_NAVIGATION_BAR_COLOR, primaryColor(context));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int textColorPrimary(@NonNull Context context) {
|
||||
return prefs(context).getInt(KEY_TEXT_COLOR_PRIMARY, ATHUtil.resolveColor(context, android.R.attr.textColorPrimary));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int textColorPrimaryInverse(@NonNull Context context) {
|
||||
return prefs(context).getInt(KEY_TEXT_COLOR_PRIMARY_INVERSE, ATHUtil.resolveColor(context, android.R.attr.textColorPrimaryInverse));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int textColorSecondary(@NonNull Context context) {
|
||||
return prefs(context).getInt(KEY_TEXT_COLOR_SECONDARY, ATHUtil.resolveColor(context, android.R.attr.textColorSecondary));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int textColorSecondaryInverse(@NonNull Context context) {
|
||||
return prefs(context).getInt(KEY_TEXT_COLOR_SECONDARY_INVERSE, ATHUtil.resolveColor(context, android.R.attr.textColorSecondaryInverse));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
public static int backgroundColor(@NonNull Context context) {
|
||||
return prefs(context).getInt(KEY_BACKGROUND_COLOR, ATHUtil.resolveColor(context, android.R.attr.colorBackground));
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
public static boolean coloredStatusBar(@NonNull Context context) {
|
||||
return prefs(context).getBoolean(KEY_APPLY_PRIMARYDARK_STATUSBAR, true);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
public static boolean coloredNavigationBar(@NonNull Context context) {
|
||||
return prefs(context).getBoolean(KEY_APPLY_PRIMARY_NAVBAR, false);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
public static boolean autoGeneratePrimaryDark(@NonNull Context context) {
|
||||
return prefs(context).getBoolean(KEY_AUTO_GENERATE_PRIMARYDARK, true);
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
public static boolean isConfigured(Context context) {
|
||||
return prefs(context).getBoolean(IS_CONFIGURED_KEY, false);
|
||||
}
|
||||
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
public static boolean isConfigured(Context context, @IntRange(from = 0, to = Integer.MAX_VALUE) int version) {
|
||||
final SharedPreferences prefs = prefs(context);
|
||||
final int lastVersion = prefs.getInt(IS_CONFIGURED_VERSION_KEY, -1);
|
||||
if (version > lastVersion) {
|
||||
prefs.edit().putInt(IS_CONFIGURED_VERSION_KEY, version).apply();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
309
app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt
Normal file
309
app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt
Normal file
@ -0,0 +1,309 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.legado.app.R
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
class ThemeStore @SuppressLint("CommitPrefEdits")
|
||||
private constructor(private val mContext: Context) : ThemeStorePrefKeys, ThemeStoreInterface {
|
||||
private val mEditor: SharedPreferences.Editor
|
||||
|
||||
init {
|
||||
mEditor = prefs(mContext).edit()
|
||||
}
|
||||
|
||||
|
||||
override fun primaryColor(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_PRIMARY_COLOR, color)
|
||||
if (autoGeneratePrimaryDark(mContext))
|
||||
primaryColorDark(ColorUtil.darkenColor(color))
|
||||
return this
|
||||
}
|
||||
|
||||
override fun primaryColorRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return primaryColor(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun primaryColorAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return primaryColor(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun primaryColorDark(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_PRIMARY_COLOR_DARK, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun primaryColorDarkRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return primaryColorDark(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun primaryColorDarkAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return primaryColorDark(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun accentColor(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_ACCENT_COLOR, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun accentColorRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return accentColor(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun accentColorAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return accentColor(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun statusBarColor(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun statusBarColorRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return statusBarColor(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun statusBarColorAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return statusBarColor(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun navigationBarColor(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_NAVIGATION_BAR_COLOR, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun navigationBarColorRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return navigationBarColor(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun navigationBarColorAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return navigationBarColor(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun textColorPrimary(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun textColorPrimaryRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return textColorPrimary(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun textColorPrimaryAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return textColorPrimary(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun textColorPrimaryInverse(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY_INVERSE, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun textColorPrimaryInverseRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return textColorPrimaryInverse(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun textColorPrimaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return textColorPrimaryInverse(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun textColorSecondary(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun textColorSecondaryRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return textColorSecondary(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun textColorSecondaryAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return textColorSecondary(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun textColorSecondaryInverse(@ColorInt color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY_INVERSE, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun textColorSecondaryInverseRes(@ColorRes colorRes: Int): ThemeStore {
|
||||
return textColorSecondaryInverse(ContextCompat.getColor(mContext, colorRes))
|
||||
}
|
||||
|
||||
override fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore {
|
||||
return textColorSecondaryInverse(ATHUtil.resolveColor(mContext, colorAttr))
|
||||
}
|
||||
|
||||
override fun backgroundColor(color: Int): ThemeStore {
|
||||
mEditor.putInt(ThemeStorePrefKeys.KEY_BACKGROUND_COLOR, color)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun coloredStatusBar(colored: Boolean): ThemeStore {
|
||||
mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, colored)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun coloredNavigationBar(applyToNavBar: Boolean): ThemeStore {
|
||||
mEditor.putBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARY_NAVBAR, applyToNavBar)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun autoGeneratePrimaryDark(autoGenerate: Boolean): ThemeStore {
|
||||
mEditor.putBoolean(ThemeStorePrefKeys.KEY_AUTO_GENERATE_PRIMARYDARK, autoGenerate)
|
||||
return this
|
||||
}
|
||||
|
||||
// Commit method
|
||||
|
||||
override fun apply() {
|
||||
mEditor.putLong(ThemeStorePrefKeys.VALUES_CHANGED, System.currentTimeMillis())
|
||||
.putBoolean(ThemeStorePrefKeys.IS_CONFIGURED_KEY, true)
|
||||
.apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun editTheme(context: Context): ThemeStore {
|
||||
return ThemeStore(context)
|
||||
}
|
||||
|
||||
// Static getters
|
||||
|
||||
@CheckResult
|
||||
protected fun prefs(context: Context): SharedPreferences {
|
||||
return context.getSharedPreferences(ThemeStorePrefKeys.CONFIG_PREFS_KEY_DEFAULT, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
fun markChanged(context: Context) {
|
||||
ThemeStore(context).apply()
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun primaryColor(context: Context): Int {
|
||||
return prefs(context).getInt(
|
||||
ThemeStorePrefKeys.KEY_PRIMARY_COLOR,
|
||||
ATHUtil.resolveColor(context, R.attr.colorPrimary, Color.parseColor("#455A64"))
|
||||
)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun primaryColorDark(context: Context): Int {
|
||||
return prefs(context).getInt(
|
||||
ThemeStorePrefKeys.KEY_PRIMARY_COLOR_DARK,
|
||||
ATHUtil.resolveColor(context, R.attr.colorPrimaryDark, Color.parseColor("#37474F"))
|
||||
)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun accentColor(context: Context): Int {
|
||||
return prefs(context).getInt(
|
||||
ThemeStorePrefKeys.KEY_ACCENT_COLOR,
|
||||
ATHUtil.resolveColor(context, R.attr.colorAccent, Color.parseColor("#263238"))
|
||||
)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun statusBarColor(context: Context): Int {
|
||||
return if (!coloredStatusBar(context)) {
|
||||
Color.BLACK
|
||||
} else prefs(context).getInt(ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR, primaryColorDark(context))
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun navigationBarColor(context: Context): Int {
|
||||
return if (!coloredNavigationBar(context)) {
|
||||
Color.BLACK
|
||||
} else prefs(context).getInt(ThemeStorePrefKeys.KEY_NAVIGATION_BAR_COLOR, primaryColor(context))
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun textColorPrimary(context: Context): Int {
|
||||
return prefs(context).getInt(
|
||||
ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY,
|
||||
ATHUtil.resolveColor(context, android.R.attr.textColorPrimary)
|
||||
)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun textColorPrimaryInverse(context: Context): Int {
|
||||
return prefs(context).getInt(
|
||||
ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY_INVERSE,
|
||||
ATHUtil.resolveColor(context, android.R.attr.textColorPrimaryInverse)
|
||||
)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun textColorSecondary(context: Context): Int {
|
||||
return prefs(context).getInt(
|
||||
ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY,
|
||||
ATHUtil.resolveColor(context, android.R.attr.textColorSecondary)
|
||||
)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun textColorSecondaryInverse(context: Context): Int {
|
||||
return prefs(context).getInt(
|
||||
ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY_INVERSE,
|
||||
ATHUtil.resolveColor(context, android.R.attr.textColorSecondaryInverse)
|
||||
)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
@ColorInt
|
||||
fun backgroundColor(context: Context): Int {
|
||||
return prefs(context).getInt(
|
||||
ThemeStorePrefKeys.KEY_BACKGROUND_COLOR,
|
||||
ATHUtil.resolveColor(context, android.R.attr.colorBackground)
|
||||
)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun coloredStatusBar(context: Context): Boolean {
|
||||
return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR, true)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun coloredNavigationBar(context: Context): Boolean {
|
||||
return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARY_NAVBAR, false)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun autoGeneratePrimaryDark(context: Context): Boolean {
|
||||
return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_AUTO_GENERATE_PRIMARYDARK, true)
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
fun isConfigured(context: Context): Boolean {
|
||||
return prefs(context).getBoolean(ThemeStorePrefKeys.IS_CONFIGURED_KEY, false)
|
||||
}
|
||||
|
||||
@SuppressLint("CommitPrefEdits")
|
||||
fun isConfigured(context: Context, version: Int): Boolean {
|
||||
val prefs = prefs(context)
|
||||
val lastVersion = prefs.getInt(ThemeStorePrefKeys.IS_CONFIGURED_VERSION_KEY, -1)
|
||||
if (version > lastVersion) {
|
||||
prefs.edit().putInt(ThemeStorePrefKeys.IS_CONFIGURED_VERSION_KEY, version).apply()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.ColorRes;
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
interface ThemeStoreInterface {
|
||||
|
||||
// Primary colors
|
||||
|
||||
ThemeStore primaryColor(@ColorInt int color);
|
||||
|
||||
ThemeStore primaryColorRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore primaryColorAttr(@AttrRes int colorAttr);
|
||||
|
||||
ThemeStore autoGeneratePrimaryDark(boolean autoGenerate);
|
||||
|
||||
ThemeStore primaryColorDark(@ColorInt int color);
|
||||
|
||||
ThemeStore primaryColorDarkRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore primaryColorDarkAttr(@AttrRes int colorAttr);
|
||||
|
||||
// Accent colors
|
||||
|
||||
ThemeStore accentColor(@ColorInt int color);
|
||||
|
||||
ThemeStore accentColorRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore accentColorAttr(@AttrRes int colorAttr);
|
||||
|
||||
// Status bar color
|
||||
|
||||
ThemeStore statusBarColor(@ColorInt int color);
|
||||
|
||||
ThemeStore statusBarColorRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore statusBarColorAttr(@AttrRes int colorAttr);
|
||||
|
||||
// Navigation bar color
|
||||
|
||||
ThemeStore navigationBarColor(@ColorInt int color);
|
||||
|
||||
ThemeStore navigationBarColorRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore navigationBarColorAttr(@AttrRes int colorAttr);
|
||||
|
||||
// Primary text color
|
||||
|
||||
ThemeStore textColorPrimary(@ColorInt int color);
|
||||
|
||||
ThemeStore textColorPrimaryRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore textColorPrimaryAttr(@AttrRes int colorAttr);
|
||||
|
||||
ThemeStore textColorPrimaryInverse(@ColorInt int color);
|
||||
|
||||
ThemeStore textColorPrimaryInverseRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore textColorPrimaryInverseAttr(@AttrRes int colorAttr);
|
||||
|
||||
// Secondary text color
|
||||
|
||||
ThemeStore textColorSecondary(@ColorInt int color);
|
||||
|
||||
ThemeStore textColorSecondaryRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore textColorSecondaryAttr(@AttrRes int colorAttr);
|
||||
|
||||
ThemeStore textColorSecondaryInverse(@ColorInt int color);
|
||||
|
||||
ThemeStore textColorSecondaryInverseRes(@ColorRes int colorRes);
|
||||
|
||||
ThemeStore textColorSecondaryInverseAttr(@AttrRes int colorAttr);
|
||||
|
||||
ThemeStore backgroundColor(@ColorInt int color);
|
||||
|
||||
// Toggle configurations
|
||||
|
||||
ThemeStore coloredStatusBar(boolean colored);
|
||||
|
||||
ThemeStore coloredNavigationBar(boolean applyToNavBar);
|
||||
|
||||
// Commit/apply
|
||||
|
||||
void apply();
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
internal interface ThemeStoreInterface {
|
||||
|
||||
// Primary colors
|
||||
|
||||
fun primaryColor(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun primaryColorRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun primaryColorAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
fun autoGeneratePrimaryDark(autoGenerate: Boolean): ThemeStore
|
||||
|
||||
fun primaryColorDark(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun primaryColorDarkRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun primaryColorDarkAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
// Accent colors
|
||||
|
||||
fun accentColor(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun accentColorRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun accentColorAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
// Status bar color
|
||||
|
||||
fun statusBarColor(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun statusBarColorRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun statusBarColorAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
// Navigation bar color
|
||||
|
||||
fun navigationBarColor(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun navigationBarColorRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun navigationBarColorAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
// Primary text color
|
||||
|
||||
fun textColorPrimary(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun textColorPrimaryRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun textColorPrimaryAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
fun textColorPrimaryInverse(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun textColorPrimaryInverseRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun textColorPrimaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
// Secondary text color
|
||||
|
||||
fun textColorSecondary(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun textColorSecondaryRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun textColorSecondaryAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
fun textColorSecondaryInverse(@ColorInt color: Int): ThemeStore
|
||||
|
||||
fun textColorSecondaryInverseRes(@ColorRes colorRes: Int): ThemeStore
|
||||
|
||||
fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore
|
||||
|
||||
fun backgroundColor(@ColorInt color: Int): ThemeStore
|
||||
|
||||
// Toggle configurations
|
||||
|
||||
fun coloredStatusBar(colored: Boolean): ThemeStore
|
||||
|
||||
fun coloredNavigationBar(applyToNavBar: Boolean): ThemeStore
|
||||
|
||||
// Commit/apply
|
||||
|
||||
fun apply()
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
interface ThemeStorePrefKeys {
|
||||
|
||||
String CONFIG_PREFS_KEY_DEFAULT = "app_themes";
|
||||
String IS_CONFIGURED_KEY = "is_configured";
|
||||
String IS_CONFIGURED_VERSION_KEY = "is_configured_version";
|
||||
String VALUES_CHANGED = "values_changed";
|
||||
|
||||
String KEY_PRIMARY_COLOR = "primary_color";
|
||||
String KEY_PRIMARY_COLOR_DARK = "primary_color_dark";
|
||||
String KEY_ACCENT_COLOR = "accent_color";
|
||||
String KEY_STATUS_BAR_COLOR = "status_bar_color";
|
||||
String KEY_NAVIGATION_BAR_COLOR = "navigation_bar_color";
|
||||
|
||||
String KEY_TEXT_COLOR_PRIMARY = "text_color_primary";
|
||||
String KEY_TEXT_COLOR_PRIMARY_INVERSE = "text_color_primary_inverse";
|
||||
String KEY_TEXT_COLOR_SECONDARY = "text_color_secondary";
|
||||
String KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse";
|
||||
|
||||
String KEY_BACKGROUND_COLOR = "backgroundColor";
|
||||
|
||||
String KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar";
|
||||
String KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar";
|
||||
String KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark";
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
internal interface ThemeStorePrefKeys {
|
||||
companion object {
|
||||
|
||||
val CONFIG_PREFS_KEY_DEFAULT = "app_themes"
|
||||
val IS_CONFIGURED_KEY = "is_configured"
|
||||
val IS_CONFIGURED_VERSION_KEY = "is_configured_version"
|
||||
val VALUES_CHANGED = "values_changed"
|
||||
|
||||
val KEY_PRIMARY_COLOR = "primary_color"
|
||||
val KEY_PRIMARY_COLOR_DARK = "primary_color_dark"
|
||||
val KEY_ACCENT_COLOR = "accent_color"
|
||||
val KEY_STATUS_BAR_COLOR = "status_bar_color"
|
||||
val KEY_NAVIGATION_BAR_COLOR = "navigation_bar_color"
|
||||
|
||||
val KEY_TEXT_COLOR_PRIMARY = "text_color_primary"
|
||||
val KEY_TEXT_COLOR_PRIMARY_INVERSE = "text_color_primary_inverse"
|
||||
val KEY_TEXT_COLOR_SECONDARY = "text_color_secondary"
|
||||
val KEY_TEXT_COLOR_SECONDARY_INVERSE = "text_color_secondary_inverse"
|
||||
|
||||
val KEY_BACKGROUND_COLOR = "backgroundColor"
|
||||
|
||||
val KEY_APPLY_PRIMARYDARK_STATUSBAR = "apply_primarydark_statusbar"
|
||||
val KEY_APPLY_PRIMARY_NAVBAR = "apply_primary_navbar"
|
||||
val KEY_AUTO_GENERATE_PRIMARYDARK = "auto_generate_primarydark"
|
||||
}
|
||||
}
|
@ -1,384 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.CheckResult;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import io.legado.app.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @author afollestad, plusCubed
|
||||
*/
|
||||
public final class TintHelper {
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
private static int getDefaultRippleColor(@NonNull Context context, boolean useDarkRipple) {
|
||||
// Light ripple is actually translucent black, and vice versa
|
||||
return ContextCompat.getColor(context, useDarkRipple ?
|
||||
R.color.ripple_material_light : R.color.ripple_material_dark);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static ColorStateList getDisabledColorStateList(@ColorInt int normal, @ColorInt int disabled) {
|
||||
return new ColorStateList(new int[][]{
|
||||
new int[]{-android.R.attr.state_enabled},
|
||||
new int[]{android.R.attr.state_enabled}
|
||||
}, new int[]{
|
||||
disabled,
|
||||
normal
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setTintSelector(@NonNull View view, @ColorInt final int color, final boolean darker, final boolean useDarkTheme) {
|
||||
final boolean isColorLight = ColorUtil.isColorLight(color);
|
||||
final int disabled = ContextCompat.getColor(view.getContext(), useDarkTheme ? R.color.ate_button_disabled_dark : R.color.ate_button_disabled_light);
|
||||
final int pressed = ColorUtil.shiftColor(color, darker ? 0.9f : 1.1f);
|
||||
final int activated = ColorUtil.shiftColor(color, darker ? 1.1f : 0.9f);
|
||||
final int rippleColor = getDefaultRippleColor(view.getContext(), isColorLight);
|
||||
final int textColor = ContextCompat.getColor(view.getContext(), isColorLight ? R.color.ate_primary_text_light : R.color.ate_primary_text_dark);
|
||||
|
||||
final ColorStateList sl;
|
||||
if (view instanceof Button) {
|
||||
sl = getDisabledColorStateList(color, disabled);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||
view.getBackground() instanceof RippleDrawable) {
|
||||
RippleDrawable rd = (RippleDrawable) view.getBackground();
|
||||
rd.setColor(ColorStateList.valueOf(rippleColor));
|
||||
}
|
||||
|
||||
// Disabled text color state for buttons, may get overridden later by ATE tags
|
||||
final Button button = (Button) view;
|
||||
button.setTextColor(getDisabledColorStateList(textColor, ContextCompat.getColor(view.getContext(), useDarkTheme ? R.color.ate_button_text_disabled_dark : R.color.ate_button_text_disabled_light)));
|
||||
} else if (view instanceof FloatingActionButton) {
|
||||
// FloatingActionButton doesn't support disabled state?
|
||||
sl = new ColorStateList(new int[][]{
|
||||
new int[]{-android.R.attr.state_pressed},
|
||||
new int[]{android.R.attr.state_pressed}
|
||||
}, new int[]{
|
||||
color,
|
||||
pressed
|
||||
});
|
||||
|
||||
final FloatingActionButton fab = (FloatingActionButton) view;
|
||||
fab.setRippleColor(rippleColor);
|
||||
fab.setBackgroundTintList(sl);
|
||||
if (fab.getDrawable() != null)
|
||||
fab.setImageDrawable(createTintedDrawable(fab.getDrawable(), textColor));
|
||||
return;
|
||||
} else {
|
||||
sl = new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{-android.R.attr.state_enabled},
|
||||
new int[]{android.R.attr.state_enabled},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_activated},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_checked}
|
||||
},
|
||||
new int[]{
|
||||
disabled,
|
||||
color,
|
||||
pressed,
|
||||
activated,
|
||||
activated
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Drawable drawable = view.getBackground();
|
||||
if (drawable != null) {
|
||||
drawable = createTintedDrawable(drawable, sl);
|
||||
ViewUtil.setBackgroundCompat(view, drawable);
|
||||
}
|
||||
|
||||
if (view instanceof TextView && !(view instanceof Button)) {
|
||||
final TextView tv = (TextView) view;
|
||||
tv.setTextColor(getDisabledColorStateList(textColor, ContextCompat.getColor(view.getContext(), isColorLight ? R.color.ate_text_disabled_light : R.color.ate_text_disabled_dark)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTintAuto(final @NonNull View view, final @ColorInt int color,
|
||||
boolean background) {
|
||||
setTintAuto(view, color, background, ATHUtil.isWindowBackgroundDark(view.getContext()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setTintAuto(final @NonNull View view, final @ColorInt int color,
|
||||
boolean background, final boolean isDark) {
|
||||
if (!background) {
|
||||
if (view instanceof RadioButton)
|
||||
setTint((RadioButton) view, color, isDark);
|
||||
else if (view instanceof SeekBar)
|
||||
setTint((SeekBar) view, color, isDark);
|
||||
else if (view instanceof ProgressBar)
|
||||
setTint((ProgressBar) view, color);
|
||||
else if (view instanceof AppCompatEditText)
|
||||
setTint((AppCompatEditText) view, color, isDark);
|
||||
else if (view instanceof CheckBox)
|
||||
setTint((CheckBox) view, color, isDark);
|
||||
else if (view instanceof ImageView)
|
||||
setTint((ImageView) view, color);
|
||||
else if (view instanceof Switch)
|
||||
setTint((Switch) view, color, isDark);
|
||||
else if (view instanceof SwitchCompat)
|
||||
setTint((SwitchCompat) view, color, isDark);
|
||||
else if (view instanceof SearchView) {
|
||||
int iconIdS[] = new int[]{androidx.appcompat.R.id.search_button, androidx.appcompat.R.id.search_close_btn,};
|
||||
for (int iconId : iconIdS) {
|
||||
ImageView icon = view.findViewById(iconId);
|
||||
if (icon != null) {
|
||||
setTint(icon, color);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
background = true;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||
!background && view.getBackground() instanceof RippleDrawable) {
|
||||
// Ripples for the above views (e.g. when you tap and hold a switch or checkbox)
|
||||
RippleDrawable rd = (RippleDrawable) view.getBackground();
|
||||
@SuppressLint("PrivateResource") final int unchecked = ContextCompat.getColor(view.getContext(),
|
||||
isDark ? R.color.ripple_material_dark : R.color.ripple_material_light);
|
||||
final int checked = ColorUtil.adjustAlpha(color, 0.4f);
|
||||
final ColorStateList sl = new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{-android.R.attr.state_activated, -android.R.attr.state_checked},
|
||||
new int[]{android.R.attr.state_activated},
|
||||
new int[]{android.R.attr.state_checked}
|
||||
},
|
||||
new int[]{
|
||||
unchecked,
|
||||
checked,
|
||||
checked
|
||||
}
|
||||
);
|
||||
rd.setColor(sl);
|
||||
}
|
||||
}
|
||||
if (background) {
|
||||
// Need to tint the background of a view
|
||||
if (view instanceof FloatingActionButton || view instanceof Button) {
|
||||
setTintSelector(view, color, false, isDark);
|
||||
} else if (view.getBackground() != null) {
|
||||
Drawable drawable = view.getBackground();
|
||||
if (drawable != null) {
|
||||
drawable = createTintedDrawable(drawable, color);
|
||||
ViewUtil.setBackgroundCompat(view, drawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull RadioButton radioButton, @ColorInt int color, boolean useDarker) {
|
||||
ColorStateList sl = new ColorStateList(new int[][]{
|
||||
new int[]{-android.R.attr.state_enabled},
|
||||
new int[]{android.R.attr.state_enabled, -android.R.attr.state_checked},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_checked}
|
||||
}, new int[]{
|
||||
// Rdio button includes own alpha for disabled state
|
||||
ColorUtil.stripAlpha(ContextCompat.getColor(radioButton.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light)),
|
||||
ContextCompat.getColor(radioButton.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light),
|
||||
color
|
||||
});
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
radioButton.setButtonTintList(sl);
|
||||
} else {
|
||||
Drawable d = createTintedDrawable(ContextCompat.getDrawable(radioButton.getContext(), R.drawable.abc_btn_radio_material), sl);
|
||||
radioButton.setButtonDrawable(d);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull SeekBar seekBar, @ColorInt int color, boolean useDarker) {
|
||||
final ColorStateList s1 = getDisabledColorStateList(color,
|
||||
ContextCompat.getColor(seekBar.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
seekBar.setThumbTintList(s1);
|
||||
seekBar.setProgressTintList(s1);
|
||||
} else {
|
||||
Drawable progressDrawable = createTintedDrawable(seekBar.getProgressDrawable(), s1);
|
||||
seekBar.setProgressDrawable(progressDrawable);
|
||||
Drawable thumbDrawable = createTintedDrawable(seekBar.getThumb(), s1);
|
||||
seekBar.setThumb(thumbDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull ProgressBar progressBar, @ColorInt int color) {
|
||||
setTint(progressBar, color, false);
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull ProgressBar progressBar, @ColorInt int color, boolean skipIndeterminate) {
|
||||
ColorStateList sl = ColorStateList.valueOf(color);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
progressBar.setProgressTintList(sl);
|
||||
progressBar.setSecondaryProgressTintList(sl);
|
||||
if (!skipIndeterminate)
|
||||
progressBar.setIndeterminateTintList(sl);
|
||||
} else {
|
||||
PorterDuff.Mode mode = PorterDuff.Mode.SRC_IN;
|
||||
if (!skipIndeterminate && progressBar.getIndeterminateDrawable() != null)
|
||||
progressBar.getIndeterminateDrawable().setColorFilter(color, mode);
|
||||
if (progressBar.getProgressDrawable() != null)
|
||||
progressBar.getProgressDrawable().setColorFilter(color, mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
public static void setTint(@NonNull AppCompatEditText editText, @ColorInt int color, boolean useDarker) {
|
||||
final ColorStateList editTextColorStateList = new ColorStateList(new int[][]{
|
||||
new int[]{-android.R.attr.state_enabled},
|
||||
new int[]{android.R.attr.state_enabled, -android.R.attr.state_pressed, -android.R.attr.state_focused},
|
||||
new int[]{}
|
||||
}, new int[]{
|
||||
ContextCompat.getColor(editText.getContext(), useDarker ? R.color.ate_text_disabled_dark : R.color.ate_text_disabled_light),
|
||||
ContextCompat.getColor(editText.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light),
|
||||
color
|
||||
});
|
||||
editText.setSupportBackgroundTintList(editTextColorStateList);
|
||||
setCursorTint(editText, color);
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull CheckBox box, @ColorInt int color, boolean useDarker) {
|
||||
ColorStateList sl = new ColorStateList(new int[][]{
|
||||
new int[]{-android.R.attr.state_enabled},
|
||||
new int[]{android.R.attr.state_enabled, -android.R.attr.state_checked},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_checked}
|
||||
}, new int[]{
|
||||
ContextCompat.getColor(box.getContext(), useDarker ? R.color.ate_control_disabled_dark : R.color.ate_control_disabled_light),
|
||||
ContextCompat.getColor(box.getContext(), useDarker ? R.color.ate_control_normal_dark : R.color.ate_control_normal_light),
|
||||
color
|
||||
});
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
box.setButtonTintList(sl);
|
||||
} else {
|
||||
Drawable drawable = createTintedDrawable(ContextCompat.getDrawable(box.getContext(), R.drawable.abc_btn_check_material), sl);
|
||||
box.setButtonDrawable(drawable);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull ImageView image, @ColorInt int color) {
|
||||
image.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
||||
}
|
||||
|
||||
private static Drawable modifySwitchDrawable(@NonNull Context context, @NonNull Drawable from, @ColorInt int tint, boolean thumb, boolean compatSwitch, boolean useDarker) {
|
||||
if (useDarker) {
|
||||
tint = ColorUtil.shiftColor(tint, 1.1f);
|
||||
}
|
||||
tint = ColorUtil.adjustAlpha(tint, (compatSwitch && !thumb) ? 0.5f : 1.0f);
|
||||
int disabled;
|
||||
int normal;
|
||||
if (thumb) {
|
||||
disabled = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_thumb_disabled_dark : R.color.ate_switch_thumb_disabled_light);
|
||||
normal = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_thumb_normal_dark : R.color.ate_switch_thumb_normal_light);
|
||||
} else {
|
||||
disabled = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_track_disabled_dark : R.color.ate_switch_track_disabled_light);
|
||||
normal = ContextCompat.getColor(context, useDarker ? R.color.ate_switch_track_normal_dark : R.color.ate_switch_track_normal_light);
|
||||
}
|
||||
|
||||
// Stock switch includes its own alpha
|
||||
if (!compatSwitch) {
|
||||
normal = ColorUtil.stripAlpha(normal);
|
||||
}
|
||||
|
||||
final ColorStateList sl = new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{-android.R.attr.state_enabled},
|
||||
new int[]{android.R.attr.state_enabled, -android.R.attr.state_activated, -android.R.attr.state_checked},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_activated},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_checked}
|
||||
},
|
||||
new int[]{
|
||||
disabled,
|
||||
normal,
|
||||
tint,
|
||||
tint
|
||||
}
|
||||
);
|
||||
return createTintedDrawable(from, sl);
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull Switch switchView, @ColorInt int color, boolean useDarker) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return;
|
||||
if (switchView.getTrackDrawable() != null) {
|
||||
switchView.setTrackDrawable(modifySwitchDrawable(switchView.getContext(),
|
||||
switchView.getTrackDrawable(), color, false, false, useDarker));
|
||||
}
|
||||
if (switchView.getThumbDrawable() != null) {
|
||||
switchView.setThumbDrawable(modifySwitchDrawable(switchView.getContext(),
|
||||
switchView.getThumbDrawable(), color, true, false, useDarker));
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTint(@NonNull SwitchCompat switchView, @ColorInt int color, boolean useDarker) {
|
||||
if (switchView.getTrackDrawable() != null) {
|
||||
switchView.setTrackDrawable(modifySwitchDrawable(switchView.getContext(),
|
||||
switchView.getTrackDrawable(), color, false, true, useDarker));
|
||||
}
|
||||
if (switchView.getThumbDrawable() != null) {
|
||||
switchView.setThumbDrawable(modifySwitchDrawable(switchView.getContext(),
|
||||
switchView.getThumbDrawable(), color, true, true, useDarker));
|
||||
}
|
||||
}
|
||||
|
||||
// This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise.
|
||||
@CheckResult
|
||||
@Nullable
|
||||
public static Drawable createTintedDrawable(@Nullable Drawable drawable, @ColorInt int color) {
|
||||
if (drawable == null) return null;
|
||||
drawable = DrawableCompat.wrap(drawable.mutate());
|
||||
DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_IN);
|
||||
DrawableCompat.setTint(drawable, color);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
// This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise.
|
||||
@CheckResult
|
||||
@Nullable
|
||||
public static Drawable createTintedDrawable(@Nullable Drawable drawable, @NonNull ColorStateList sl) {
|
||||
if (drawable == null) return null;
|
||||
drawable = DrawableCompat.wrap(drawable.mutate());
|
||||
DrawableCompat.setTintList(drawable, sl);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public static void setCursorTint(@NonNull EditText editText, @ColorInt int color) {
|
||||
try {
|
||||
Field fCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
|
||||
fCursorDrawableRes.setAccessible(true);
|
||||
int mCursorDrawableRes = fCursorDrawableRes.getInt(editText);
|
||||
Field fEditor = TextView.class.getDeclaredField("mEditor");
|
||||
fEditor.setAccessible(true);
|
||||
Object editor = fEditor.get(editText);
|
||||
Class<?> clazz = editor.getClass();
|
||||
Field fCursorDrawable = clazz.getDeclaredField("mCursorDrawable");
|
||||
fCursorDrawable.setAccessible(true);
|
||||
Drawable[] drawables = new Drawable[2];
|
||||
drawables[0] = ContextCompat.getDrawable(editText.getContext(), mCursorDrawableRes);
|
||||
drawables[0] = createTintedDrawable(drawables[0], color);
|
||||
drawables[1] = ContextCompat.getDrawable(editText.getContext(), mCursorDrawableRes);
|
||||
drawables[1] = createTintedDrawable(drawables[1], color);
|
||||
fCursorDrawable.set(editor, drawables);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
455
app/src/main/java/io/legado/app/lib/theme/TintHelper.kt
Normal file
455
app/src/main/java/io/legado/app/lib/theme/TintHelper.kt
Normal file
@ -0,0 +1,455 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.RippleDrawable
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import io.legado.app.R
|
||||
|
||||
/**
|
||||
* @author afollestad, plusCubed
|
||||
*/
|
||||
object TintHelper {
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
@ColorInt
|
||||
private fun getDefaultRippleColor(context: Context, useDarkRipple: Boolean): Int {
|
||||
// Light ripple is actually translucent black, and vice versa
|
||||
return ContextCompat.getColor(
|
||||
context, if (useDarkRipple)
|
||||
R.color.ripple_material_light
|
||||
else
|
||||
R.color.ripple_material_dark
|
||||
)
|
||||
}
|
||||
|
||||
private fun getDisabledColorStateList(@ColorInt normal: Int, @ColorInt disabled: Int): ColorStateList {
|
||||
return ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_enabled),
|
||||
intArrayOf(android.R.attr.state_enabled)
|
||||
), intArrayOf(disabled, normal)
|
||||
)
|
||||
}
|
||||
|
||||
fun setTintSelector(view: View, @ColorInt color: Int, darker: Boolean, useDarkTheme: Boolean) {
|
||||
val isColorLight = ColorUtil.isColorLight(color)
|
||||
val disabled = ContextCompat.getColor(
|
||||
view.context,
|
||||
if (useDarkTheme) R.color.ate_button_disabled_dark else R.color.ate_button_disabled_light
|
||||
)
|
||||
val pressed = ColorUtil.shiftColor(color, if (darker) 0.9f else 1.1f)
|
||||
val activated = ColorUtil.shiftColor(color, if (darker) 1.1f else 0.9f)
|
||||
val rippleColor = getDefaultRippleColor(view.context, isColorLight)
|
||||
val textColor = ContextCompat.getColor(
|
||||
view.context,
|
||||
if (isColorLight) R.color.ate_primary_text_light else R.color.ate_primary_text_dark
|
||||
)
|
||||
|
||||
val sl: ColorStateList
|
||||
if (view is Button) {
|
||||
sl = getDisabledColorStateList(color, disabled)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && view.getBackground() is RippleDrawable) {
|
||||
val rd = view.getBackground() as RippleDrawable
|
||||
rd.setColor(ColorStateList.valueOf(rippleColor))
|
||||
}
|
||||
|
||||
// Disabled text color state for buttons, may get overridden later by ATE tags
|
||||
view.setTextColor(
|
||||
getDisabledColorStateList(
|
||||
textColor,
|
||||
ContextCompat.getColor(
|
||||
view.getContext(),
|
||||
if (useDarkTheme) R.color.ate_button_text_disabled_dark else R.color.ate_button_text_disabled_light
|
||||
)
|
||||
)
|
||||
)
|
||||
} else if (view is FloatingActionButton) {
|
||||
// FloatingActionButton doesn't support disabled state?
|
||||
sl = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_pressed),
|
||||
intArrayOf(android.R.attr.state_pressed)
|
||||
), intArrayOf(color, pressed)
|
||||
)
|
||||
|
||||
view.rippleColor = rippleColor
|
||||
view.backgroundTintList = sl
|
||||
if (view.drawable != null)
|
||||
view.setImageDrawable(createTintedDrawable(view.drawable, textColor))
|
||||
return
|
||||
} else {
|
||||
sl = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_enabled),
|
||||
intArrayOf(android.R.attr.state_enabled),
|
||||
intArrayOf(android.R.attr.state_enabled, android.R.attr.state_pressed),
|
||||
intArrayOf(android.R.attr.state_enabled, android.R.attr.state_activated),
|
||||
intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)
|
||||
),
|
||||
intArrayOf(disabled, color, pressed, activated, activated)
|
||||
)
|
||||
}
|
||||
|
||||
var drawable: Drawable? = view.background
|
||||
if (drawable != null) {
|
||||
drawable = createTintedDrawable(drawable, sl)
|
||||
ViewUtil.setBackgroundCompat(view, drawable)
|
||||
}
|
||||
|
||||
if (view is TextView && view !is Button) {
|
||||
view.setTextColor(
|
||||
getDisabledColorStateList(
|
||||
textColor,
|
||||
ContextCompat.getColor(
|
||||
view.getContext(),
|
||||
if (isColorLight) R.color.ate_text_disabled_light else R.color.ate_text_disabled_dark
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTintAuto(
|
||||
view: View, @ColorInt color: Int,
|
||||
background: Boolean
|
||||
) {
|
||||
setTintAuto(view, color, background, ATHUtil.isWindowBackgroundDark(view.context))
|
||||
}
|
||||
|
||||
fun setTintAuto(
|
||||
view: View, @ColorInt color: Int,
|
||||
background: Boolean, isDark: Boolean
|
||||
) {
|
||||
var background = background
|
||||
if (!background) {
|
||||
if (view is RadioButton)
|
||||
setTint(view, color, isDark)
|
||||
else if (view is SeekBar)
|
||||
setTint(view, color, isDark)
|
||||
else if (view is ProgressBar)
|
||||
setTint(view, color)
|
||||
else if (view is AppCompatEditText)
|
||||
setTint(view, color, isDark)
|
||||
else if (view is CheckBox)
|
||||
setTint(view, color, isDark)
|
||||
else if (view is ImageView)
|
||||
setTint(view, color)
|
||||
else if (view is Switch)
|
||||
setTint(view, color, isDark)
|
||||
else if (view is SwitchCompat)
|
||||
setTint(view, color, isDark)
|
||||
else if (view is SearchView) {
|
||||
val iconIdS =
|
||||
intArrayOf(androidx.appcompat.R.id.search_button, androidx.appcompat.R.id.search_close_btn)
|
||||
for (iconId in iconIdS) {
|
||||
val icon = view.findViewById<ImageView>(iconId)
|
||||
if (icon != null) {
|
||||
setTint(icon, color)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
background = true
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||
!background && view.background is RippleDrawable
|
||||
) {
|
||||
// Ripples for the above views (e.g. when you tap and hold a switch or checkbox)
|
||||
val rd = view.background as RippleDrawable
|
||||
@SuppressLint("PrivateResource") val unchecked = ContextCompat.getColor(
|
||||
view.context,
|
||||
if (isDark) R.color.ripple_material_dark else R.color.ripple_material_light
|
||||
)
|
||||
val checked = ColorUtil.adjustAlpha(color, 0.4f)
|
||||
val sl = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_activated, -android.R.attr.state_checked),
|
||||
intArrayOf(android.R.attr.state_activated),
|
||||
intArrayOf(android.R.attr.state_checked)
|
||||
),
|
||||
intArrayOf(unchecked, checked, checked)
|
||||
)
|
||||
rd.setColor(sl)
|
||||
}
|
||||
}
|
||||
if (background) {
|
||||
// Need to tint the background of a view
|
||||
if (view is FloatingActionButton || view is Button) {
|
||||
setTintSelector(view, color, false, isDark)
|
||||
} else if (view.background != null) {
|
||||
var drawable: Drawable? = view.background
|
||||
if (drawable != null) {
|
||||
drawable = createTintedDrawable(drawable, color)
|
||||
ViewUtil.setBackgroundCompat(view, drawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setTint(radioButton: RadioButton, @ColorInt color: Int, useDarker: Boolean) {
|
||||
val sl = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_enabled),
|
||||
intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked),
|
||||
intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)
|
||||
), intArrayOf(
|
||||
// Rdio button includes own alpha for disabled state
|
||||
ColorUtil.stripAlpha(
|
||||
ContextCompat.getColor(
|
||||
radioButton.context,
|
||||
if (useDarker) R.color.ate_control_disabled_dark else R.color.ate_control_disabled_light
|
||||
)
|
||||
),
|
||||
ContextCompat.getColor(
|
||||
radioButton.context,
|
||||
if (useDarker) R.color.ate_control_normal_dark else R.color.ate_control_normal_light
|
||||
),
|
||||
color
|
||||
)
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
radioButton.buttonTintList = sl
|
||||
} else {
|
||||
val d = createTintedDrawable(
|
||||
ContextCompat.getDrawable(radioButton.context, R.drawable.abc_btn_radio_material),
|
||||
sl
|
||||
)
|
||||
radioButton.buttonDrawable = d
|
||||
}
|
||||
}
|
||||
|
||||
fun setTint(seekBar: SeekBar, @ColorInt color: Int, useDarker: Boolean) {
|
||||
val s1 = getDisabledColorStateList(
|
||||
color,
|
||||
ContextCompat.getColor(
|
||||
seekBar.context,
|
||||
if (useDarker) R.color.ate_control_disabled_dark else R.color.ate_control_disabled_light
|
||||
)
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
seekBar.thumbTintList = s1
|
||||
seekBar.progressTintList = s1
|
||||
} else {
|
||||
val progressDrawable = createTintedDrawable(seekBar.progressDrawable, s1)
|
||||
seekBar.progressDrawable = progressDrawable
|
||||
val thumbDrawable = createTintedDrawable(seekBar.thumb, s1)
|
||||
seekBar.thumb = thumbDrawable
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun setTint(progressBar: ProgressBar, @ColorInt color: Int, skipIndeterminate: Boolean = false) {
|
||||
val sl = ColorStateList.valueOf(color)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
progressBar.progressTintList = sl
|
||||
progressBar.secondaryProgressTintList = sl
|
||||
if (!skipIndeterminate)
|
||||
progressBar.indeterminateTintList = sl
|
||||
} else {
|
||||
val mode = PorterDuff.Mode.SRC_IN
|
||||
if (!skipIndeterminate && progressBar.indeterminateDrawable != null)
|
||||
progressBar.indeterminateDrawable.setColorFilter(color, mode)
|
||||
if (progressBar.progressDrawable != null)
|
||||
progressBar.progressDrawable.setColorFilter(color, mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun setTint(editText: AppCompatEditText, @ColorInt color: Int, useDarker: Boolean) {
|
||||
val editTextColorStateList = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_enabled),
|
||||
intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_pressed, -android.R.attr.state_focused),
|
||||
intArrayOf()
|
||||
),
|
||||
intArrayOf(
|
||||
ContextCompat.getColor(
|
||||
editText.context,
|
||||
if (useDarker) R.color.ate_text_disabled_dark else R.color.ate_text_disabled_light
|
||||
),
|
||||
ContextCompat.getColor(
|
||||
editText.context,
|
||||
if (useDarker) R.color.ate_control_normal_dark else R.color.ate_control_normal_light
|
||||
),
|
||||
color
|
||||
)
|
||||
)
|
||||
editText.supportBackgroundTintList = editTextColorStateList
|
||||
setCursorTint(editText, color)
|
||||
}
|
||||
|
||||
fun setTint(box: CheckBox, @ColorInt color: Int, useDarker: Boolean) {
|
||||
val sl = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_enabled),
|
||||
intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked),
|
||||
intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)
|
||||
),
|
||||
intArrayOf(
|
||||
ContextCompat.getColor(
|
||||
box.context,
|
||||
if (useDarker) R.color.ate_control_disabled_dark else R.color.ate_control_disabled_light
|
||||
),
|
||||
ContextCompat.getColor(
|
||||
box.context,
|
||||
if (useDarker) R.color.ate_control_normal_dark else R.color.ate_control_normal_light
|
||||
),
|
||||
color
|
||||
)
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
box.buttonTintList = sl
|
||||
} else {
|
||||
val drawable =
|
||||
createTintedDrawable(ContextCompat.getDrawable(box.context, R.drawable.abc_btn_check_material), sl)
|
||||
box.buttonDrawable = drawable
|
||||
}
|
||||
}
|
||||
|
||||
fun setTint(image: ImageView, @ColorInt color: Int) {
|
||||
image.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
|
||||
}
|
||||
|
||||
private fun modifySwitchDrawable(
|
||||
context: Context,
|
||||
from: Drawable, @ColorInt tint: Int,
|
||||
thumb: Boolean,
|
||||
compatSwitch: Boolean,
|
||||
useDarker: Boolean
|
||||
): Drawable? {
|
||||
var tint = tint
|
||||
if (useDarker) {
|
||||
tint = ColorUtil.shiftColor(tint, 1.1f)
|
||||
}
|
||||
tint = ColorUtil.adjustAlpha(tint, if (compatSwitch && !thumb) 0.5f else 1.0f)
|
||||
val disabled: Int
|
||||
var normal: Int
|
||||
if (thumb) {
|
||||
disabled = ContextCompat.getColor(
|
||||
context,
|
||||
if (useDarker) R.color.ate_switch_thumb_disabled_dark else R.color.ate_switch_thumb_disabled_light
|
||||
)
|
||||
normal = ContextCompat.getColor(
|
||||
context,
|
||||
if (useDarker) R.color.ate_switch_thumb_normal_dark else R.color.ate_switch_thumb_normal_light
|
||||
)
|
||||
} else {
|
||||
disabled = ContextCompat.getColor(
|
||||
context,
|
||||
if (useDarker) R.color.ate_switch_track_disabled_dark else R.color.ate_switch_track_disabled_light
|
||||
)
|
||||
normal = ContextCompat.getColor(
|
||||
context,
|
||||
if (useDarker) R.color.ate_switch_track_normal_dark else R.color.ate_switch_track_normal_light
|
||||
)
|
||||
}
|
||||
|
||||
// Stock switch includes its own alpha
|
||||
if (!compatSwitch) {
|
||||
normal = ColorUtil.stripAlpha(normal)
|
||||
}
|
||||
|
||||
val sl = ColorStateList(
|
||||
arrayOf(
|
||||
intArrayOf(-android.R.attr.state_enabled),
|
||||
intArrayOf(
|
||||
android.R.attr.state_enabled,
|
||||
-android.R.attr.state_activated,
|
||||
-android.R.attr.state_checked
|
||||
),
|
||||
intArrayOf(android.R.attr.state_enabled, android.R.attr.state_activated),
|
||||
intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)
|
||||
),
|
||||
intArrayOf(disabled, normal, tint, tint)
|
||||
)
|
||||
return createTintedDrawable(from, sl)
|
||||
}
|
||||
|
||||
fun setTint(switchView: Switch, @ColorInt color: Int, useDarker: Boolean) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return
|
||||
if (switchView.trackDrawable != null) {
|
||||
switchView.trackDrawable = modifySwitchDrawable(
|
||||
switchView.context,
|
||||
switchView.trackDrawable, color, false, false, useDarker
|
||||
)
|
||||
}
|
||||
if (switchView.thumbDrawable != null) {
|
||||
switchView.thumbDrawable = modifySwitchDrawable(
|
||||
switchView.context,
|
||||
switchView.thumbDrawable, color, true, false, useDarker
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTint(switchView: SwitchCompat, @ColorInt color: Int, useDarker: Boolean) {
|
||||
if (switchView.trackDrawable != null) {
|
||||
switchView.trackDrawable = modifySwitchDrawable(
|
||||
switchView.context,
|
||||
switchView.trackDrawable, color, false, true, useDarker
|
||||
)
|
||||
}
|
||||
if (switchView.thumbDrawable != null) {
|
||||
switchView.thumbDrawable = modifySwitchDrawable(
|
||||
switchView.context,
|
||||
switchView.thumbDrawable, color, true, true, useDarker
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise.
|
||||
@CheckResult
|
||||
fun createTintedDrawable(drawable: Drawable?, @ColorInt color: Int): Drawable? {
|
||||
var drawable: Drawable? = drawable ?: return null
|
||||
drawable = DrawableCompat.wrap(drawable!!.mutate())
|
||||
DrawableCompat.setTintMode(drawable!!, PorterDuff.Mode.SRC_IN)
|
||||
DrawableCompat.setTint(drawable, color)
|
||||
return drawable
|
||||
}
|
||||
|
||||
// This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise.
|
||||
@CheckResult
|
||||
fun createTintedDrawable(drawable: Drawable?, sl: ColorStateList): Drawable? {
|
||||
var drawable: Drawable? = drawable ?: return null
|
||||
drawable = DrawableCompat.wrap(drawable!!.mutate())
|
||||
DrawableCompat.setTintList(drawable!!, sl)
|
||||
return drawable
|
||||
}
|
||||
|
||||
fun setCursorTint(editText: EditText, @ColorInt color: Int) {
|
||||
try {
|
||||
val fCursorDrawableRes = TextView::class.java.getDeclaredField("mCursorDrawableRes")
|
||||
fCursorDrawableRes.isAccessible = true
|
||||
val mCursorDrawableRes = fCursorDrawableRes.getInt(editText)
|
||||
val fEditor = TextView::class.java.getDeclaredField("mEditor")
|
||||
fEditor.isAccessible = true
|
||||
val editor = fEditor.get(editText)
|
||||
val clazz = editor.javaClass
|
||||
val fCursorDrawable = clazz.getDeclaredField("mCursorDrawable")
|
||||
fCursorDrawable.isAccessible = true
|
||||
val drawables = arrayOfNulls<Drawable>(2)
|
||||
drawables[0] = ContextCompat.getDrawable(editText.context, mCursorDrawableRes)
|
||||
drawables[0] = createTintedDrawable(drawables[0], color)
|
||||
drawables[1] = ContextCompat.getDrawable(editText.context, mCursorDrawableRes)
|
||||
drawables[1] = createTintedDrawable(drawables[1], color)
|
||||
fCursorDrawable.set(editor, drawables)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
package io.legado.app.lib.theme;
|
||||
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.TransitionDrawable;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public final class ViewUtil {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) {
|
||||
v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setBackgroundCompat(@NonNull View view, @Nullable Drawable drawable) {
|
||||
view.setBackground(drawable);
|
||||
}
|
||||
|
||||
public static TransitionDrawable setBackgroundTransition(@NonNull View view, @NonNull Drawable newDrawable) {
|
||||
TransitionDrawable transition = DrawableUtil.createTransitionDrawable(view.getBackground(), newDrawable);
|
||||
setBackgroundCompat(view, transition);
|
||||
return transition;
|
||||
}
|
||||
|
||||
public static TransitionDrawable setBackgroundColorTransition(@NonNull View view, @ColorInt int newColor) {
|
||||
final Drawable oldColor = view.getBackground();
|
||||
|
||||
Drawable start = oldColor != null ? oldColor : new ColorDrawable(view.getSolidColor());
|
||||
Drawable end = new ColorDrawable(newColor);
|
||||
|
||||
TransitionDrawable transition = DrawableUtil.createTransitionDrawable(start, end);
|
||||
|
||||
setBackgroundCompat(view, transition);
|
||||
|
||||
return transition;
|
||||
}
|
||||
|
||||
private ViewUtil() {
|
||||
}
|
||||
}
|
41
app/src/main/java/io/legado/app/lib/theme/ViewUtil.kt
Normal file
41
app/src/main/java/io/legado/app/lib/theme/ViewUtil.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package io.legado.app.lib.theme
|
||||
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.TransitionDrawable
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
object ViewUtil {
|
||||
|
||||
fun removeOnGlobalLayoutListener(v: View, listener: ViewTreeObserver.OnGlobalLayoutListener) {
|
||||
v.viewTreeObserver.removeOnGlobalLayoutListener(listener)
|
||||
}
|
||||
|
||||
fun setBackgroundCompat(view: View, drawable: Drawable?) {
|
||||
view.background = drawable
|
||||
}
|
||||
|
||||
fun setBackgroundTransition(view: View, newDrawable: Drawable): TransitionDrawable {
|
||||
val transition = DrawableUtil.createTransitionDrawable(view.background, newDrawable)
|
||||
setBackgroundCompat(view, transition)
|
||||
return transition
|
||||
}
|
||||
|
||||
fun setBackgroundColorTransition(view: View, @ColorInt newColor: Int): TransitionDrawable {
|
||||
val oldColor = view.background
|
||||
|
||||
val start = oldColor ?: ColorDrawable(view.solidColor)
|
||||
val end = ColorDrawable(newColor)
|
||||
|
||||
val transition = DrawableUtil.createTransitionDrawable(start, end)
|
||||
|
||||
setBackgroundCompat(view, transition)
|
||||
|
||||
return transition
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package io.legado.app.lib.theme.prefs
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceCategory
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
|
||||
class ATEPreferenceCategory : PreferenceCategory {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr,
|
||||
defStyleRes
|
||||
)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
override fun onBindView(view: View) {
|
||||
super.onBindView(view)
|
||||
if (view is TextView) {
|
||||
view.setTextColor(ThemeStore.accentColor(view.getContext()))//设置title文本的颜色
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package io.legado.app.lib.theme.prefs
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.preference.SwitchPreference
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Switch
|
||||
import androidx.annotation.RequiresApi
|
||||
import io.legado.app.lib.theme.ATH
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import java.util.*
|
||||
|
||||
class ATESwitchPreference : SwitchPreference {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr,
|
||||
defStyleRes
|
||||
)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
override fun onBindView(view: View) {
|
||||
super.onBindView(view)
|
||||
|
||||
if (view is ViewGroup) {
|
||||
val queue = LinkedList<ViewGroup>()
|
||||
queue.add(view)
|
||||
while (!queue.isEmpty()) {
|
||||
val current = queue.removeFirst()
|
||||
for (i in 0 until current.childCount) {
|
||||
if (current.getChildAt(i) is Switch) {
|
||||
ATH.setTint(current.getChildAt(i), ThemeStore.accentColor(view.getContext()))
|
||||
return
|
||||
} else if (current.getChildAt(i) is ViewGroup) {
|
||||
queue.addLast(current.getChildAt(i) as ViewGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package io.legado.app.lib.theme.prefs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog.Builder
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.preference.ListPreference
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.CheckedTextView
|
||||
import android.widget.ImageView
|
||||
import android.widget.ListAdapter
|
||||
import io.legado.app.R
|
||||
import java.util.*
|
||||
|
||||
|
||||
class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference(context, attrs) {
|
||||
|
||||
private val mEntryDrawables = ArrayList<Drawable>()
|
||||
|
||||
init {
|
||||
|
||||
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.IconListPreference, 0, 0)
|
||||
|
||||
val drawables: Array<CharSequence>
|
||||
|
||||
try {
|
||||
drawables = a.getTextArray(R.styleable.IconListPreference_icons)
|
||||
} finally {
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
for (drawable in drawables) {
|
||||
val resId = context.resources.getIdentifier(drawable.toString(), "mipmap", context.packageName)
|
||||
|
||||
val d = context.resources.getDrawable(resId)
|
||||
|
||||
mEntryDrawables.add(d)
|
||||
}
|
||||
|
||||
widgetLayoutResource = R.layout.view_icon
|
||||
}
|
||||
|
||||
protected fun createListAdapter(): ListAdapter {
|
||||
val selectedValue = value
|
||||
val selectedIndex = findIndexOfValue(selectedValue)
|
||||
return AppArrayAdapter(context, R.layout.item_icon_preference, entries, mEntryDrawables, selectedIndex)
|
||||
}
|
||||
|
||||
override fun onBindView(view: View) {
|
||||
super.onBindView(view)
|
||||
|
||||
val selectedValue = value
|
||||
val selectedIndex = findIndexOfValue(selectedValue)
|
||||
|
||||
val drawable = mEntryDrawables[selectedIndex]
|
||||
|
||||
(view.findViewById<View>(R.id.preview) as ImageView).setImageDrawable(drawable)
|
||||
}
|
||||
|
||||
override fun onPrepareDialogBuilder(builder: Builder) {
|
||||
builder.setAdapter(createListAdapter(), this)
|
||||
super.onPrepareDialogBuilder(builder)
|
||||
}
|
||||
|
||||
inner class AppArrayAdapter(
|
||||
context: Context, textViewResourceId: Int,
|
||||
objects: Array<CharSequence>, imageDrawables: List<Drawable>,
|
||||
selectedIndex: Int
|
||||
) : ArrayAdapter<CharSequence>(context, textViewResourceId, objects) {
|
||||
private var mImageDrawables: List<Drawable>? = null
|
||||
private var mSelectedIndex = 0
|
||||
|
||||
init {
|
||||
mSelectedIndex = selectedIndex
|
||||
mImageDrawables = imageDrawables
|
||||
}
|
||||
|
||||
@SuppressLint("ViewHolder")
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val inflater = (context as Activity).layoutInflater
|
||||
val view = inflater.inflate(R.layout.item_icon_preference, parent, false)
|
||||
val textView = view.findViewById<View>(R.id.label) as CheckedTextView
|
||||
textView.text = getItem(position)
|
||||
textView.isChecked = position == mSelectedIndex
|
||||
|
||||
val imageView = view.findViewById<View>(R.id.icon) as ImageView
|
||||
imageView.setImageDrawable(mImageDrawables!![position])
|
||||
return view
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import io.legado.app.lib.theme.ColorUtil
|
||||
import io.legado.app.lib.theme.Selector
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import io.legado.app.utils.dp
|
||||
|
||||
class ATEAccentBgTextView : AppCompatTextView {
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
background = Selector.shapeBuild()
|
||||
.setCornerRadius(3.dp)
|
||||
.setDefaultBgColor(ThemeStore.accentColor(context))
|
||||
.setPressedBgColor(ColorUtil.darkenColor(ThemeStore.accentColor(context)))
|
||||
.create()
|
||||
setTextColor(Color.WHITE)
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import io.legado.app.R
|
||||
import io.legado.app.lib.theme.Selector
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import io.legado.app.utils.dp
|
||||
|
||||
class ATEAccentStrokeTextView : AppCompatTextView {
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
background = Selector.shapeBuild()
|
||||
.setCornerRadius(3.dp)
|
||||
.setStrokeWidth(1.dp)
|
||||
.setDisabledStrokeColor(context.resources.getColor(R.color.md_grey_500))
|
||||
.setDefaultStrokeColor(ThemeStore.accentColor(context))
|
||||
.setPressedBgColor(context.resources.getColor(R.color.transparent30))
|
||||
.create()
|
||||
setTextColor(
|
||||
Selector.colorBuild()
|
||||
.setDefaultColor(ThemeStore.accentColor(context))
|
||||
.setDisabledColor(context.resources.getColor(R.color.md_grey_500))
|
||||
.create()
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatAutoCompleteTextView
|
||||
import io.legado.app.lib.theme.Selector
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
|
||||
class ATEAutoCompleteTextView : AppCompatAutoCompleteTextView {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
private fun init(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
backgroundTintList = Selector.colorBuild()
|
||||
.setFocusedColor(ThemeStore.accentColor(context))
|
||||
.setDefaultColor(ThemeStore.textColorPrimary(context))
|
||||
.create()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatCheckBox
|
||||
import io.legado.app.lib.theme.ATH
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATECheckBox : AppCompatCheckBox {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
ATH.setTint(this, ThemeStore.accentColor(context))
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import io.legado.app.lib.theme.ATH
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATEEditText : AppCompatEditText {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
ATH.setTint(this, ThemeStore.accentColor(context))
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATEPrimaryTextView : AppCompatTextView {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
setTextColor(ThemeStore.textColorPrimary(context))
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.widget.ProgressBar
|
||||
import io.legado.app.lib.theme.ATH
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATEProgressBar : ProgressBar {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr,
|
||||
defStyleRes
|
||||
) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
ATH.setTint(this, ThemeStore.accentColor(context))
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatRadioButton
|
||||
import io.legado.app.lib.theme.ATH
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATERadioButton : AppCompatRadioButton {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
ATH.setTint(this, ThemeStore.accentColor(context))
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatRadioButton
|
||||
import io.legado.app.lib.theme.Selector
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import io.legado.app.utils.dp
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATERadioNoButton : AppCompatRadioButton {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
background = Selector.shapeBuild()
|
||||
.setCornerRadius(3.dp)
|
||||
.setStrokeWidth(3.dp)
|
||||
.setCheckedBgColor(ThemeStore.accentColor(context))
|
||||
.setCheckedStrokeColor(ThemeStore.accentColor(context))
|
||||
.setDefaultStrokeColor(Color.WHITE)
|
||||
.create()
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATESecondaryTextView : AppCompatTextView {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
setTextColor(ThemeStore.textColorSecondary(context))
|
||||
}
|
||||
}
|
29
app/src/main/java/io/legado/app/lib/theme/view/ATESeekBar.kt
Normal file
29
app/src/main/java/io/legado/app/lib/theme/view/ATESeekBar.kt
Normal file
@ -0,0 +1,29 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatSeekBar
|
||||
import io.legado.app.lib.theme.ATH
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATESeekBar : AppCompatSeekBar {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
ATH.setTint(this, ThemeStore.accentColor(context))
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.Switch
|
||||
import io.legado.app.lib.theme.ATH
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATEStockSwitch : Switch {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
ATH.setTint(this, ThemeStore.accentColor(context))
|
||||
}
|
||||
|
||||
override fun isShown(): Boolean {
|
||||
return parent != null && visibility == View.VISIBLE
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import io.legado.app.R
|
||||
import io.legado.app.lib.theme.Selector
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
import io.legado.app.utils.dp
|
||||
|
||||
class ATEStrokeTextView : AppCompatTextView {
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
background = Selector.shapeBuild()
|
||||
.setCornerRadius(1.dp)
|
||||
.setStrokeWidth(1.dp)
|
||||
.setDisabledStrokeColor(context.resources.getColor(R.color.md_grey_500))
|
||||
.setDefaultStrokeColor(ThemeStore.textColorSecondary(context))
|
||||
.setSelectedStrokeColor(ThemeStore.accentColor(context))
|
||||
.setPressedBgColor(context.resources.getColor(R.color.transparent30))
|
||||
.create()
|
||||
setTextColor(
|
||||
Selector.colorBuild()
|
||||
.setDefaultColor(ThemeStore.textColorSecondary(context))
|
||||
.setSelectedColor(ThemeStore.accentColor(context))
|
||||
.setDisabledColor(context.resources.getColor(R.color.md_grey_500))
|
||||
.create()
|
||||
)
|
||||
}
|
||||
}
|
34
app/src/main/java/io/legado/app/lib/theme/view/ATESwitch.kt
Normal file
34
app/src/main/java/io/legado/app/lib/theme/view/ATESwitch.kt
Normal file
@ -0,0 +1,34 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.Switch
|
||||
import io.legado.app.lib.theme.ATH
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
/**
|
||||
* @author Aidan Follestad (afollestad)
|
||||
*/
|
||||
class ATESwitch : Switch {
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
ATH.setTint(this, ThemeStore.accentColor(context))
|
||||
}
|
||||
|
||||
override fun isShown(): Boolean {
|
||||
return parent != null && visibility == View.VISIBLE
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.legado.app.lib.theme.view
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.util.AttributeSet
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import io.legado.app.lib.theme.Selector
|
||||
import io.legado.app.lib.theme.ThemeStore
|
||||
|
||||
class ATETextInputLayout : TextInputLayout {
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
init(context)
|
||||
}
|
||||
|
||||
private fun init(context: Context) {
|
||||
defaultHintTextColor = Selector.colorBuild().setDefaultColor(ThemeStore.accentColor(context)).create()
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
|
||||
super.draw(canvas)
|
||||
}
|
||||
}
|
@ -1,13 +1,19 @@
|
||||
package io.legado.app.ui.about
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseActivity
|
||||
import io.legado.app.utils.getViewModel
|
||||
|
||||
class AboutActivity : AppCompatActivity() {
|
||||
class AboutActivity : BaseActivity<AndroidViewModel>() {
|
||||
override val viewModel: AndroidViewModel
|
||||
get() = getViewModel(AndroidViewModel::class.java)
|
||||
override val layoutID: Int
|
||||
get() = R.layout.activity_about
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_about)
|
||||
override fun onViewModelCreated(viewModel: AndroidViewModel, savedInstanceState: Bundle?) {
|
||||
super.onViewModelCreated(viewModel, savedInstanceState)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.app_bar_main.*
|
||||
import org.jetbrains.anko.startActivity
|
||||
|
||||
class MainActivity : BaseActivity<MainDataBinding, MainViewModel>(), NavigationView.OnNavigationItemSelectedListener {
|
||||
class MainActivity : BaseActivity<MainViewModel>(), NavigationView.OnNavigationItemSelectedListener {
|
||||
override val viewModel: MainViewModel
|
||||
get() = getViewModel(MainViewModel::class.java)
|
||||
|
||||
|
@ -2,34 +2,37 @@ package io.legado.app.ui.replacerule
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.legado.app.App
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseActivity
|
||||
import io.legado.app.constant.AppConst.APP_TAG
|
||||
import io.legado.app.data.entities.ReplaceRule
|
||||
import io.legado.app.utils.getViewModel
|
||||
import kotlinx.android.synthetic.main.activity_replace_rule.*
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import io.legado.app.constant.AppConst.APP_TAG
|
||||
import kotlinx.android.synthetic.main.item_relace_rule.*
|
||||
|
||||
|
||||
class ReplaceRuleActivity : AppCompatActivity() {
|
||||
class ReplaceRuleActivity : BaseActivity<ReplaceRuleViewModel>() {
|
||||
override val viewModel: ReplaceRuleViewModel
|
||||
get() = getViewModel(ReplaceRuleViewModel::class.java)
|
||||
override val layoutID: Int
|
||||
get() = R.layout.activity_replace_rule
|
||||
private lateinit var adapter: ReplaceRuleAdapter
|
||||
private var rulesLiveData: LiveData<PagedList<ReplaceRule>>? = null
|
||||
private var allEnabled = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_replace_rule)
|
||||
override fun onViewModelCreated(viewModel: ReplaceRuleViewModel, savedInstanceState: Bundle?) {
|
||||
super.onViewModelCreated(viewModel, savedInstanceState)
|
||||
initRecyclerView()
|
||||
initDataObservers()
|
||||
initSwipeToDelete()
|
||||
|
@ -0,0 +1,11 @@
|
||||
package io.legado.app.ui.replacerule;
|
||||
|
||||
import android.app.Application;
|
||||
import io.legado.app.base.BaseViewModel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ReplaceRuleViewModel extends BaseViewModel {
|
||||
public ReplaceRuleViewModel(@NotNull Application application) {
|
||||
super(application);
|
||||
}
|
||||
}
|
@ -3,11 +3,10 @@ package io.legado.app.ui.search
|
||||
import android.os.Bundle
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseActivity
|
||||
import io.legado.app.data.entities.rule.Rule
|
||||
import io.legado.app.utils.getViewModel
|
||||
import org.jetbrains.anko.startActivity
|
||||
|
||||
class SearchActivity : BaseActivity<SearchDataBinding, SearchViewModel>() {
|
||||
class SearchActivity : BaseActivity<SearchViewModel>() {
|
||||
|
||||
override val viewModel: SearchViewModel
|
||||
get() = getViewModel(SearchViewModel::class.java)
|
||||
|
@ -0,0 +1,7 @@
|
||||
package io.legado.app.utils
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import io.legado.app.lib.theme.ATH
|
||||
|
||||
val AlertDialog.upTint: AlertDialog
|
||||
get() = ATH.setAlertDialogTint(this)
|
16
app/src/main/java/io/legado/app/utils/FloatExtensions.kt
Normal file
16
app/src/main/java/io/legado/app/utils/FloatExtensions.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package io.legado.app.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
|
||||
|
||||
val Float.dp: Float // [xxhdpi](360 -> 1080)
|
||||
get() = android.util.TypedValue.applyDimension(
|
||||
android.util.TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics
|
||||
)
|
||||
|
||||
|
||||
val Float.sp: Float // [xxhdpi](360 -> 1080)
|
||||
get() = android.util.TypedValue.applyDimension(
|
||||
android.util.TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics
|
||||
)
|
||||
|
13
app/src/main/java/io/legado/app/utils/IntExtensions.kt
Normal file
13
app/src/main/java/io/legado/app/utils/IntExtensions.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package io.legado.app.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
|
||||
val Int.dp: Int
|
||||
get() = android.util.TypedValue.applyDimension(
|
||||
android.util.TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), Resources.getSystem().displayMetrics
|
||||
).toInt()
|
||||
|
||||
val Int.sp: Int
|
||||
get() = android.util.TypedValue.applyDimension(
|
||||
android.util.TypedValue.COMPLEX_UNIT_SP, this.toFloat(), Resources.getSystem().displayMetrics
|
||||
).toInt()
|
5
app/src/main/res/drawable-v21/bg_ib_pre.xml
Normal file
5
app/src/main/res/drawable-v21/bg_ib_pre.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/btn_bg_press_2">
|
||||
<item android:id="@android:id/mask" android:drawable="@color/btn_bg_press_2"/>
|
||||
</ripple>
|
3
app/src/main/res/drawable-v21/bg_ib_pre_round.xml
Normal file
3
app/src/main/res/drawable-v21/bg_ib_pre_round.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/btn_bg_press"/>
|
10
app/src/main/res/drawable/bg_chapter_item_divider.xml
Normal file
10
app/src/main/res/drawable/bg_chapter_item_divider.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="line">
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/bg_divider_line"
|
||||
android:dashWidth="3dp"
|
||||
android:dashGap="3dp"/>
|
||||
|
||||
</shape>
|
6
app/src/main/res/drawable/bg_edit.xml
Normal file
6
app/src/main/res/drawable/bg_edit.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<stroke android:width="1dp"
|
||||
android:color="@color/btn_bg_press"/>
|
||||
</shape>
|
5
app/src/main/res/drawable/bg_ib_pre.xml
Normal file
5
app/src/main/res/drawable/bg_ib_pre.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/transparent" android:state_pressed="false"/>
|
||||
<item android:drawable="@color/btn_bg_press" android:state_pressed="true"/>
|
||||
</selector>
|
BIN
app/src/main/res/drawable/bg_shadow_bottom.png
Normal file
BIN
app/src/main/res/drawable/bg_shadow_bottom.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/drawable/bg_shadow_bottom_night.png
Normal file
BIN
app/src/main/res/drawable/bg_shadow_bottom_night.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 418 B |
BIN
app/src/main/res/drawable/bg_shadow_top.png
Normal file
BIN
app/src/main/res/drawable/bg_shadow_top.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 340 B |
BIN
app/src/main/res/drawable/bg_shadow_top_night.png
Normal file
BIN
app/src/main/res/drawable/bg_shadow_top_night.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 424 B |
10
app/src/main/res/drawable/bg_textfield_search.xml
Normal file
10
app/src/main/res/drawable/bg_textfield_search.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="PrivateResource">
|
||||
<item android:drawable="@drawable/abc_textfield_search_activated_mtrl_alpha" android:state_enabled="true"
|
||||
android:state_focused="true"/>
|
||||
<item android:drawable="@drawable/abc_textfield_search_activated_mtrl_alpha" android:state_activated="true"
|
||||
android:state_enabled="true"/>
|
||||
<item android:drawable="@drawable/abc_textfield_search_default_mtrl_alpha" android:state_enabled="true"/>
|
||||
<item android:drawable="@drawable/abc_textfield_search_default_mtrl_alpha"/>
|
||||
</selector>
|
39
app/src/main/res/drawable/fastscroll_bubble.xml
Normal file
39
app/src/main/res/drawable/fastscroll_bubble.xml
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2016 L4 Digital LLC. All rights reserved.
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:shape="rectangle">
|
||||
|
||||
<tools:solid android:color="#777777"/>
|
||||
|
||||
<corners
|
||||
android:topLeftRadius="@dimen/fastscroll_bubble_radius"
|
||||
android:topRightRadius="@dimen/fastscroll_bubble_radius"
|
||||
android:bottomLeftRadius="@dimen/fastscroll_bubble_radius"
|
||||
android:bottomRightRadius="0dp"/>
|
||||
|
||||
<size
|
||||
android:height="@dimen/fastscroll_bubble_size"
|
||||
android:width="@dimen/fastscroll_bubble_size"/>
|
||||
|
||||
<padding
|
||||
android:left="@dimen/fastscroll_bubble_padding"
|
||||
android:right="@dimen/fastscroll_bubble_padding"/>
|
||||
|
||||
</shape>
|
31
app/src/main/res/drawable/fastscroll_handle.xml
Normal file
31
app/src/main/res/drawable/fastscroll_handle.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2016 L4 Digital LLC. All rights reserved.
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:shape="rectangle">
|
||||
|
||||
<tools:solid android:color="#555555"/>
|
||||
|
||||
<corners android:radius="@dimen/fastscroll_handle_radius"/>
|
||||
|
||||
<size
|
||||
android:height="@dimen/fastscroll_handle_height"
|
||||
android:width="@dimen/fastscroll_handle_width"/>
|
||||
|
||||
</shape>
|
27
app/src/main/res/drawable/fastscroll_track.xml
Normal file
27
app/src/main/res/drawable/fastscroll_track.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2016 L4 Digital LLC. All rights reserved.
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:shape="rectangle">
|
||||
|
||||
<tools:solid android:color="#CCCCCC"/>
|
||||
|
||||
<size android:width="@dimen/fastscroll_track_width"/>
|
||||
|
||||
</shape>
|
11
app/src/main/res/drawable/ic_about.xml
Normal file
11
app/src/main/res/drawable/ic_about.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M7.699,18.758 L7.699,13.219 L4,9.742 L20,5.242 L13.332,18.574 L10.785,16.125 L7.699,18.758 Z M9.455,13.035 L12.888,16.267 L16.987,8.288 L9.455,13.035 Z M9.035,14.477 L9.035,15.887 L9.81,15.209 L9.035,14.477 Z M6.655,10.398 L8.449,12.086 L14.544,8.248 L6.655,10.398 Z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_add.xml
Normal file
11
app/src/main/res/drawable/ic_add.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M12.822,20 L11.177,20 L11.177,12.821 L4,12.821 L4,11.178 L11.178,11.178 L11.178,4 L12.823,4 L12.823,11.179 L20,11.179 L20,12.822 L12.822,12.822 L12.822,20 Z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_add_online.xml
Normal file
11
app/src/main/res/drawable/ic_add_online.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M5.689,20 C5.164,20,4.74,19.844,4.432,19.537 C3.242,18.345,4.772,15.546,5.863,13.892 C5.683,13.308,5.595,12.688,5.595,12 C5.595,8.469,8.467,5.596,11.998,5.596 C12.684,5.596,13.308,5.685,13.898,5.868 C15.013,5.131,16.948,4,18.339,4 C18.84,4,19.242,4.146,19.531,4.435 C20.308,5.212,20.118,6.648,18.965,8.697 C18.724,9.127,18.433,9.588,18.099,10.076 C18.296,10.703,18.396,11.345,18.396,11.986 C18.396,15.524,15.526,18.403,11.998,18.403 C11.349,18.403,10.7,18.304,10.067,18.103 C8.774,18.991,7.013,20,5.689,20 Z M6.51,15.317 C5.331,17.292,5.196,18.449,5.357,18.611 C5.418,18.673,5.543,18.706,5.709,18.706 C6.337,18.706,7.422,18.249,8.669,17.469 C7.791,16.935,7.046,16.193,6.51,15.317 Z M11.473,17.068 C11.651,17.087,11.826,17.096,11.998,17.096 C14.806,17.096,17.089,14.803,17.089,11.985 C17.089,11.82,17.081,11.651,17.063,11.482 C16.282,12.473,15.398,13.461,14.428,14.43 C13.48,15.38,12.47,16.28,11.473,17.068 Z M11.998,6.901 C9.188,6.901,6.902,9.19,6.902,12 C6.902,14.017,8.066,15.818,9.885,16.641 C11.084,15.764,12.301,14.711,13.504,13.508 C14.686,12.326,15.765,11.076,16.635,9.883 C15.811,8.068,14.011,6.901,11.998,6.901 Z M15.317,6.516 C16.19,7.051,16.929,7.791,17.461,8.666 C17.598,8.448,17.72,8.247,17.827,8.055 C18.848,6.24,18.729,5.482,18.607,5.36 C18.605,5.357,18.549,5.304,18.335,5.304 C18.084,5.304,17.163,5.396,15.317,6.516 Z"/>
|
||||
</vector>
|
17
app/src/main/res/drawable/ic_arrange.xml
Normal file
17
app/src/main/res/drawable/ic_arrange.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M12.167,4h-2.025c-0.604,0-1.08,0.529-1.08,1.206v13.588c0,0.677,0.476,1.206,1.08,1.206h2.025 c0.605,0,1.08-0.529,1.08-1.206V5.206C13.247,4.529,12.772,4,12.167,4z M11.792,5.454v9.531h-1.273V5.454H11.792z M10.519,18.546 v-2.106h1.273v2.106H10.519z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M7.104,6.693H5.079c-0.596,0-1.078,0.501-1.078,1.117v11.072C4.001,19.499,4.483,20,5.079,20h2.025 c0.596,0,1.081-0.501,1.081-1.117V7.811C8.186,7.195,7.7,6.693,7.104,6.693z M6.729,8.148v6.837H5.455V8.148H6.729z M5.455,18.546 v-2.106h1.274v2.106H5.455z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M17.976,7.433c-0.098-0.534-0.551-0.921-1.075-0.921l-2.183,0.383 c-0.584,0.108-0.969,0.689-0.858,1.294l2.006,10.89c0.099,0.534,0.55,0.92,1.074,0.92l2.183-0.383 c0.585-0.108,0.969-0.689,0.859-1.294L17.976,7.433z M15.354,8.256l1.25-0.234l1.289,7.004l-1.256,0.209L15.354,8.256z M17.235,18.484L16.9,16.667l1.257-0.209l0.33,1.797L17.235,18.484z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_arrow_back.xml
Normal file
9
app/src/main/res/drawable/ic_arrow_back.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_arrow_drop_down.xml
Normal file
9
app/src/main/res/drawable/ic_arrow_drop_down.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7,10l5,5 5,-5z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_arrow_drop_up.xml
Normal file
9
app/src/main/res/drawable/ic_arrow_drop_up.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7,14l5,-5 5,5z"/>
|
||||
</vector>
|
12
app/src/main/res/drawable/ic_author.xml
Normal file
12
app/src/main/res/drawable/ic_author.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,3c-4.963,0 -9,4.037 -9,9s4.037,9 9,9s9,-4.037 9,-9S16.963,3 12,3zM12,13.826c-2.277,0 -4.371,1.177 -5.542,3.092C5.245,15.554 4.581,13.82 4.581,12c0,-4.091 3.328,-7.419 7.419,-7.419S19.419,7.909 19.419,12c0,1.82 -0.664,3.554 -1.877,4.918C16.371,15.003 14.277,13.826 12,13.826zM12,15.407c1.811,0 3.489,1.013 4.33,2.598c-1.27,0.926 -2.762,1.414 -4.33,1.414c-1.568,0 -3.062,-0.488 -4.332,-1.414C8.511,16.42 10.189,15.407 12,15.407z"
|
||||
android:fillColor="#4A4B4A"/>
|
||||
<path
|
||||
android:pathData="M12,6.014c-2.178,0 -3.951,1.746 -3.951,3.893c0,2.149 1.772,3.899 3.951,3.899s3.951,-1.75 3.951,-3.899C15.951,7.76 14.178,6.014 12,6.014zM12,12.225c-1.307,0 -2.37,-1.041 -2.37,-2.318c0,-1.274 1.063,-2.312 2.37,-2.312s2.37,1.037 2.37,2.312C14.37,11.184 13.307,12.225 12,12.225z"
|
||||
android:fillColor="#4A4B4A"/>
|
||||
</vector>
|
14
app/src/main/res/drawable/ic_auto_page.xml
Normal file
14
app/src/main/res/drawable/ic_auto_page.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M19.109,6.212h-2.16l-0.002-0.817c0-0.366-0.164-0.66-0.44-0.788 c-0.133-0.062-0.278-0.091-0.445-0.091c-0.295,0-0.595,0.092-0.885,0.181l-5.121,1.516H4.891c-0.569,0-1.016,0.407-1.016,0.927 v9.907c0,0.52,0.446,0.926,1.016,0.926h5.283c0.693,0.19,4.949,1.359,5.308,1.463c0.115,0.033,0.229,0.05,0.339,0.05 c0.529,0,1.083-0.422,1.098-0.84l0.002-0.673h2.189c0.569,0,1.016-0.406,1.016-0.926V7.139C20.125,6.619,19.679,6.212,19.109,6.212 z M18.687,7.65v8.883h-1.763l0.023-8.883H18.687z M15.516,17.95c-0.723-0.204-2.282-0.65-3.459-0.988 c-0.794-0.228-1.414-0.405-1.485-0.425l-5.258-0.004V7.65h5.225l4.972-1.524L15.516,17.95z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M 9.544 8.915 L 6.366 12.092 L 9.544 15.269 L 10.562 14.252 L 9.12 12.811 L 14.217 12.811 L 14.217 11.372 L 9.12 11.372 L 10.562 9.932 Z"/>
|
||||
</vector>
|
17
app/src/main/res/drawable/ic_auto_page_stop.xml
Normal file
17
app/src/main/res/drawable/ic_auto_page_stop.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M19.109,6.212h-2.16l-0.002-0.817c0-0.366-0.164-0.66-0.44-0.788 c-0.133-0.062-0.278-0.091-0.445-0.091c-0.295,0-0.595,0.092-0.885,0.181l-5.121,1.516H4.891c-0.569,0-1.016,0.407-1.016,0.927 v9.907c0,0.52,0.446,0.926,1.016,0.926h5.283c0.693,0.19,4.949,1.359,5.308,1.463c0.115,0.033,0.229,0.05,0.339,0.05 c0.529,0,1.083-0.422,1.098-0.84l0.002-0.673h2.189c0.569,0,1.016-0.406,1.016-0.926V7.139C20.125,6.619,19.679,6.212,19.109,6.212 z M12.057,16.962c-0.794-0.228-1.414-0.405-1.485-0.425l-5.258-0.004V7.65h5.225l4.972-1.524l0.006,11.824 C14.793,17.746,13.233,17.3,12.057,16.962z M18.687,16.533h-1.763l0.023-8.883h1.739V16.533z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M 8.208 9.5 H 9.646 V 14.5 H 8.208 V 9.5 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M 11.396 9.5 H 12.834 V 14.5 H 11.396 V 9.5 Z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_back_last.xml
Normal file
11
app/src/main/res/drawable/ic_back_last.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="64.063"
|
||||
android:viewportHeight="64">
|
||||
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M54.054,49.348H24.72c-3.033,0-5.5-2.466-5.5-5.499V18.94l5.12,5.121c0.358,0.358,0.828,0.537,1.296,0.537 c0.469,0,0.938-0.179,1.297-0.537c0.715-0.716,0.715-1.877,0-2.592l-8.25-8.25c-0.716-0.716-1.876-0.716-2.592,0l-8.25,8.25 c-0.715,0.715-0.715,1.876,0,2.592c0.717,0.716,1.877,0.716,2.593,0l5.12-5.121v24.908c0,5.054,4.113,9.166,9.167,9.166h29.333 c1.014,0,1.833-0.819,1.833-1.833S55.067,49.348,54.054,49.348L54.054,49.348z"/>
|
||||
</vector>
|
29
app/src/main/res/drawable/ic_backup.xml
Normal file
29
app/src/main/res/drawable/ic_backup.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M15.923,11.848 C13.675,11.848,11.846,13.678,11.846,15.924 C11.846,18.172,13.675,20,15.923,20 S20,18.172,20,15.924 C20,13.678,18.171,11.848,15.923,11.848 Z M15.923,18.713 C14.385,18.713,13.133,17.461,13.133,15.924 C13.133,14.385,14.385,13.135,15.923,13.135 C17.461,13.135,18.713,14.385,18.713,15.924 C18.713,17.461,17.461,18.713,15.923,18.713 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M16.567,13.961 L15.279,13.961 L15.279,16.568 L17.886,16.568 L17.886,15.279 L16.567,15.279 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M5.287,5.623 C5.287,5.439,5.439,5.289,5.625,5.289 L16.412,5.289 C16.599,5.289,16.751,5.439,16.751,5.623 L16.751,11.019 L18.038,11.019 L18.038,5.623 C18.038,4.729,17.309,4,16.412,4 L5.625,4 C4.73,4,4,4.729,4,5.623 L4,18.375 C4,19.273,4.73,20,5.625,20 L11.018,20 L11.018,18.713 L5.625,18.713 C5.439,18.713,5.287,18.561,5.287,18.375 L5.287,18.039 L11.018,18.039 L11.018,16.752 L5.287,16.752 L5.287,5.623 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M5.96,6.115 L7.247,6.115 L7.247,7.095 L5.96,7.095 L5.96,6.115 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M14.788,6.115 L16.075,6.115 L16.075,7.095 L14.788,7.095 L14.788,6.115 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M5.96,14.943 L7.247,14.943 L7.247,15.923 L5.96,15.923 L5.96,14.943 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M11.474,11.475 L13.387,9.561 C13.65,9.987,13.809,10.483,13.809,11.02 L15.096,11.02 C15.096,10.127,14.799,9.308,14.311,8.636 L14.908,8.039 L13.997,7.131 L13.4,7.727 C12.728,7.24,11.909,6.944,11.018,6.944 C8.77,6.944,6.941,8.772,6.941,11.02 S8.77,15.096,11.018,15.096 L11.018,13.807 C9.48,13.807,8.228,12.557,8.228,11.02 C8.228,9.479,9.479,8.229,11.018,8.229 C11.554,8.229,12.05,8.388,12.476,8.651 L10.563,10.563 L11.474,11.475 Z"/>
|
||||
</vector>
|
14
app/src/main/res/drawable/ic_book_has.xml
Normal file
14
app/src/main/res/drawable/ic_book_has.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="680.31"
|
||||
android:viewportWidth="680.31">
|
||||
|
||||
<path
|
||||
android:fillColor="#2C2C2C"
|
||||
android:pathData="M339.865,79.038c-147.014,0-266.518,119.198-266.518,266.519 c0,147.011,119.198,266.519,266.518,266.519c147.013,0,266.52-119.201,266.52-266.519 C606.078,198.542,486.878,79.038,339.865,79.038L339.865,79.038z M339.865,587.929c-133.871,0-242.373-108.505-242.373-242.373 c0-133.872,108.502-242.066,242.373-242.066c133.868,0,242.372,108.5,242.372,242.373 C582.237,479.731,473.432,587.929,339.865,587.929L339.865,587.929z M339.865,587.929"/>
|
||||
<path
|
||||
android:fillColor="#2C2C2C"
|
||||
android:pathData="M305.329,491.346c20.782,0,28.118,33.619,28.118,33.619h12.836c0,0,7.337-33.619,28.12-33.619h133.869 c7.95,0,14.673-6.725,14.673-14.671V242.25c0-33.316-33.316-33.316-33.316-33.316H361.261c-10.697,0-21.396,13.449-21.396,20.479 c0-7.03-10.391-20.479-21.088-20.479H190.103c0,0-33.317,0-33.317,33.316v234.426c0,8.253,6.418,14.671,14.67,14.671H305.329z M348.117,236.75c0-6.115,4.89-11.31,11.005-11.31h139.065c3.973,0,7.336,3.361,7.336,7.64l0.607,235.647 c0,4.279-3.36,7.644-7.333,7.644H372.873c-14.06,0-24.756,15.589-24.756,15.589V236.75z M173.902,233.08 c0-4.279,3.364-7.64,7.336-7.64h139.066c6.112,0,11.003,4.891,11.003,11.31v254.903c0,0-11.003-15.589-24.759-15.589H180.627 c-3.974,0-7.337-3.36-7.337-7.639L173.902,233.08z M173.902,233.08"/>
|
||||
</vector>
|
15
app/src/main/res/drawable/ic_book_last.xml
Normal file
15
app/src/main/res/drawable/ic_book_last.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,12m-0.756,0a0.756,0.756 0,1 1,1.512 0a0.756,0.756 0,1 1,-1.512 0"
|
||||
android:fillColor="#4A4B4A"/>
|
||||
<path
|
||||
android:pathData="M7.038,16.963l6.94,-2.985l2.984,-6.939l-6.938,2.985L7.038,16.963zM14.195,9.804l-1.321,3.071l-3.069,1.322l1.321,-3.07L14.195,9.804z"
|
||||
android:fillColor="#4A4B4A"/>
|
||||
<path
|
||||
android:pathData="M12,3c-4.963,0 -9,4.037 -9,9s4.037,9 9,9s9,-4.037 9,-9S16.963,3 12,3zM12,19.419c-4.091,0 -7.419,-3.328 -7.419,-7.419S7.909,4.581 12,4.581S19.419,7.909 19.419,12S16.091,19.419 12,19.419z"
|
||||
android:fillColor="#4A4B4A"/>
|
||||
</vector>
|
20
app/src/main/res/drawable/ic_book_source_manage.xml
Normal file
20
app/src/main/res/drawable/ic_book_source_manage.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,7.046 L4,7.046 L4,5.522 L20,5.522 L20,7.046 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,10.855 L4,10.855 L4,9.334 L20,9.334 L20,10.855 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,14.666 L4,14.666 L4,13.145 L20,13.145 L20,14.666 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,18.477 L4,18.477 L4,16.954 L20,16.954 L20,18.477 Z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_bookmark.xml
Normal file
11
app/src/main/res/drawable/ic_bookmark.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M5.938,4v16h1.414l4.648-2.789L16.649,20h1.411V4H5.938z M16.606,18.278l-4.605-2.763l-4.609,2.764 V5.454h9.215V18.278z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_brightness.xml
Normal file
11
app/src/main/res/drawable/ic_brightness.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M12,20 C7.59,20,4,16.412,4,12 S7.59,4,12,4 L12.848,4 L12.698,4.835 C12.693,4.862,12.221,7.656,13.793,9.529 C14.782,10.711,16.402,11.306,18.606,11.307 L18.606,11.307 C18.816,11.307,19.032,11.304,19.255,11.29 L20,11.256 L20,12 C20,16.412,16.411,20,12,20 Z M11.215,5.463 C7.955,5.854,5.418,8.635,5.418,12 C5.418,15.631,8.371,18.583,12,18.583 C15.384,18.583,18.18,16.015,18.543,12.727 C15.925,12.713,13.958,11.943,12.698,10.43 C11.305,8.762,11.165,6.641,11.215,5.463 Z"/>
|
||||
</vector>
|
14
app/src/main/res/drawable/ic_cancel.xml
Normal file
14
app/src/main/res/drawable/ic_cancel.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M12,4c-4.412,0-8,3.588-8,8s3.588,8,8,8c4.41,0,8-3.588,8-8S16.41,4,12,4z M12,18.545 c-3.608,0-6.546-2.936-6.546-6.545S8.392,5.455,12,5.455c3.607,0,6.545,2.936,6.545,6.545S15.607,18.545,12,18.545z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M 13.426 9.546 L 12.002 10.972 L 10.576 9.546 L 9.547 10.575 L 10.973 12 L 9.547 13.425 L 10.576 14.454 L 12.002 13.028 L 13.426 14.454 L 14.454 13.425 L 13.029 12 L 14.454 10.575 Z"/>
|
||||
</vector>
|
20
app/src/main/res/drawable/ic_chapter_list.xml
Normal file
20
app/src/main/res/drawable/ic_chapter_list.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,7.046H4V5.522h16V7.046z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,10.855H4V9.334h16V10.855z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,14.666H4v-1.521h16V14.666z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,18.477H4v-1.523h16V18.477z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_check.xml
Normal file
9
app/src/main/res/drawable/ic_check.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
14
app/src/main/res/drawable/ic_check_source.xml
Normal file
14
app/src/main/res/drawable/ic_check_source.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M20,6.182h-1.454v1.249C17.067,5.315,14.646,4,12,4c-4.412,0-8,3.589-8,8s3.588,8,8,8 c3.789,0,7.086-2.691,7.84-6.401l-1.426-0.289C17.798,16.344,15.1,18.546,12,18.546c-3.608,0-6.546-2.937-6.546-6.546 S8.392,5.454,12,5.454c2.399,0,4.576,1.32,5.721,3.395h-1.842v1.454H20V6.182z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M11.273,8.363c-1.604,0-2.909,1.305-2.909,2.91c0,1.604,1.305,2.909,2.909,2.909 c0.537,0,1.035-0.157,1.468-0.412l2.14,2.139l1.027-1.029l-2.139-2.139c0.256-0.432,0.412-0.93,0.412-1.468 C14.182,9.668,12.877,8.363,11.273,8.363z M9.818,11.273c0-0.802,0.652-1.455,1.455-1.455c0.801,0,1.453,0.653,1.453,1.455 c0,0.801-0.652,1.454-1.453,1.454C10.471,12.727,9.818,12.074,9.818,11.273z"/>
|
||||
</vector>
|
17
app/src/main/res/drawable/ic_copy.xml
Normal file
17
app/src/main/res/drawable/ic_copy.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M 18.303 8.091 L 14.939 8.091 L 14.939 4.727 L 15.848 4.727 L 15.848 7.182 L 18.303 7.182 Z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M19.03,18.061H6.91V4h8.785l3.335,3.335V18.061z M8.363,16.606h9.213V7.938l-2.483-2.484H8.363 V16.606z"/>
|
||||
<path
|
||||
android:fillColor="#595757"
|
||||
android:pathData="M 17.09 20 L 4.969 20 L 4.969 5.939 L 7.636 5.939 L 7.636 7.394 L 6.424 7.394 L 6.424 18.546 L 15.636 18.546 L 15.636 17.333 L 17.09 17.333 Z"/>
|
||||
</vector>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user