commit
d5e7395807
@ -1,5 +0,0 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="app_name">閲讀.D</string> |
||||
<string name="receiving_shared_label">閲讀·D·搜索</string> |
||||
</resources> |
@ -1,5 +0,0 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="app_name">閲讀.D</string> |
||||
<string name="receiving_shared_label">閲讀·D·搜尋</string> |
||||
</resources> |
@ -0,0 +1,17 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" |
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> |
||||
<html xmlns="http://www.w3.org/1999/xhtml"> |
||||
<head> |
||||
<title>Chapter</title> |
||||
<link href="../Styles/fonts.css" type="text/css" rel="stylesheet"/> |
||||
<link href="../Styles/main.css" type="text/css" rel="stylesheet"/> |
||||
</head> |
||||
<body> |
||||
<div class="logo"> |
||||
<img alt="" class="logo" src="../Images/logo.png"/> |
||||
</div> |
||||
<h2 class="head">{title}</h2> |
||||
{content} |
||||
</body> |
||||
</html> |
@ -0,0 +1,22 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" |
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> |
||||
<html xmlns="http://www.w3.org/1999/xhtml"> |
||||
<head> |
||||
<title>Cover</title> |
||||
<style type="text/css"> |
||||
.pic { |
||||
margin: 50% 30% 0 30%; |
||||
padding: 2px 2px; |
||||
border: 1px solid #f5f5dc; |
||||
background-color: rgba(250,250,250, 0); |
||||
border-radius: 1px; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body style="text-align: center;"> |
||||
<div class="pic"><img src="../Images/cover.jpg" style="width: 100%; height: auto;"/></div> |
||||
<h1 style="margin-top: 5%; font-size: 110%;">{name}</h1> |
||||
<div class="author" style="margin-top: 0;"><b>{author}</b> <span style="font-size: smaller;">/ 著</span></div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,267 @@ |
||||
@charset "utf-8"; |
||||
/*---常用---*/ |
||||
|
||||
@font-face { |
||||
font-family: "zw"; |
||||
src: |
||||
local("宋体"),local("明体"),local("明朝"), |
||||
local("Songti"),local("Songti SC"),local("Songti TC"), /*iOS6+iBooks3*/ |
||||
local("Song S"),local("Song T"),local("STBShusong"),local("TBMincho"),local("HYMyeongJo"), /*Kindle Paperwihite*/ |
||||
local("DK-SONGTI"), |
||||
url(../Fonts/zw.ttf), |
||||
url(res:///opt/sony/ebook/FONT/zw.ttf), |
||||
url(res:///Data/FONT/zw.ttf), |
||||
url(res:///opt/sony/ebook/FONT/tt0011m_.ttf), |
||||
url(res:///ebook/fonts/../../mnt/sdcard/fonts/zw.ttf), |
||||
url(res:///ebook/fonts/../../mnt/extsd/fonts/zw.ttf), |
||||
url(res:///ebook/fonts/zw.ttf), |
||||
url(res:///ebook/fonts/DroidSansFallback.ttf), |
||||
url(res:///fonts/ttf/zw.ttf), |
||||
url(res:///../../media/mmcblk0p1/fonts/zw.ttf), |
||||
url(file:///mnt/us/DK_System/system/fonts/zw.ttf), /*Duokan Old Path*/ |
||||
url(file:///mnt/us/DK_System/xKindle/res/userfonts/zw.ttf), /*Duokan 2012 Path*/ |
||||
url(res:///abook/fonts/zw.ttf), |
||||
url(res:///system/fonts/zw.ttf), |
||||
url(res:///system/media/sdcard/fonts/zw.ttf), |
||||
url(res:///media/fonts/zw.ttf), |
||||
url(res:///sdcard/fonts/zw.ttf), |
||||
url(res:///system/fonts/DroidSansFallback.ttf), |
||||
url(res:///mnt/MOVIFAT/font/zw.ttf), |
||||
url(res:///media/flash/fonts/zw.ttf), |
||||
url(res:///media/sd/fonts/zw.ttf), |
||||
url(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf), |
||||
url(res:///../../fonts/zw.ttf), |
||||
url(res:///../fonts/zw.ttf), |
||||
url(../../../../../zw.ttf), /*EpubReaderI*/ |
||||
url(res:///mnt/sdcard/fonts/zw.ttf), /*Nook for Android: fonts in TF Card*/ |
||||
url(res:///fonts/zw.ttf), /*ADE1,8, 2.0 Program Path*/ |
||||
url(res:///../../../../Windows/fonts/zw.ttf); |
||||
/*ADE1,8, 2.0 Windows Path*/; |
||||
} |
||||
|
||||
@font-face { |
||||
font-family: "fs"; |
||||
src: |
||||
local("amasis30"),local("仿宋"),local("仿宋_GB2312"), |
||||
local("Yuanti"),local("Yuanti SC"),local("Yuanti TC"), /*iOS6+iBooks3*/ |
||||
local("DK-FANGSONG"), |
||||
url(../Fonts/fs.ttf), |
||||
url(res:///opt/sony/ebook/FONT/fs.ttf), |
||||
url(res:///Data/FONT/fs.ttf), |
||||
url(res:///opt/sony/ebook/FONT/tt0011m_.ttf), |
||||
url(res:///ebook/fonts/../../mnt/sdcard/fonts/fs.ttf), |
||||
url(res:///ebook/fonts/../../mnt/extsd/fonts/fs.ttf), |
||||
url(res:///ebook/fonts/fs.ttf), |
||||
url(res:///ebook/fonts/DroidSansFallback.ttf), |
||||
url(res:///fonts/ttf/fs.ttf), |
||||
url(res:///../../media/mmcblk0p1/fonts/fs.ttf), |
||||
url(file:///mnt/us/DK_System/system/fonts/fs.ttf), /*Duokan Old Path*/ |
||||
url(file:///mnt/us/DK_System/xKindle/res/userfonts/fs.ttf), /*Duokan 2012 Path*/ |
||||
url(res:///abook/fonts/fs.ttf), |
||||
url(res:///system/fonts/fs.ttf), |
||||
url(res:///system/media/sdcard/fonts/fs.ttf), |
||||
url(res:///media/fonts/fs.ttf), |
||||
url(res:///sdcard/fonts/fs.ttf), |
||||
url(res:///system/fonts/DroidSansFallback.ttf), |
||||
url(res:///mnt/MOVIFAT/font/fs.ttf), |
||||
url(res:///media/flash/fonts/fs.ttf), |
||||
url(res:///media/sd/fonts/fs.ttf), |
||||
url(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf), |
||||
url(res:///../../fonts/fs.ttf), |
||||
url(res:///../fonts/fs.ttf), |
||||
url(../../../../../fs.ttf), /*EpubReaderI*/ |
||||
url(res:///mnt/sdcard/fonts/fs.ttf), /*Nook for Android: fonts in TF Card*/ |
||||
url(res:///fonts/fs.ttf), /*ADE1,8, 2.0 Program Path*/ |
||||
url(res:///../../../../Windows/fonts/fs.ttf); |
||||
/*ADE1,8, 2.0 Windows Path*/; |
||||
} |
||||
|
||||
@font-face { |
||||
font-family: "kt"; |
||||
src: |
||||
local("Caecilia"),local("楷体"),local("楷体_GB2312"), |
||||
local("Kaiti"),local("Kaiti SC"),local("Kaiti TC"), /*iOS6+iBooks3*/ |
||||
local("MKai PRC"),local("MKaiGB18030C-Medium"),local("MKaiGB18030C-Bold"), /*Kindle Paperwihite*/ |
||||
local("DK-KAITI"), |
||||
url(../Fonts/kt.ttf), |
||||
url(res:///opt/sony/ebook/FONT/kt.ttf), |
||||
url(res:///Data/FONT/kt.ttf), |
||||
url(res:///opt/sony/ebook/FONT/tt0011m_.ttf), |
||||
url(res:///ebook/fonts/../../mnt/sdcard/fonts/kt.ttf), |
||||
url(res:///ebook/fonts/../../mnt/extsd/fonts/kt.ttf), |
||||
url(res:///ebook/fonts/kt.ttf), |
||||
url(res:///ebook/fonts/DroidSansFallback.ttf), |
||||
url(res:///fonts/ttf/kt.ttf), |
||||
url(res:///../../media/mmcblk0p1/fonts/kt.ttf), |
||||
url(file:///mnt/us/DK_System/system/fonts/kt.ttf), /*Duokan Old Path*/ |
||||
url(file:///mnt/us/DK_System/xKindle/res/userfonts/kt.ttf), /*Duokan 2012 Path*/ |
||||
url(res:///abook/fonts/kt.ttf), |
||||
url(res:///system/fonts/kt.ttf), |
||||
url(res:///system/media/sdcard/fonts/kt.ttf), |
||||
url(res:///media/fonts/kt.ttf), |
||||
url(res:///sdcard/fonts/kt.ttf), |
||||
url(res:///system/fonts/DroidSansFallback.ttf), |
||||
url(res:///mnt/MOVIFAT/font/kt.ttf), |
||||
url(res:///media/flash/fonts/kt.ttf), |
||||
url(res:///media/sd/fonts/kt.ttf), |
||||
url(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf), |
||||
url(res:///../../fonts/kt.ttf), |
||||
url(res:///../fonts/kt.ttf), |
||||
url(../../../../../kt.ttf), /*EpubReaderI*/ |
||||
url(res:///mnt/sdcard/fonts/kt.ttf), /*Nook for Android: fonts in TF Card*/ |
||||
url(res:///fonts/kt.ttf), /*ADE1,8, 2.0 Program Path*/ |
||||
url(res:///../../../../Windows/fonts/kt.ttf); |
||||
/*ADE1,8, 2.0 Windows Path*/; |
||||
} |
||||
|
||||
@font-face { |
||||
font-family: "ht"; |
||||
src: |
||||
local("黑体"),local("微软雅黑"), |
||||
local("Heiti"),local("Heiti SC"),local("Heiti TC"), /*iOS6+iBooks3*/ |
||||
local("MYing Hei S"),local("MYing Hei T"),local("TBGothic"), /*Kindle Paperwihite*/ |
||||
local("DK-HEITI"), |
||||
url(../Fonts/ht.ttf), |
||||
url(res:///opt/sony/ebook/FONT/ht.ttf), |
||||
url(res:///Data/FONT/ht.ttf), |
||||
url(res:///opt/sony/ebook/FONT/tt0011m_.ttf), |
||||
url(res:///ebook/fonts/../../mnt/sdcard/fonts/ht.ttf), |
||||
url(res:///ebook/fonts/../../mnt/extsd/fonts/ht.ttf), |
||||
url(res:///ebook/fonts/ht.ttf), |
||||
url(res:///ebook/fonts/DroidSansFallback.ttf), |
||||
url(res:///fonts/ttf/ht.ttf), |
||||
url(res:///../../media/mmcblk0p1/fonts/ht.ttf), |
||||
url(file:///mnt/us/DK_System/system/fonts/ht.ttf), /*Duokan Old Path*/ |
||||
url(file:///mnt/us/DK_System/xKindle/res/userfonts/ht.ttf), /*Duokan 2012 Path*/ |
||||
url(res:///abook/fonts/ht.ttf), |
||||
url(res:///system/fonts/ht.ttf), |
||||
url(res:///system/media/sdcard/fonts/ht.ttf), |
||||
url(res:///media/fonts/ht.ttf), |
||||
url(res:///sdcard/fonts/ht.ttf), |
||||
url(res:///system/fonts/DroidSansFallback.ttf), |
||||
url(res:///mnt/MOVIFAT/font/ht.ttf), |
||||
url(res:///media/flash/fonts/ht.ttf), |
||||
url(res:///media/sd/fonts/ht.ttf), |
||||
url(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf), |
||||
url(res:///../../fonts/ht.ttf), |
||||
url(res:///../fonts/ht.ttf), |
||||
url(../../../../../ht.ttf), /*EpubReaderI*/ |
||||
url(res:///mnt/sdcard/fonts/ht.ttf), /*Nook for Android: fonts in TF Card*/ |
||||
url(res:///fonts/ht.ttf), /*ADE1,8, 2.0 Program Path*/ |
||||
url(res:///../../../../Windows/fonts/ht.ttf); |
||||
/*ADE1,8, 2.0 Windows Path*/; |
||||
} |
||||
@font-face { |
||||
font-family:"h1"; |
||||
src: |
||||
local("方正兰亭特黑长_GBK"),local("方正兰亭特黑长简体"),local("方正兰亭特黑长繁体"), |
||||
local("LantingTeheichang"), |
||||
local("Yuanti"),local("Yuanti SC"),local("Yuanti TC"), |
||||
local("DK-HEITI"), |
||||
url(../Fonts/h1.ttf), |
||||
url(res:///opt/sony/ebook/FONT/h1.ttf), |
||||
url(res:///Data/FONT/h1.ttf), |
||||
url(res:///opt/sony/ebook/FONT/tt0011m_.ttf), |
||||
url(res:///ebook/fonts/../../mnt/sdcard/fonts/h1.ttf), |
||||
url(res:///ebook/fonts/../../mnt/extsd/fonts/h1.ttf), |
||||
url(res:///ebook/fonts/h1.ttf), |
||||
url(res:///ebook/fonts/DroidSansFallback.ttf), |
||||
url(res:///fonts/ttf/h1.ttf), |
||||
url(res:///../../media/mmcblk0p1/fonts/h1.ttf), |
||||
url(file:///mnt/us/DK_System/system/fonts/h1.ttf), /*Duokan Old Path*/ |
||||
url(file:///mnt/us/DK_System/xKindle/res/userfonts/h1.ttf), /*Duokan 2012 Path*/ |
||||
url(res:///abook/fonts/h1.ttf), |
||||
url(res:///system/fonts/h1.ttf), |
||||
url(res:///system/media/sdcard/fonts/h1.ttf), |
||||
url(res:///media/fonts/h1.ttf), |
||||
url(res:///sdcard/fonts/h1.ttf), |
||||
url(res:///system/fonts/DroidSansFallback.ttf), |
||||
url(res:///mnt/MOVIFAT/font/h1.ttf), |
||||
url(res:///media/flash/fonts/h1.ttf), |
||||
url(res:///media/sd/fonts/h1.ttf), |
||||
url(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf), |
||||
url(res:///../../fonts/h1.ttf), |
||||
url(res:///../fonts/h1.ttf), |
||||
url(../../../../../h1.ttf), /*EpubReaderI*/ |
||||
url(res:///mnt/sdcard/fonts/h1.ttf), /*Nook for Android: fonts in TF Card*/ |
||||
url(res:///fonts/h1.ttf), /*ADE1,8, 2.0 Program Path*/ |
||||
url(res:///../../../../Windows/fonts/h1.ttf); /*ADE1,8, 2.0 Windows Path*/ |
||||
} |
||||
@font-face { |
||||
font-family:"h2"; |
||||
src: |
||||
local("方正大标宋_GBK"),local("方正大标宋简体"),local("方正大标宋繁体"), |
||||
local("Dabiaosong"), |
||||
local("Heiti"),local("Heiti SC"),local("Heiti TC"), |
||||
local("DK-XIAOBIAOSONG"), |
||||
url(../Fonts/h2.ttf), |
||||
url(res:///opt/sony/ebook/FONT/h2.ttf), |
||||
url(res:///Data/FONT/h2.ttf), |
||||
url(res:///opt/sony/ebook/FONT/tt0011m_.ttf), |
||||
url(res:///ebook/fonts/../../mnt/sdcard/fonts/h2.ttf), |
||||
url(res:///ebook/fonts/../../mnt/extsd/fonts/h2.ttf), |
||||
url(res:///ebook/fonts/h2.ttf), |
||||
url(res:///ebook/fonts/DroidSansFallback.ttf), |
||||
url(res:///fonts/ttf/h2.ttf), |
||||
url(res:///../../media/mmcblk0p1/fonts/h2.ttf), |
||||
url(file:///mnt/us/DK_System/system/fonts/h2.ttf), /*Duokan Old Path*/ |
||||
url(file:///mnt/us/DK_System/xKindle/res/userfonts/h2.ttf), /*Duokan 2012 Path*/ |
||||
url(res:///abook/fonts/h2.ttf), |
||||
url(res:///system/fonts/h2.ttf), |
||||
url(res:///system/media/sdcard/fonts/h2.ttf), |
||||
url(res:///media/fonts/h2.ttf), |
||||
url(res:///sdcard/fonts/h2.ttf), |
||||
url(res:///system/fonts/DroidSansFallback.ttf), |
||||
url(res:///mnt/MOVIFAT/font/h2.ttf), |
||||
url(res:///media/flash/fonts/h2.ttf), |
||||
url(res:///media/sd/fonts/h2.ttf), |
||||
url(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf), |
||||
url(res:///../../fonts/h2.ttf), |
||||
url(res:///../fonts/h2.ttf), |
||||
url(../../../../../h2.ttf), /*EpubReaderI*/ |
||||
url(res:///mnt/sdcard/fonts/h2.ttf), /*Nook for Android: fonts in TF Card*/ |
||||
url(res:///fonts/h2.ttf), /*ADE1,8, 2.0 Program Path*/ |
||||
url(res:///../../../../Windows/fonts/h2.ttf); /*ADE1,8, 2.0 Windows Path*/ |
||||
} |
||||
|
||||
@font-face { |
||||
font-family:"h3"; |
||||
src: |
||||
local("方正华隶_GBK"),local("方正行黑简体"),local("方正行黑繁体"), |
||||
local("Yuanti"),local("Yuanti SC"),local("Yuanti TC"), |
||||
local("DK-FANGSONG"), |
||||
url(../Fonts/h3.ttf), |
||||
url(res:///opt/sony/ebook/FONT/h3.ttf), |
||||
url(res:///Data/FONT/h3.ttf), |
||||
url(res:///opt/sony/ebook/FONT/tt0011m_.ttf), |
||||
url(res:///ebook/fonts/../../mnt/sdcard/fonts/h3.ttf), |
||||
url(res:///ebook/fonts/../../mnt/extsd/fonts/h3.ttf), |
||||
url(res:///ebook/fonts/h3.ttf), |
||||
url(res:///ebook/fonts/DroidSansFallback.ttf), |
||||
url(res:///fonts/ttf/h3.ttf), |
||||
url(res:///../../media/mmcblk0p1/fonts/h3.ttf), |
||||
url(file:///mnt/us/DK_System/system/fonts/h3.ttf), /*Duokan Old Path*/ |
||||
url(file:///mnt/us/DK_System/xKindle/res/userfonts/h3.ttf), /*Duokan 2012 Path*/ |
||||
url(res:///abook/fonts/h3.ttf), |
||||
url(res:///system/fonts/h3.ttf), |
||||
url(res:///system/media/sdcard/fonts/h3.ttf), |
||||
url(res:///media/fonts/h3.ttf), |
||||
url(res:///sdcard/fonts/h3.ttf), |
||||
url(res:///system/fonts/DroidSansFallback.ttf), |
||||
url(res:///mnt/MOVIFAT/font/h3.ttf), |
||||
url(res:///media/flash/fonts/h3.ttf), |
||||
url(res:///media/sd/fonts/h3.ttf), |
||||
url(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf), |
||||
url(res:///../../fonts/h3.ttf), |
||||
url(res:///../fonts/h3.ttf), |
||||
url(../../../../../h3.ttf), /*EpubReaderI*/ |
||||
url(res:///mnt/sdcard/fonts/h3.ttf), /*Nook for Android: fonts in TF Card*/ |
||||
url(res:///fonts/h3.ttf), /*ADE1,8, 2.0 Program Path*/ |
||||
url(res:///../../../../Windows/fonts/h3.ttf); /*ADE1,8, 2.0 Windows Path*/ |
||||
} |
||||
|
||||
@font-face { |
||||
font-family:"luohua"; |
||||
src:local("汉仪落花体"), |
||||
url("../Fonts/hylh.ttf"); |
||||
} |
@ -0,0 +1,12 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" |
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> |
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN"> |
||||
<head> |
||||
<title>Intro</title> |
||||
<link href="../Styles/fonts.css" type="text/css" rel="stylesheet" /> |
||||
<link href="../Styles/main.css" type="text/css" rel="stylesheet" /> |
||||
</head> |
||||
<body> |
||||
<h1 class="head" style="margin-bottom:2em;">内容简介</h1>{intro}</body> |
||||
</html> |
After Width: | Height: | Size: 92 KiB |
@ -0,0 +1,551 @@ |
||||
@charset "utf-8"; |
||||
@import url("../Styles/fonts.css"); |
||||
body { |
||||
padding: 0%; |
||||
margin-top: 0%; |
||||
margin-bottom: 0%; |
||||
margin-left: 0.5%; |
||||
margin-right: 0.5%; |
||||
line-height: 130%; |
||||
text-align: justify; |
||||
font-family: "DK-SONGTI","st","宋体","zw",sans-serif; |
||||
} |
||||
|
||||
p { |
||||
text-align: justify; |
||||
text-indent: 2em; |
||||
line-height: 130%; |
||||
margin-right: 0.5%; |
||||
margin-left: 0.5%; |
||||
font-family: "DK-SONGTI","st","宋体","zw",sans-serif; |
||||
} |
||||
p.kaiti { |
||||
font-family: "DK-KAITI","kt","楷体","zw",serif; |
||||
} |
||||
|
||||
p.fangsong { |
||||
font-family: "DK-FANGSONG","fs","仿宋","zw",serif; |
||||
} |
||||
|
||||
span.xinli { |
||||
font-family: "DK-KAITI","kt","楷体","zw",serif; |
||||
color: #4e753f; |
||||
} |
||||
/** 英文斜体字 **/ |
||||
span.english{ |
||||
font-style: italic; |
||||
} |
||||
div { |
||||
margin: 0px; |
||||
padding: 0px; |
||||
line-height: 120%; |
||||
text-align: justify; |
||||
font-family: "zw"; |
||||
} |
||||
div.foot { |
||||
text-indent: 2em; |
||||
margin: 30% 5% 0 5%; |
||||
padding: 8px 0; |
||||
} |
||||
p.foot { |
||||
font-family: "DK-KAITI","kt","楷体","zw",serif; |
||||
} |
||||
|
||||
/*扉页*/ |
||||
.booksubtitle { |
||||
padding: 10px 0 0px 0; |
||||
text-indent: 0em; |
||||
font-size: 75%; |
||||
font-family: "ht"; |
||||
} |
||||
|
||||
.booktitle { |
||||
padding: 9% 0 0 0; |
||||
font-size: 1.3em; |
||||
font-family: "方正小标宋_GBK","DK-XIAOBIAOSONG"; |
||||
font-weight: normal; |
||||
text-indent: 0em; |
||||
color: #000; |
||||
text-align: center; |
||||
line-height: 1.6; |
||||
} |
||||
|
||||
.booktitle0 { |
||||
font-size: 1.2em; |
||||
font-family: "fs"; |
||||
text-indent: 0em; |
||||
text-align: center; |
||||
line-height: 1.8; |
||||
} |
||||
|
||||
.booktitle1 { |
||||
padding: 0 0 0 0; |
||||
font-size: 0.85em; |
||||
font-family: "fs"; |
||||
text-indent: 0em; |
||||
text-align: center; |
||||
line-height: 1.6; |
||||
} |
||||
|
||||
.bookauthor { |
||||
font-family: "DK-FANGSONG",仿宋,"fs","fangsong",sans-serif; |
||||
padding: 5% 5px 0px 5px; |
||||
text-indent: 0em; |
||||
text-align: center; |
||||
color: #000; |
||||
font-size: 90%; |
||||
line-height: 1.3; |
||||
} |
||||
|
||||
.booktranslator { |
||||
padding: 1% 5px 0px 5px; |
||||
text-indent: 0em; |
||||
text-align: center; |
||||
font-size: 85%; |
||||
line-height: 1.3; |
||||
} |
||||
|
||||
.bookpub { |
||||
font-family: "DK-KAITI","kt","楷体","楷体_gb2312"; |
||||
padding: 30% 5px 5px 5px; |
||||
text-indent: 0em; |
||||
color: #000; |
||||
text-align: center; |
||||
font-size: 80%; |
||||
} |
||||
|
||||
/*标题页*/ |
||||
body.head { |
||||
background-repeat:no-repeat no-repeat; |
||||
background-size:160px 229px; |
||||
background-position:bottom right; |
||||
background-attachment:fixed; |
||||
} |
||||
|
||||
body.xhead { |
||||
background-color: #FDF5E6; |
||||
} |
||||
|
||||
h1.head { |
||||
font-family: "DK-HEITI",黑体,sans-serif; |
||||
font-size: 1.2em; |
||||
font-weight: bold; |
||||
color: #311a02; |
||||
text-indent: 0em; |
||||
font-weight: normal; |
||||
duokan-text-indent: 0em; |
||||
padding: auto; |
||||
text-align: center; |
||||
margin-top: -8em; |
||||
} |
||||
|
||||
div.head { |
||||
border: solid 2px #ffffff; |
||||
padding: 2px; |
||||
margin: 2em auto 0.7em auto; |
||||
text-align: center; |
||||
width: 1em; |
||||
} |
||||
|
||||
h1.head b { |
||||
font-family: "方正小标宋_GBK","DK-XIAOBIAOSONG"; |
||||
font-weight: bold; |
||||
font-size: 1.2em; |
||||
text-align: center; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
color: #311a02; |
||||
margin: 0.5em auto; |
||||
line-height: 140%; |
||||
} |
||||
|
||||
div.back { |
||||
text-align: center; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
margin: 4em auto; |
||||
} |
||||
|
||||
img.back { |
||||
width: 70%; |
||||
} |
||||
img.back2 { |
||||
width: 40%; |
||||
margin: 2em 0 0 0; |
||||
} |
||||
/*正文*/ |
||||
/**楷体引文**/ |
||||
.titou { |
||||
font-family: "DK-FANGSONG",仿宋,"fs","fangsong",sans-serif; |
||||
} |
||||
.yinwen { |
||||
font-family: "DK-KAITI","kt","楷体","zw",serif; |
||||
margin-left: 2em; |
||||
text-indent: 0em; |
||||
} |
||||
.nicename { |
||||
font-family: "DK-HEITI",黑体,sans-serif; |
||||
font-weight: bold; |
||||
font-size: 0.9em; |
||||
} |
||||
body.head3 { |
||||
background-color: #a7bdcc; |
||||
color: #354f66; |
||||
} |
||||
|
||||
body.head4 { |
||||
background-color: #bfd19b; |
||||
color: #4e753f; |
||||
} |
||||
|
||||
h2.head { |
||||
font-family: "小标宋"; |
||||
text-align: left; |
||||
font-weight: bold; |
||||
font-size: 1.1em; |
||||
margin: -3em 2em 2em 0; |
||||
color: #3f83e8; |
||||
line-height: 140%; |
||||
} |
||||
|
||||
h2.head span { |
||||
font-family: "仿宋"; |
||||
font-size: 0.7em; |
||||
background-color: #3f83e8; |
||||
border-radius: 9px; |
||||
padding: 4px; |
||||
color: #fff; |
||||
} |
||||
|
||||
|
||||
div.logo { |
||||
margin: -2em 0% 0 0; |
||||
text-align: right; |
||||
} |
||||
|
||||
img.logo { |
||||
width: 40%; |
||||
} |
||||
.imgl { |
||||
/*图片居右*/ |
||||
margin: -8.8em 1em 4em 0em; |
||||
width: 80%; |
||||
text-align: right; |
||||
} |
||||
|
||||
h1.head { |
||||
line-height:130%; |
||||
font-size:1.4em; |
||||
text-align: center; |
||||
color: #BA2213; |
||||
font-weight: bold; |
||||
margin-top: 2em; |
||||
margin-bottom: 1em; |
||||
font-family: "方正小标宋_GBK","DK-XIAOBIAOSONG"; |
||||
|
||||
} |
||||
h3 { |
||||
font-family: "DK-HEITI",黑体,sans-serif; |
||||
font-size: 1.1em; |
||||
margin: 1em 0; |
||||
border-left: 1.2em solid #00a1e9; |
||||
line-height: 120%; |
||||
padding-left: 3px; |
||||
color: #00a1e9; |
||||
} |
||||
h4 { |
||||
font-family: "DK-HEITI",黑体,sans-serif; |
||||
font-size: 1.1em; |
||||
text-align: center; |
||||
margin: 1em 0; |
||||
line-height: 120%; |
||||
color: #000; |
||||
} |
||||
h1.post { |
||||
font-family: "方正小标宋_GBK","DK-XIAOBIAOSONG"; |
||||
text-align: center; |
||||
font-size: 1.3em; |
||||
color: #026fca; |
||||
margin: 3em auto 2em auto; |
||||
} |
||||
.banquan { |
||||
font-family: "DK-FANGSONG",仿宋,"fs","fangsong",sans-serif; |
||||
text-align: left; |
||||
color: #000; |
||||
font-size:1.1em; |
||||
margin-bottom:1em; |
||||
text-indent: 1em; |
||||
duokan-text-indent: 1em; |
||||
} |
||||
p.post { |
||||
font-family: "DK-FANGSONG",仿宋,"fs","fangsong",sans-serif; |
||||
} |
||||
p.zy { |
||||
font-family: "DK-FANGSONG",仿宋,"fs","fangsong",sans-serif; |
||||
margin: 1em 0 0 1em; |
||||
padding: 5px 0px 5px 10px; |
||||
text-indent: 0em; |
||||
border-left: 5px solid #a9b5c1; |
||||
} |
||||
.sign { |
||||
font-family: "DK-KAITI","kt","楷体","zw",serif; |
||||
margin: 1em 2px 0 auto; |
||||
text-align: right; |
||||
font-size: 0.8em; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
} |
||||
|
||||
.mark { |
||||
font-family: "DK-HEITI",黑体,sans-serif; |
||||
font-size: 0.9em; |
||||
color: #fff; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
background-color: maroon; |
||||
text-align: center; |
||||
padding: 0px; |
||||
margin: 2em 30%; |
||||
} |
||||
|
||||
/*出版社*/ |
||||
.chubanshe img{ |
||||
width:106px; |
||||
height:28px; |
||||
} |
||||
.chubanshe { |
||||
margin-top:20px; |
||||
} |
||||
.cr { |
||||
font-size:0.9em; |
||||
} |
||||
|
||||
/*多看画廊*/ |
||||
div.duokan-image-single { |
||||
text-align: center; |
||||
margin: 0.5em auto; /*插图盒子上下外边距为0.5em,左右设置auto是为了水平居中这个盒子*/ |
||||
} |
||||
img.picture-80 { |
||||
margin: 0; /*清除img元素的外边距*/ |
||||
width: 80%; /*预览窗口的宽度*/ |
||||
box-shadow: 3px 3px 10px #bfbfbf; /*给图片添加阴影效果*/ |
||||
} |
||||
p.duokan-image-maintitle { |
||||
margin: 1em 0 0; /*图片说明的段间距*/ |
||||
font-family: "楷体"; /*图片说明使用的字体*/ |
||||
font-size: 0.9em; /*字体大小*/ |
||||
text-indent: 0; /*首行缩进为零,当你使用单标签p来指定首行缩进为2em时,记得在需要居中的文本中清除缩进,因为样式是叠加的*/ |
||||
text-align: center; /*图片说明水平居中*/ |
||||
color: #a52a2a; /*字体颜色*/ |
||||
line-height: 1.25em; /*行高,防止有很长的图片说明*/ |
||||
} |
||||
|
||||
|
||||
/*制作说明页*/ |
||||
body.description { |
||||
background-image: url(../Images/001.png); |
||||
background-position: bottom center; |
||||
background-repeat: no-repeat; |
||||
background-size: cover; |
||||
padding: 25% 10% 0; |
||||
font-size: 0.9em; |
||||
} |
||||
|
||||
div.description-body { |
||||
width: 55%; |
||||
padding: 2em 1.3em; |
||||
border-radius: 0.5em; |
||||
font-size: 0.9em; |
||||
border-style: solid; |
||||
border-color: #393939; |
||||
border-width: 0.3em; |
||||
border-radius: 5em; |
||||
background-color: #5a5a5a; |
||||
box-shadow: 2px 2px 3px #828281; |
||||
} |
||||
|
||||
h1.description-title { |
||||
text-align: center; |
||||
font-family: "黑体"; |
||||
font-size: 1.2em; |
||||
margin: 0 0 1em 0; |
||||
color: #FF9; |
||||
text-shadow: 1px 1px 0 black; |
||||
} |
||||
|
||||
p.description-text { |
||||
color: #f9ddd2; |
||||
font-family: "准圆"; |
||||
margin: 0; |
||||
text-align: justify; |
||||
text-indent: 0; |
||||
duokan-text-indent: 0; |
||||
} |
||||
|
||||
hr.description-hr { |
||||
margin: 0.5em -1em; |
||||
border-style: dotted; |
||||
border-color: #9C9; |
||||
border-width: 0.05em 0 0 0; |
||||
} |
||||
|
||||
p.tips { |
||||
text-align: justify; |
||||
text-indent: 0; |
||||
duokan-text-indent: 0; |
||||
font-family: "楷体"; |
||||
font-size: 0.7em; |
||||
color: #FFC; |
||||
margin: 0; |
||||
} |
||||
|
||||
/*版本说明页*/ |
||||
.ver { |
||||
font-family: "DK-CODE","DK-XIHEITI",细黑体,"xihei",sans-serif; |
||||
font-weight: bold; |
||||
font-size: 100%; |
||||
color: #000; |
||||
margin: 1em 0 1em 0; |
||||
text-align: center; |
||||
} |
||||
|
||||
.vertitle { |
||||
font-family: "DK-FANGSONG",仿宋,"fs","fangsong",sans-serif; |
||||
font-size: 100%; |
||||
text-indent: 0em; |
||||
text-align: left; |
||||
duokan-text-indent: 0em; |
||||
} |
||||
|
||||
.vertxt { |
||||
font-family: "DK-FANGSONG",仿宋,"fs","fangsong",sans-serif; |
||||
line-height: 100%; |
||||
font-size: 85%; |
||||
text-indent: 0em; |
||||
text-align: left; |
||||
duokan-text-indent: 0em; |
||||
} |
||||
.verchar { |
||||
font-family: "DK-KAITI","kt","楷体","楷体_gb2312"; |
||||
text-align: left; |
||||
text-indent: 1em; |
||||
duokan-text-indent: 1em; |
||||
margin-bottom: 1em; |
||||
margin-top: 1em; |
||||
} |
||||
.vernote { |
||||
font-family: "DK-FANGSONG",仿宋,"fs","fangsong",sans-serif; |
||||
font-size: 75%; |
||||
color: #686d70; |
||||
text-indent: 0em; |
||||
text-align: left; |
||||
duokan-text-indent: 0em; |
||||
padding-bottom: 15px; |
||||
} |
||||
|
||||
.line { |
||||
border: dotted #A2906A; |
||||
border-width: 1px 0 0 0; |
||||
} |
||||
|
||||
.entry { |
||||
margin-left: 18px; |
||||
font-size: 83%; |
||||
color: #8fe0a3; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
} |
||||
/*版权信息*/ |
||||
.vol { |
||||
text-indent: 0em; |
||||
text-align: center; |
||||
padding: 0.8em; |
||||
margin: 0 auto 3px auto; |
||||
color: #000; |
||||
font-family: "方正小标宋_GBK","DK-XIAOBIAOSONG"; |
||||
font-size: 130%; |
||||
text-shadow: none; |
||||
} |
||||
|
||||
.cp { |
||||
font-family: "DK-CODE","DK-XIHEITI",细黑体,"xihei",sans-serif; |
||||
color: #412938; |
||||
font-size: 70%; |
||||
text-align: left; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
} |
||||
|
||||
.xchar { |
||||
font-family: "DK-KAITI","kt","楷体","楷体_gb2312"; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
} |
||||
/*多看弹注*/ |
||||
sup img { |
||||
line-height: 100%; |
||||
width: auto; |
||||
height: 1.0em; |
||||
margin: 0em; |
||||
padding: 0em; |
||||
vertical-align: text-top; |
||||
} |
||||
|
||||
ol { |
||||
margin-bottom:0; |
||||
padding:0 auto; |
||||
list-style-type: decimal; |
||||
} |
||||
.hr { |
||||
width:50%; |
||||
margin:2em 0 0 0.5em; |
||||
padding:0; |
||||
height:2px; |
||||
background-color: #F3221D; |
||||
} |
||||
|
||||
.duokan-footnote-content{ |
||||
padding:0 auto; |
||||
text-align: left; |
||||
} |
||||
|
||||
.duokan-footnote-item { |
||||
font-family:"DK-XIHEITI",细黑体,"xihei",sans-serif; |
||||
text-align: left; |
||||
font-size: 80%; |
||||
line-height: 100%; |
||||
clear: both; |
||||
color:#000; |
||||
list-style-type:decimal; |
||||
} |
||||
|
||||
li.duokan-footnote-item a { |
||||
font-family:"DK-HEITI"; |
||||
text-align: left; |
||||
} |
||||
a{ |
||||
text-decoration: none; |
||||
color: #222; |
||||
} |
||||
|
||||
a:hover {background: #81caf9} |
||||
a:active {background: yellow} |
||||
.duokan-image-maintitle { |
||||
font-family:"DK-HEITI",黑体,"hei",sans-serif; |
||||
text-align: center; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
font-size:90%; |
||||
color: #1F4150; |
||||
margin-top: 1em; |
||||
} |
||||
|
||||
.duokan-image-subtitle { |
||||
font-family:"DK-XIHEITI",细黑体,"xihei",sans-serif; |
||||
text-align: center; |
||||
text-indent: 0em; |
||||
duokan-text-indent: 0em; |
||||
font-size:70%; |
||||
color: #3A3348; |
||||
margin-top: 1em; |
||||
} |
@ -0,0 +1,140 @@ |
||||
package io.legado.app.ui.main.bookshelf |
||||
|
||||
import android.annotation.SuppressLint |
||||
import android.view.MenuItem |
||||
import androidx.fragment.app.activityViewModels |
||||
import androidx.fragment.app.viewModels |
||||
import io.legado.app.R |
||||
import io.legado.app.base.VMBaseFragment |
||||
import io.legado.app.constant.EventBus |
||||
import io.legado.app.constant.PreferKey |
||||
import io.legado.app.data.entities.Book |
||||
import io.legado.app.databinding.DialogBookshelfConfigBinding |
||||
import io.legado.app.databinding.DialogEditTextBinding |
||||
import io.legado.app.help.AppConfig |
||||
import io.legado.app.lib.dialogs.alert |
||||
import io.legado.app.ui.book.arrange.ArrangeBookActivity |
||||
import io.legado.app.ui.book.cache.CacheActivity |
||||
import io.legado.app.ui.book.group.GroupManageDialog |
||||
import io.legado.app.ui.book.local.ImportBookActivity |
||||
import io.legado.app.ui.book.search.SearchActivity |
||||
import io.legado.app.ui.document.FilePicker |
||||
import io.legado.app.ui.document.FilePickerParam |
||||
import io.legado.app.ui.main.MainViewModel |
||||
import io.legado.app.utils.* |
||||
|
||||
abstract class BaseBookshelfFragment(layoutId: Int) : VMBaseFragment<BookshelfViewModel>(layoutId) { |
||||
|
||||
val activityViewModel: MainViewModel by activityViewModels() |
||||
override val viewModel: BookshelfViewModel by viewModels() |
||||
|
||||
private val importBookshelf = registerForActivityResult(FilePicker()) { |
||||
it?.readText(requireContext())?.let { text -> |
||||
viewModel.importBookshelf(text, groupId) |
||||
} |
||||
} |
||||
|
||||
abstract val groupId: Long |
||||
abstract val books: List<Book> |
||||
|
||||
abstract fun gotoTop() |
||||
|
||||
override fun onCompatOptionsItemSelected(item: MenuItem) { |
||||
super.onCompatOptionsItemSelected(item) |
||||
when (item.itemId) { |
||||
R.id.menu_search -> startActivity<SearchActivity>() |
||||
R.id.menu_update_toc -> activityViewModel.upToc(books) |
||||
R.id.menu_bookshelf_layout -> configBookshelf() |
||||
R.id.menu_group_manage -> GroupManageDialog() |
||||
.show(childFragmentManager, "groupManageDialog") |
||||
R.id.menu_add_local -> startActivity<ImportBookActivity>() |
||||
R.id.menu_add_url -> addBookByUrl() |
||||
R.id.menu_arrange_bookshelf -> startActivity<ArrangeBookActivity> { |
||||
putExtra("groupId", groupId) |
||||
} |
||||
R.id.menu_download -> startActivity<CacheActivity> { |
||||
putExtra("groupId", groupId) |
||||
} |
||||
R.id.menu_export_bookshelf -> viewModel.exportBookshelf(books) { |
||||
activity?.share(it) |
||||
} |
||||
R.id.menu_import_bookshelf -> importBookshelfAlert(groupId) |
||||
} |
||||
} |
||||
|
||||
@SuppressLint("InflateParams") |
||||
fun addBookByUrl() { |
||||
alert(titleResource = R.string.add_book_url) { |
||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater) |
||||
customView { alertBinding.root } |
||||
okButton { |
||||
alertBinding.editView.text?.toString()?.let { |
||||
viewModel.addBookByUrl(it) |
||||
} |
||||
} |
||||
noButton() |
||||
}.show() |
||||
} |
||||
|
||||
@SuppressLint("InflateParams") |
||||
fun configBookshelf() { |
||||
alert(titleResource = R.string.bookshelf_layout) { |
||||
val bookshelfLayout = getPrefInt(PreferKey.bookshelfLayout) |
||||
val bookshelfSort = getPrefInt(PreferKey.bookshelfSort) |
||||
val alertBinding = |
||||
DialogBookshelfConfigBinding.inflate(layoutInflater) |
||||
.apply { |
||||
spGroupStyle.setSelection(AppConfig.bookGroupStyle) |
||||
rgLayout.checkByIndex(bookshelfLayout) |
||||
rgSort.checkByIndex(bookshelfSort) |
||||
} |
||||
customView { alertBinding.root } |
||||
okButton { |
||||
alertBinding.apply { |
||||
var changed = false |
||||
if (AppConfig.bookGroupStyle != spGroupStyle.selectedItemPosition) { |
||||
AppConfig.bookGroupStyle = spGroupStyle.selectedItemPosition |
||||
postEvent(EventBus.NOTIFY_MAIN, false) |
||||
} |
||||
if (bookshelfLayout != rgLayout.getCheckedIndex()) { |
||||
putPrefInt(PreferKey.bookshelfLayout, rgLayout.getCheckedIndex()) |
||||
changed = true |
||||
} |
||||
if (bookshelfSort != rgSort.getCheckedIndex()) { |
||||
putPrefInt(PreferKey.bookshelfSort, rgSort.getCheckedIndex()) |
||||
changed = true |
||||
} |
||||
if (changed) { |
||||
postEvent(EventBus.RECREATE, "") |
||||
} |
||||
} |
||||
} |
||||
noButton() |
||||
}.show() |
||||
} |
||||
|
||||
|
||||
private fun importBookshelfAlert(groupId: Long) { |
||||
alert(titleResource = R.string.import_bookshelf) { |
||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply { |
||||
editView.hint = "url/json" |
||||
} |
||||
customView { alertBinding.root } |
||||
okButton { |
||||
alertBinding.editView.text?.toString()?.let { |
||||
viewModel.importBookshelf(it, groupId) |
||||
} |
||||
} |
||||
noButton() |
||||
neutralButton(R.string.select_file) { |
||||
importBookshelf.launch( |
||||
FilePickerParam( |
||||
mode = FilePicker.FILE, |
||||
allowExtensions = arrayOf("txt", "json") |
||||
) |
||||
) |
||||
} |
||||
}.show() |
||||
} |
||||
|
||||
} |
@ -1,278 +0,0 @@ |
||||
package io.legado.app.ui.main.bookshelf |
||||
|
||||
import android.annotation.SuppressLint |
||||
import android.os.Bundle |
||||
import android.view.Menu |
||||
import android.view.MenuItem |
||||
import android.view.View |
||||
import androidx.appcompat.widget.SearchView |
||||
import androidx.fragment.app.Fragment |
||||
import androidx.fragment.app.activityViewModels |
||||
import androidx.fragment.app.viewModels |
||||
import androidx.lifecycle.LiveData |
||||
import androidx.viewpager2.adapter.FragmentStateAdapter |
||||
import com.google.android.material.tabs.TabLayout |
||||
import com.google.android.material.tabs.TabLayoutMediator |
||||
import io.legado.app.R |
||||
import io.legado.app.base.VMBaseFragment |
||||
import io.legado.app.constant.AppConst |
||||
import io.legado.app.constant.PreferKey |
||||
import io.legado.app.data.appDb |
||||
import io.legado.app.data.entities.BookGroup |
||||
import io.legado.app.databinding.DialogBookshelfConfigBinding |
||||
import io.legado.app.databinding.DialogEditTextBinding |
||||
import io.legado.app.databinding.FragmentBookshelfBinding |
||||
import io.legado.app.help.AppConfig |
||||
import io.legado.app.lib.dialogs.alert |
||||
import io.legado.app.lib.theme.ATH |
||||
import io.legado.app.lib.theme.accentColor |
||||
import io.legado.app.ui.book.arrange.ArrangeBookActivity |
||||
import io.legado.app.ui.book.cache.CacheActivity |
||||
import io.legado.app.ui.book.group.GroupManageDialog |
||||
import io.legado.app.ui.book.local.ImportBookActivity |
||||
import io.legado.app.ui.book.search.SearchActivity |
||||
import io.legado.app.ui.document.FilePicker |
||||
import io.legado.app.ui.document.FilePickerParam |
||||
import io.legado.app.ui.main.MainViewModel |
||||
import io.legado.app.ui.main.bookshelf.books.BooksFragment |
||||
import io.legado.app.utils.* |
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding |
||||
|
||||
/** |
||||
* 书架界面 |
||||
*/ |
||||
class BookshelfFragment : VMBaseFragment<BookshelfViewModel>(R.layout.fragment_bookshelf), |
||||
TabLayout.OnTabSelectedListener, |
||||
SearchView.OnQueryTextListener { |
||||
|
||||
private val binding by viewBinding(FragmentBookshelfBinding::bind) |
||||
override val viewModel: BookshelfViewModel by viewModels() |
||||
private val activityViewModel: MainViewModel by activityViewModels() |
||||
private lateinit var adapter: FragmentStateAdapter |
||||
private lateinit var tabLayout: TabLayout |
||||
private var bookGroupLiveData: LiveData<List<BookGroup>>? = null |
||||
private val bookGroups = mutableListOf<BookGroup>() |
||||
private val fragmentMap = hashMapOf<Long, BooksFragment>() |
||||
private val importBookshelf = registerForActivityResult(FilePicker()) { |
||||
it?.readText(requireContext())?.let { text -> |
||||
viewModel.importBookshelf(text, selectedGroup.groupId) |
||||
} |
||||
} |
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { |
||||
tabLayout = binding.titleBar.findViewById(R.id.tab_layout) |
||||
setSupportToolbar(binding.titleBar.toolbar) |
||||
initView() |
||||
initBookGroupData() |
||||
} |
||||
|
||||
override fun onCompatCreateOptionsMenu(menu: Menu) { |
||||
menuInflater.inflate(R.menu.main_bookshelf, menu) |
||||
} |
||||
|
||||
override fun onCompatOptionsItemSelected(item: MenuItem) { |
||||
super.onCompatOptionsItemSelected(item) |
||||
when (item.itemId) { |
||||
R.id.menu_search -> startActivity<SearchActivity>() |
||||
R.id.menu_update_toc -> { |
||||
val fragment = fragmentMap[selectedGroup.groupId] |
||||
fragment?.getBooks()?.let { |
||||
activityViewModel.upToc(it) |
||||
} |
||||
} |
||||
R.id.menu_bookshelf_layout -> configBookshelf() |
||||
R.id.menu_group_manage -> GroupManageDialog() |
||||
.show(childFragmentManager, "groupManageDialog") |
||||
R.id.menu_add_local -> startActivity<ImportBookActivity>() |
||||
R.id.menu_add_url -> addBookByUrl() |
||||
R.id.menu_arrange_bookshelf -> startActivity<ArrangeBookActivity> { |
||||
putExtra("groupId", selectedGroup.groupId) |
||||
putExtra("groupName", selectedGroup.groupName) |
||||
} |
||||
R.id.menu_download -> startActivity<CacheActivity> { |
||||
putExtra("groupId", selectedGroup.groupId) |
||||
putExtra("groupName", selectedGroup.groupName) |
||||
} |
||||
R.id.menu_export_bookshelf -> { |
||||
val fragment = fragmentMap[selectedGroup.groupId] |
||||
viewModel.exportBookshelf(fragment?.getBooks()) { |
||||
activity?.share(it) |
||||
} |
||||
} |
||||
R.id.menu_import_bookshelf -> importBookshelfAlert() |
||||
} |
||||
} |
||||
|
||||
private val selectedGroup: BookGroup |
||||
get() = bookGroups[tabLayout.selectedTabPosition] |
||||
|
||||
private fun initView() { |
||||
ATH.applyEdgeEffectColor(binding.viewPagerBookshelf) |
||||
tabLayout.isTabIndicatorFullWidth = false |
||||
tabLayout.tabMode = TabLayout.MODE_SCROLLABLE |
||||
tabLayout.setSelectedTabIndicatorColor(requireContext().accentColor) |
||||
binding.viewPagerBookshelf.offscreenPageLimit = 1 |
||||
adapter = TabFragmentPageAdapter() |
||||
binding.viewPagerBookshelf.adapter = adapter |
||||
TabLayoutMediator(tabLayout, binding.viewPagerBookshelf) { tab, i -> |
||||
tab.text = bookGroups[i].groupName |
||||
}.attach() |
||||
} |
||||
|
||||
private fun initBookGroupData() { |
||||
bookGroupLiveData?.removeObservers(viewLifecycleOwner) |
||||
bookGroupLiveData = appDb.bookGroupDao.liveDataShow().apply { |
||||
observe(viewLifecycleOwner) { |
||||
viewModel.checkGroup(it) |
||||
upGroup(it) |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean { |
||||
startActivity<SearchActivity> { |
||||
putExtra("key", query) |
||||
} |
||||
return false |
||||
} |
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean { |
||||
return false |
||||
} |
||||
|
||||
@Synchronized |
||||
private fun upGroup(data: List<BookGroup>) { |
||||
if (data.isEmpty()) { |
||||
appDb.bookGroupDao.enableGroup(AppConst.bookGroupAllId) |
||||
} else { |
||||
if (data != bookGroups) { |
||||
bookGroups.clear() |
||||
bookGroups.addAll(data) |
||||
adapter.notifyDataSetChanged() |
||||
selectLastTab() |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Synchronized |
||||
private fun selectLastTab() { |
||||
tabLayout.removeOnTabSelectedListener(this) |
||||
tabLayout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select() |
||||
tabLayout.addOnTabSelectedListener(this) |
||||
} |
||||
|
||||
@SuppressLint("InflateParams") |
||||
private fun configBookshelf() { |
||||
alert(titleResource = R.string.bookshelf_layout) { |
||||
val bookshelfLayout = getPrefInt(PreferKey.bookshelfLayout) |
||||
val bookshelfSort = getPrefInt(PreferKey.bookshelfSort) |
||||
val alertBinding = |
||||
DialogBookshelfConfigBinding.inflate(layoutInflater) |
||||
.apply { |
||||
rgLayout.checkByIndex(bookshelfLayout) |
||||
rgSort.checkByIndex(bookshelfSort) |
||||
swShowUnread.isChecked = AppConfig.showUnread |
||||
} |
||||
customView { alertBinding.root } |
||||
okButton { |
||||
alertBinding.apply { |
||||
var changed = false |
||||
if (bookshelfLayout != rgLayout.getCheckedIndex()) { |
||||
putPrefInt(PreferKey.bookshelfLayout, rgLayout.getCheckedIndex()) |
||||
changed = true |
||||
} |
||||
if (bookshelfSort != rgSort.getCheckedIndex()) { |
||||
putPrefInt(PreferKey.bookshelfSort, rgSort.getCheckedIndex()) |
||||
changed = true |
||||
} |
||||
if (AppConfig.showUnread != swShowUnread.isChecked) { |
||||
AppConfig.showUnread = swShowUnread.isChecked |
||||
changed = true |
||||
} |
||||
if (changed) { |
||||
activity?.recreate() |
||||
} |
||||
} |
||||
} |
||||
noButton() |
||||
}.show() |
||||
} |
||||
|
||||
@SuppressLint("InflateParams") |
||||
private fun addBookByUrl() { |
||||
alert(titleResource = R.string.add_book_url) { |
||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater) |
||||
customView { alertBinding.root } |
||||
okButton { |
||||
alertBinding.editView.text?.toString()?.let { |
||||
viewModel.addBookByUrl(it) |
||||
} |
||||
} |
||||
noButton() |
||||
}.show() |
||||
} |
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab) { |
||||
fragmentMap[selectedGroup.groupId]?.let { |
||||
toastOnUi("${selectedGroup.groupName}(${it.getBooksCount()})") |
||||
} |
||||
} |
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) = Unit |
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab) { |
||||
putPrefInt(PreferKey.saveTabPosition, tab.position) |
||||
} |
||||
|
||||
fun gotoTop() { |
||||
fragmentMap[selectedGroup.groupId]?.gotoTop() |
||||
} |
||||
|
||||
private fun importBookshelfAlert() { |
||||
alert(titleResource = R.string.import_bookshelf) { |
||||
val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply { |
||||
editView.hint = "url/json" |
||||
} |
||||
customView { alertBinding.root } |
||||
okButton { |
||||
alertBinding.editView.text?.toString()?.let { |
||||
viewModel.importBookshelf(it, selectedGroup.groupId) |
||||
} |
||||
} |
||||
noButton() |
||||
neutralButton(R.string.select_file) { |
||||
importBookshelf.launch( |
||||
FilePickerParam( |
||||
mode = FilePicker.FILE, |
||||
allowExtensions = arrayOf("txt", "json") |
||||
) |
||||
) |
||||
} |
||||
}.show() |
||||
} |
||||
|
||||
private inner class TabFragmentPageAdapter : |
||||
FragmentStateAdapter(this) { |
||||
|
||||
override fun getItemId(position: Int): Long { |
||||
val group = bookGroups[position] |
||||
return group.groupId |
||||
} |
||||
|
||||
override fun containsItem(itemId: Long): Boolean { |
||||
return fragmentMap.containsKey(itemId) |
||||
} |
||||
|
||||
override fun getItemCount(): Int { |
||||
return bookGroups.size |
||||
} |
||||
|
||||
override fun createFragment(position: Int): Fragment { |
||||
val group = bookGroups[position] |
||||
val fragment = BooksFragment.newInstance(position, group.groupId) |
||||
fragmentMap[group.groupId] = fragment |
||||
return fragment |
||||
} |
||||
|
||||
} |
||||
} |
@ -1,82 +0,0 @@ |
||||
package io.legado.app.ui.main.bookshelf |
||||
|
||||
import android.content.Context |
||||
import android.gesture.GestureOverlayView.ORIENTATION_HORIZONTAL |
||||
import android.util.AttributeSet |
||||
import android.view.MotionEvent |
||||
import android.view.View |
||||
import android.view.ViewConfiguration |
||||
import android.widget.LinearLayout |
||||
import androidx.viewpager2.widget.ViewPager2 |
||||
import io.legado.app.R |
||||
import io.legado.app.ui.main.MainActivity |
||||
import io.legado.app.utils.activity |
||||
import kotlin.math.absoluteValue |
||||
import kotlin.math.sign |
||||
|
||||
class RootView : LinearLayout { |
||||
|
||||
constructor(context: Context) : super(context) |
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) |
||||
|
||||
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop |
||||
private var initialX = 0f |
||||
private var initialY = 0f |
||||
|
||||
private val parentViewPager: ViewPager2? |
||||
get() = (activity as? MainActivity)?.getViewPager() |
||||
private val childViewPager: View? |
||||
get() = findViewById(R.id.view_pager_bookshelf) |
||||
|
||||
private fun canChildScroll(orientation: Int, delta: Float): Boolean { |
||||
val direction = -delta.sign.toInt() |
||||
return when (orientation) { |
||||
0 -> childViewPager?.canScrollHorizontally(direction) ?: false |
||||
1 -> childViewPager?.canScrollVertically(direction) ?: false |
||||
else -> throw IllegalArgumentException() |
||||
} |
||||
} |
||||
|
||||
override fun onInterceptTouchEvent(e: MotionEvent): Boolean { |
||||
handleInterceptTouchEvent(e) |
||||
return super.onInterceptTouchEvent(e) |
||||
} |
||||
|
||||
private fun handleInterceptTouchEvent(e: MotionEvent) { |
||||
val orientation = parentViewPager?.orientation ?: return |
||||
// Early return if child can't scroll in same direction as parent |
||||
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) { |
||||
return |
||||
} |
||||
|
||||
if (e.action == MotionEvent.ACTION_DOWN) { |
||||
initialX = e.x |
||||
initialY = e.y |
||||
parent.requestDisallowInterceptTouchEvent(true) |
||||
} else if (e.action == MotionEvent.ACTION_MOVE) { |
||||
val dx = e.x - initialX |
||||
val dy = e.y - initialY |
||||
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL |
||||
// assuming ViewPager2 touch-slop is 2x touch-slop of child |
||||
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f |
||||
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f |
||||
|
||||
if (scaledDx > touchSlop || scaledDy > touchSlop) { |
||||
|
||||
if (isVpHorizontal == (scaledDy > scaledDx)) { |
||||
// Gesture is perpendicular, allow all parents to intercept |
||||
parent.requestDisallowInterceptTouchEvent(false) |
||||
} else { |
||||
// Gesture is parallel, query child if movement in that direction is possible |
||||
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { |
||||
// Child can scroll, disallow all parents to intercept |
||||
parent.requestDisallowInterceptTouchEvent(true) |
||||
} else { |
||||
// Child cannot scroll, allow all parents to intercept |
||||
parent.requestDisallowInterceptTouchEvent(false) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,166 @@ |
||||
@file:Suppress("DEPRECATION") |
||||
|
||||
package io.legado.app.ui.main.bookshelf.style1 |
||||
|
||||
import android.os.Bundle |
||||
import android.view.Menu |
||||
import android.view.View |
||||
import android.view.ViewGroup |
||||
import androidx.appcompat.widget.SearchView |
||||
import androidx.fragment.app.Fragment |
||||
import androidx.fragment.app.FragmentManager |
||||
import androidx.fragment.app.FragmentStatePagerAdapter |
||||
import androidx.lifecycle.LiveData |
||||
import com.google.android.material.tabs.TabLayout |
||||
import io.legado.app.R |
||||
import io.legado.app.constant.AppConst |
||||
import io.legado.app.constant.PreferKey |
||||
import io.legado.app.data.appDb |
||||
import io.legado.app.data.entities.Book |
||||
import io.legado.app.data.entities.BookGroup |
||||
import io.legado.app.databinding.FragmentBookshelfBinding |
||||
import io.legado.app.lib.theme.ATH |
||||
import io.legado.app.lib.theme.accentColor |
||||
import io.legado.app.ui.book.search.SearchActivity |
||||
import io.legado.app.ui.main.bookshelf.BaseBookshelfFragment |
||||
import io.legado.app.ui.main.bookshelf.style1.books.BooksFragment |
||||
import io.legado.app.utils.getPrefInt |
||||
import io.legado.app.utils.putPrefInt |
||||
import io.legado.app.utils.startActivity |
||||
import io.legado.app.utils.toastOnUi |
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding |
||||
|
||||
/** |
||||
* 书架界面 |
||||
*/ |
||||
class BookshelfFragment1 : BaseBookshelfFragment(R.layout.fragment_bookshelf), |
||||
TabLayout.OnTabSelectedListener, |
||||
SearchView.OnQueryTextListener { |
||||
|
||||
private val binding by viewBinding(FragmentBookshelfBinding::bind) |
||||
private lateinit var adapter: FragmentStatePagerAdapter |
||||
private lateinit var tabLayout: TabLayout |
||||
private var bookGroupLiveData: LiveData<List<BookGroup>>? = null |
||||
private val bookGroups = mutableListOf<BookGroup>() |
||||
private val fragmentMap = hashMapOf<Long, BooksFragment>() |
||||
|
||||
override val groupId: Long get() = selectedGroup.groupId |
||||
|
||||
override val books: List<Book> |
||||
get() { |
||||
val fragment = fragmentMap[selectedGroup.groupId] |
||||
return fragment?.getBooks() ?: emptyList() |
||||
} |
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { |
||||
tabLayout = binding.titleBar.findViewById(R.id.tab_layout) |
||||
setSupportToolbar(binding.titleBar.toolbar) |
||||
initView() |
||||
initBookGroupData() |
||||
} |
||||
|
||||
override fun onCompatCreateOptionsMenu(menu: Menu) { |
||||
menuInflater.inflate(R.menu.main_bookshelf, menu) |
||||
} |
||||
|
||||
private val selectedGroup: BookGroup |
||||
get() = bookGroups[tabLayout.selectedTabPosition] |
||||
|
||||
private fun initView() { |
||||
ATH.applyEdgeEffectColor(binding.viewPagerBookshelf) |
||||
tabLayout.isTabIndicatorFullWidth = false |
||||
tabLayout.tabMode = TabLayout.MODE_SCROLLABLE |
||||
tabLayout.setSelectedTabIndicatorColor(requireContext().accentColor) |
||||
tabLayout.setupWithViewPager(binding.viewPagerBookshelf) |
||||
binding.viewPagerBookshelf.offscreenPageLimit = 1 |
||||
adapter = TabFragmentPageAdapter(childFragmentManager) |
||||
binding.viewPagerBookshelf.adapter = adapter |
||||
} |
||||
|
||||
private fun initBookGroupData() { |
||||
bookGroupLiveData?.removeObservers(viewLifecycleOwner) |
||||
bookGroupLiveData = appDb.bookGroupDao.liveDataShow().apply { |
||||
observe(viewLifecycleOwner) { |
||||
viewModel.checkGroup(it) |
||||
upGroup(it) |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean { |
||||
startActivity<SearchActivity> { |
||||
putExtra("key", query) |
||||
} |
||||
return false |
||||
} |
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean { |
||||
return false |
||||
} |
||||
|
||||
@Synchronized |
||||
private fun upGroup(data: List<BookGroup>) { |
||||
if (data.isEmpty()) { |
||||
appDb.bookGroupDao.enableGroup(AppConst.bookGroupAllId) |
||||
} else { |
||||
if (data != bookGroups) { |
||||
bookGroups.clear() |
||||
bookGroups.addAll(data) |
||||
adapter.notifyDataSetChanged() |
||||
selectLastTab() |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Synchronized |
||||
private fun selectLastTab() { |
||||
tabLayout.removeOnTabSelectedListener(this) |
||||
tabLayout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select() |
||||
tabLayout.addOnTabSelectedListener(this) |
||||
} |
||||
|
||||
override fun onTabReselected(tab: TabLayout.Tab) { |
||||
fragmentMap[selectedGroup.groupId]?.let { |
||||
toastOnUi("${selectedGroup.groupName}(${it.getBooksCount()})") |
||||
} |
||||
} |
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) = Unit |
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab) { |
||||
putPrefInt(PreferKey.saveTabPosition, tab.position) |
||||
} |
||||
|
||||
override fun gotoTop() { |
||||
fragmentMap[selectedGroup.groupId]?.gotoTop() |
||||
} |
||||
|
||||
private inner class TabFragmentPageAdapter(fm: FragmentManager) : |
||||
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { |
||||
|
||||
override fun getPageTitle(position: Int): CharSequence { |
||||
return bookGroups[position].groupName |
||||
} |
||||
|
||||
override fun getItemPosition(`object`: Any): Int { |
||||
return POSITION_NONE |
||||
} |
||||
|
||||
override fun getItem(position: Int): Fragment { |
||||
val group = bookGroups[position] |
||||
return BooksFragment.newInstance(position, group.groupId) |
||||
} |
||||
|
||||
override fun getCount(): Int { |
||||
return bookGroups.size |
||||
} |
||||
|
||||
override fun instantiateItem(container: ViewGroup, position: Int): Any { |
||||
val fragment = super.instantiateItem(container, position) as BooksFragment |
||||
val group = bookGroups[position] |
||||
fragmentMap[group.groupId] = fragment |
||||
return fragment |
||||
} |
||||
|
||||
} |
||||
} |
@ -1,4 +1,4 @@ |
||||
package io.legado.app.ui.main.bookshelf.books |
||||
package io.legado.app.ui.main.bookshelf.style1.books |
||||
|
||||
import android.content.Context |
||||
import androidx.core.os.bundleOf |
@ -1,4 +1,4 @@ |
||||
package io.legado.app.ui.main.bookshelf.books |
||||
package io.legado.app.ui.main.bookshelf.style1.books |
||||
|
||||
import android.content.Context |
||||
import android.os.Bundle |
@ -1,4 +1,4 @@ |
||||
package io.legado.app.ui.main.bookshelf.books |
||||
package io.legado.app.ui.main.bookshelf.style1.books |
||||
|
||||
import android.content.Context |
||||
import android.os.Bundle |
@ -0,0 +1,85 @@ |
||||
package io.legado.app.ui.main.bookshelf.style2 |
||||
|
||||
import android.content.Context |
||||
import androidx.core.os.bundleOf |
||||
import androidx.recyclerview.widget.DiffUtil |
||||
import androidx.recyclerview.widget.RecyclerView |
||||
import io.legado.app.data.entities.Book |
||||
|
||||
abstract class BaseBooksAdapter<VH : RecyclerView.ViewHolder>( |
||||
val context: Context, |
||||
val callBack: CallBack |
||||
) : RecyclerView.Adapter<VH>() { |
||||
|
||||
val diffItemCallback: DiffUtil.ItemCallback<Book> |
||||
get() = object : DiffUtil.ItemCallback<Book>() { |
||||
|
||||
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean { |
||||
return oldItem.name == newItem.name |
||||
&& oldItem.author == newItem.author |
||||
} |
||||
|
||||
override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean { |
||||
return when { |
||||
oldItem.durChapterTime != newItem.durChapterTime -> false |
||||
oldItem.name != newItem.name -> false |
||||
oldItem.author != newItem.author -> false |
||||
oldItem.durChapterTitle != newItem.durChapterTitle -> false |
||||
oldItem.latestChapterTitle != newItem.latestChapterTitle -> false |
||||
oldItem.lastCheckCount != newItem.lastCheckCount -> false |
||||
oldItem.getDisplayCover() != newItem.getDisplayCover() -> false |
||||
oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum() -> false |
||||
else -> true |
||||
} |
||||
} |
||||
|
||||
override fun getChangePayload(oldItem: Book, newItem: Book): Any? { |
||||
val bundle = bundleOf() |
||||
if (oldItem.name != newItem.name) { |
||||
bundle.putString("name", newItem.name) |
||||
} |
||||
if (oldItem.author != newItem.author) { |
||||
bundle.putString("author", newItem.author) |
||||
} |
||||
if (oldItem.durChapterTitle != newItem.durChapterTitle) { |
||||
bundle.putString("dur", newItem.durChapterTitle) |
||||
} |
||||
if (oldItem.latestChapterTitle != newItem.latestChapterTitle) { |
||||
bundle.putString("last", newItem.latestChapterTitle) |
||||
} |
||||
if (oldItem.getDisplayCover() != newItem.getDisplayCover()) { |
||||
bundle.putString("cover", newItem.getDisplayCover()) |
||||
} |
||||
if (oldItem.lastCheckCount != newItem.lastCheckCount |
||||
|| oldItem.durChapterTime != newItem.durChapterTime |
||||
|| oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum() |
||||
|| oldItem.lastCheckCount != newItem.lastCheckCount |
||||
) { |
||||
bundle.putBoolean("refresh", true) |
||||
} |
||||
if (bundle.isEmpty) return null |
||||
return bundle |
||||
} |
||||
|
||||
} |
||||
|
||||
fun notification(bookUrl: String) { |
||||
for (i in 0 until itemCount) { |
||||
callBack.getItem(i).let { |
||||
if (it is Book && it.bookUrl == bookUrl) { |
||||
notifyItemChanged(i, bundleOf(Pair("refresh", null))) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
interface CallBack { |
||||
fun onItemClick(position: Int) |
||||
fun onItemLongClick(position: Int) |
||||
fun isUpdate(bookUrl: String): Boolean |
||||
fun getItemCount(): Int |
||||
fun getItem(position: Int): Any |
||||
} |
||||
} |
@ -0,0 +1,134 @@ |
||||
package io.legado.app.ui.main.bookshelf.style2 |
||||
|
||||
import android.content.Context |
||||
import android.os.Bundle |
||||
import android.view.LayoutInflater |
||||
import android.view.ViewGroup |
||||
import androidx.recyclerview.widget.RecyclerView |
||||
import io.legado.app.constant.BookType |
||||
import io.legado.app.data.entities.Book |
||||
import io.legado.app.data.entities.BookGroup |
||||
import io.legado.app.databinding.ItemBookshelfGridBinding |
||||
import io.legado.app.databinding.ItemBookshelfGridGroupBinding |
||||
import io.legado.app.help.AppConfig |
||||
import io.legado.app.utils.invisible |
||||
import splitties.views.onLongClick |
||||
|
||||
class BooksAdapterGrid(context: Context, callBack: CallBack) : |
||||
BaseBooksAdapter<RecyclerView.ViewHolder>(context, callBack) { |
||||
|
||||
override fun getItemCount(): Int { |
||||
return callBack.getItemCount() |
||||
} |
||||
|
||||
override fun onCreateViewHolder( |
||||
parent: ViewGroup, |
||||
viewType: Int |
||||
): RecyclerView.ViewHolder { |
||||
return BookViewHolder( |
||||
ItemBookshelfGridBinding.inflate(LayoutInflater.from(context), parent, false) |
||||
) |
||||
} |
||||
|
||||
override fun onBindViewHolder( |
||||
holder: RecyclerView.ViewHolder, |
||||
position: Int, |
||||
payloads: MutableList<Any> |
||||
) { |
||||
val bundle = payloads.getOrNull(0) as? Bundle |
||||
when { |
||||
bundle == null -> super.onBindViewHolder(holder, position, payloads) |
||||
holder is BookViewHolder -> onBindBook(holder.binding, position, bundle) |
||||
holder is GroupViewHolder -> onBindGroup(holder.binding, position, bundle) |
||||
} |
||||
} |
||||
|
||||
private fun onBindGroup(binding: ItemBookshelfGridGroupBinding, position: Int, bundle: Bundle) { |
||||
binding.run { |
||||
val item = callBack.getItem(position) |
||||
if (item is BookGroup) { |
||||
tvName.text = item.groupName |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun onBindBook(binding: ItemBookshelfGridBinding, position: Int, bundle: Bundle) { |
||||
binding.run { |
||||
val item = callBack.getItem(position) |
||||
if (item is Book) { |
||||
bundle.keySet().forEach { |
||||
when (it) { |
||||
"name" -> tvName.text = item.name |
||||
"cover" -> ivCover.load( |
||||
item.getDisplayCover(), |
||||
item.name, |
||||
item.author |
||||
) |
||||
"refresh" -> upRefresh(this, item) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { |
||||
when (holder) { |
||||
is BookViewHolder -> onBindBook(holder.binding, position) |
||||
is GroupViewHolder -> onBindGroup(holder.binding, position) |
||||
} |
||||
} |
||||
|
||||
private fun onBindGroup(binding: ItemBookshelfGridGroupBinding, position: Int) { |
||||
binding.run { |
||||
val item = callBack.getItem(position) |
||||
if (item is BookGroup) { |
||||
tvName.text = item.groupName |
||||
} |
||||
root.setOnClickListener { |
||||
callBack.onItemClick(position) |
||||
} |
||||
root.onLongClick { |
||||
callBack.onItemLongClick(position) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun onBindBook(binding: ItemBookshelfGridBinding, position: Int) { |
||||
binding.run { |
||||
val item = callBack.getItem(position) |
||||
if (item is Book) { |
||||
tvName.text = item.name |
||||
ivCover.load(item.getDisplayCover(), item.name, item.author) |
||||
upRefresh(this, item) |
||||
} |
||||
root.setOnClickListener { |
||||
callBack.onItemClick(position) |
||||
} |
||||
root.onLongClick { |
||||
callBack.onItemLongClick(position) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun upRefresh(binding: ItemBookshelfGridBinding, item: Book) { |
||||
if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { |
||||
binding.bvUnread.invisible() |
||||
binding.rlLoading.show() |
||||
} else { |
||||
binding.rlLoading.hide() |
||||
if (AppConfig.showUnread) { |
||||
binding.bvUnread.setBadgeCount(item.getUnreadChapterNum()) |
||||
binding.bvUnread.setHighlight(item.lastCheckCount > 0) |
||||
} else { |
||||
binding.bvUnread.invisible() |
||||
} |
||||
} |
||||
} |
||||
|
||||
class BookViewHolder(val binding: ItemBookshelfGridBinding) : |
||||
RecyclerView.ViewHolder(binding.root) |
||||
|
||||
class GroupViewHolder(val binding: ItemBookshelfGridGroupBinding) : |
||||
RecyclerView.ViewHolder(binding.root) |
||||
|
||||
} |
@ -0,0 +1,150 @@ |
||||
package io.legado.app.ui.main.bookshelf.style2 |
||||
|
||||
import android.content.Context |
||||
import android.os.Bundle |
||||
import android.view.LayoutInflater |
||||
import android.view.ViewGroup |
||||
import androidx.recyclerview.widget.RecyclerView |
||||
import io.legado.app.constant.BookType |
||||
import io.legado.app.data.entities.Book |
||||
import io.legado.app.data.entities.BookGroup |
||||
import io.legado.app.databinding.ItemBookshelfListBinding |
||||
import io.legado.app.databinding.ItemBookshelfListGroupBinding |
||||
import io.legado.app.help.AppConfig |
||||
import io.legado.app.utils.gone |
||||
import io.legado.app.utils.invisible |
||||
import io.legado.app.utils.visible |
||||
import splitties.views.onLongClick |
||||
|
||||
class BooksAdapterList(context: Context, callBack: CallBack) : |
||||
BaseBooksAdapter<RecyclerView.ViewHolder>(context, callBack) { |
||||
|
||||
override fun getItemCount(): Int { |
||||
return callBack.getItemCount() |
||||
} |
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { |
||||
return BookViewHolder( |
||||
ItemBookshelfListBinding.inflate(LayoutInflater.from(context), parent, false) |
||||
) |
||||
} |
||||
|
||||
override fun onBindViewHolder( |
||||
holder: RecyclerView.ViewHolder, |
||||
position: Int, |
||||
payloads: MutableList<Any> |
||||
) { |
||||
val bundle = payloads.getOrNull(0) as? Bundle |
||||
when { |
||||
bundle == null -> super.onBindViewHolder(holder, position, payloads) |
||||
holder is BookViewHolder -> onBindBook(holder.binding, position, bundle) |
||||
holder is GroupViewHolder -> onBindGroup(holder.binding, position, bundle) |
||||
} |
||||
} |
||||
|
||||
private fun onBindGroup(binding: ItemBookshelfListGroupBinding, position: Int, bundle: Bundle) { |
||||
binding.run { |
||||
val item = callBack.getItem(position) |
||||
if (item is BookGroup) { |
||||
tvName.text = item.groupName |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun onBindBook(binding: ItemBookshelfListBinding, position: Int, bundle: Bundle) { |
||||
binding.run { |
||||
val item = callBack.getItem(position) |
||||
if (item is Book) { |
||||
tvRead.text = item.durChapterTitle |
||||
tvLast.text = item.latestChapterTitle |
||||
bundle.keySet().forEach { |
||||
when (it) { |
||||
"name" -> tvName.text = item.name |
||||
"author" -> tvAuthor.text = item.author |
||||
"cover" -> ivCover.load( |
||||
item.getDisplayCover(), |
||||
item.name, |
||||
item.author |
||||
) |
||||
"refresh" -> upRefresh(this, item) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { |
||||
when (holder) { |
||||
is BookViewHolder -> onBindBook(holder.binding, position) |
||||
is GroupViewHolder -> onBindGroup(holder.binding, position) |
||||
} |
||||
} |
||||
|
||||
private fun onBindGroup(binding: ItemBookshelfListGroupBinding, position: Int) { |
||||
binding.run { |
||||
val item = callBack.getItem(position) |
||||
if (item is BookGroup) { |
||||
tvName.text = item.groupName |
||||
flHasNew.gone() |
||||
ivAuthor.gone() |
||||
ivLast.gone() |
||||
ivRead.gone() |
||||
tvAuthor.gone() |
||||
tvLast.gone() |
||||
tvRead.gone() |
||||
} |
||||
root.setOnClickListener { |
||||
callBack.onItemClick(position) |
||||
} |
||||
root.onLongClick { |
||||
callBack.onItemLongClick(position) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun onBindBook(binding: ItemBookshelfListBinding, position: Int) { |
||||
binding.run { |
||||
val item = callBack.getItem(position) |
||||
if (item is Book) { |
||||
tvName.text = item.name |
||||
tvAuthor.text = item.author |
||||
tvRead.text = item.durChapterTitle |
||||
tvLast.text = item.latestChapterTitle |
||||
ivCover.load(item.getDisplayCover(), item.name, item.author) |
||||
flHasNew.visible() |
||||
ivAuthor.visible() |
||||
ivLast.visible() |
||||
ivRead.visible() |
||||
upRefresh(this, item) |
||||
} |
||||
root.setOnClickListener { |
||||
callBack.onItemClick(position) |
||||
} |
||||
root.onLongClick { |
||||
callBack.onItemLongClick(position) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun upRefresh(binding: ItemBookshelfListBinding, item: Book) { |
||||
if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { |
||||
binding.bvUnread.invisible() |
||||
binding.rlLoading.show() |
||||
} else { |
||||
binding.rlLoading.hide() |
||||
if (AppConfig.showUnread) { |
||||
binding.bvUnread.setHighlight(item.lastCheckCount > 0) |
||||
binding.bvUnread.setBadgeCount(item.getUnreadChapterNum()) |
||||
} else { |
||||
binding.bvUnread.invisible() |
||||
} |
||||
} |
||||
} |
||||
|
||||
class BookViewHolder(val binding: ItemBookshelfListBinding) : |
||||
RecyclerView.ViewHolder(binding.root) |
||||
|
||||
class GroupViewHolder(val binding: ItemBookshelfListGroupBinding) : |
||||
RecyclerView.ViewHolder(binding.root) |
||||
|
||||
} |
@ -0,0 +1,235 @@ |
||||
package io.legado.app.ui.main.bookshelf.style2 |
||||
|
||||
import android.os.Bundle |
||||
import android.view.Menu |
||||
import android.view.View |
||||
import androidx.appcompat.widget.SearchView |
||||
import androidx.core.view.isGone |
||||
import androidx.lifecycle.LiveData |
||||
import androidx.recyclerview.widget.GridLayoutManager |
||||
import androidx.recyclerview.widget.LinearLayoutManager |
||||
import androidx.recyclerview.widget.RecyclerView |
||||
import io.legado.app.R |
||||
import io.legado.app.constant.AppConst |
||||
import io.legado.app.constant.BookType |
||||
import io.legado.app.constant.EventBus |
||||
import io.legado.app.constant.PreferKey |
||||
import io.legado.app.data.appDb |
||||
import io.legado.app.data.entities.Book |
||||
import io.legado.app.data.entities.BookGroup |
||||
import io.legado.app.databinding.FragmentBookshelf1Binding |
||||
import io.legado.app.help.AppConfig |
||||
import io.legado.app.lib.theme.ATH |
||||
import io.legado.app.lib.theme.accentColor |
||||
import io.legado.app.lib.theme.primaryTextColor |
||||
import io.legado.app.ui.audio.AudioPlayActivity |
||||
import io.legado.app.ui.book.info.BookInfoActivity |
||||
import io.legado.app.ui.book.read.ReadBookActivity |
||||
import io.legado.app.ui.book.search.SearchActivity |
||||
import io.legado.app.ui.main.bookshelf.BaseBookshelfFragment |
||||
import io.legado.app.utils.cnCompare |
||||
import io.legado.app.utils.getPrefInt |
||||
import io.legado.app.utils.observeEvent |
||||
import io.legado.app.utils.startActivity |
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding |
||||
import kotlin.math.max |
||||
|
||||
/** |
||||
* 书架界面 |
||||
*/ |
||||
class BookshelfFragment2 : BaseBookshelfFragment(R.layout.fragment_bookshelf1), |
||||
SearchView.OnQueryTextListener, |
||||
BaseBooksAdapter.CallBack { |
||||
|
||||
private val binding by viewBinding(FragmentBookshelf1Binding::bind) |
||||
private lateinit var searchView: SearchView |
||||
private lateinit var booksAdapter: BaseBooksAdapter<*> |
||||
override var groupId = AppConst.bookGroupNoneId |
||||
private var bookGroupLiveData: LiveData<List<BookGroup>>? = null |
||||
private var bookshelfLiveData: LiveData<List<Book>>? = null |
||||
private var bookGroups: List<BookGroup> = emptyList() |
||||
override var books: List<Book> = emptyList() |
||||
|
||||
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { |
||||
searchView = binding.titleBar.findViewById(R.id.search_view) |
||||
setSupportToolbar(binding.titleBar.toolbar) |
||||
initSearchView() |
||||
initRecyclerView() |
||||
initGroupData() |
||||
initBooksData() |
||||
} |
||||
|
||||
override fun onCompatCreateOptionsMenu(menu: Menu) { |
||||
menuInflater.inflate(R.menu.main_bookshelf, menu) |
||||
menu.findItem(R.id.menu_search).isVisible = false |
||||
} |
||||
|
||||
private fun initSearchView() { |
||||
ATH.setTint(searchView, primaryTextColor) |
||||
searchView.onActionViewExpanded() |
||||
searchView.isSubmitButtonEnabled = true |
||||
searchView.queryHint = getString(R.string.screen_find) |
||||
searchView.clearFocus() |
||||
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { |
||||
override fun onQueryTextSubmit(query: String?): Boolean { |
||||
return false |
||||
} |
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean { |
||||
|
||||
return false |
||||
} |
||||
}) |
||||
} |
||||
|
||||
private fun initRecyclerView() { |
||||
ATH.applyEdgeEffectColor(binding.rvBookshelf) |
||||
binding.refreshLayout.setColorSchemeColors(accentColor) |
||||
binding.refreshLayout.setOnRefreshListener { |
||||
binding.refreshLayout.isRefreshing = false |
||||
activityViewModel.upToc(books) |
||||
} |
||||
val bookshelfLayout = getPrefInt(PreferKey.bookshelfLayout) |
||||
if (bookshelfLayout == 0) { |
||||
binding.rvBookshelf.layoutManager = LinearLayoutManager(context) |
||||
booksAdapter = BooksAdapterList(requireContext(), this) |
||||
} else { |
||||
binding.rvBookshelf.layoutManager = GridLayoutManager(context, bookshelfLayout + 2) |
||||
booksAdapter = BooksAdapterGrid(requireContext(), this) |
||||
} |
||||
binding.rvBookshelf.adapter = booksAdapter |
||||
booksAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { |
||||
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { |
||||
val layoutManager = binding.rvBookshelf.layoutManager |
||||
if (positionStart == 0 && layoutManager is LinearLayoutManager) { |
||||
val scrollTo = layoutManager.findFirstVisibleItemPosition() - itemCount |
||||
binding.rvBookshelf.scrollToPosition(max(0, scrollTo)) |
||||
} |
||||
} |
||||
|
||||
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { |
||||
val layoutManager = binding.rvBookshelf.layoutManager |
||||
if (toPosition == 0 && layoutManager is LinearLayoutManager) { |
||||
val scrollTo = layoutManager.findFirstVisibleItemPosition() - itemCount |
||||
binding.rvBookshelf.scrollToPosition(max(0, scrollTo)) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
private fun initGroupData() { |
||||
bookGroupLiveData?.removeObservers(this) |
||||
bookGroupLiveData = appDb.bookGroupDao.liveDataShow().apply { |
||||
observe(viewLifecycleOwner) { |
||||
if (it != bookGroups) { |
||||
bookGroups = it |
||||
booksAdapter.notifyDataSetChanged() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun initBooksData() { |
||||
bookshelfLiveData?.removeObservers(this) |
||||
bookshelfLiveData = when (groupId) { |
||||
AppConst.bookGroupAllId -> appDb.bookDao.observeAll() |
||||
AppConst.bookGroupLocalId -> appDb.bookDao.observeLocal() |
||||
AppConst.bookGroupAudioId -> appDb.bookDao.observeAudio() |
||||
AppConst.bookGroupNoneId -> appDb.bookDao.observeNoGroup() |
||||
else -> appDb.bookDao.observeByGroup(groupId) |
||||
}.apply { |
||||
observe(viewLifecycleOwner) { list -> |
||||
binding.tvEmptyMsg.isGone = list.isNotEmpty() |
||||
books = when (getPrefInt(PreferKey.bookshelfSort)) { |
||||
1 -> list.sortedByDescending { |
||||
it.latestChapterTime |
||||
} |
||||
2 -> list.sortedWith { o1, o2 -> |
||||
o1.name.cnCompare(o2.name) |
||||
} |
||||
3 -> list.sortedBy { |
||||
it.order |
||||
} |
||||
else -> list.sortedByDescending { |
||||
it.durChapterTime |
||||
} |
||||
} |
||||
booksAdapter.notifyDataSetChanged() |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean { |
||||
startActivity<SearchActivity> { |
||||
putExtra("key", query) |
||||
} |
||||
return false |
||||
} |
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean { |
||||
return false |
||||
} |
||||
|
||||
override fun gotoTop() { |
||||
if (AppConfig.isEInkMode) { |
||||
binding.rvBookshelf.scrollToPosition(0) |
||||
} else { |
||||
binding.rvBookshelf.smoothScrollToPosition(0) |
||||
} |
||||
} |
||||
|
||||
override fun onItemClick(position: Int) { |
||||
if (position < bookGroups.size) { |
||||
val bookGroup = bookGroups[position] |
||||
groupId = bookGroup.groupId |
||||
initBooksData() |
||||
} else { |
||||
val book = books[position - bookGroups.size] |
||||
when (book.type) { |
||||
BookType.audio -> |
||||
startActivity<AudioPlayActivity> { |
||||
putExtra("bookUrl", book.bookUrl) |
||||
} |
||||
else -> startActivity<ReadBookActivity> { |
||||
putExtra("bookUrl", book.bookUrl) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onItemLongClick(position: Int) { |
||||
if (position < bookGroups.size) { |
||||
val bookGroup = bookGroups[position] |
||||
|
||||
} else { |
||||
val book = books[position - bookGroups.size] |
||||
startActivity<BookInfoActivity> { |
||||
putExtra("name", book.name) |
||||
putExtra("author", book.author) |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun isUpdate(bookUrl: String): Boolean { |
||||
return bookUrl in activityViewModel.updateList |
||||
} |
||||
|
||||
override fun getItemCount(): Int { |
||||
return bookGroups.size + books.size |
||||
} |
||||
|
||||
override fun getItem(position: Int): Any { |
||||
return if (position < bookGroups.size) { |
||||
bookGroups[position] |
||||
} else { |
||||
books[position - bookGroups.size] |
||||
} |
||||
} |
||||
|
||||
override fun observeLiveBus() { |
||||
super.observeLiveBus() |
||||
observeEvent<String>(EventBus.UP_BOOK) { |
||||
booksAdapter.notification(it) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,48 @@ |
||||
package io.legado.app.ui.widget.recycler |
||||
|
||||
import android.content.Context |
||||
import android.util.AttributeSet |
||||
import android.view.MotionEvent |
||||
import androidx.recyclerview.widget.RecyclerView |
||||
import kotlin.math.abs |
||||
|
||||
class RecyclerViewAtPager2 : RecyclerView { |
||||
|
||||
constructor(context: Context) : super(context) |
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) |
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( |
||||
context, |
||||
attrs, |
||||
defStyleAttr |
||||
) |
||||
|
||||
private var startX = 0 |
||||
private var startY = 0 |
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean { |
||||
when (ev.action) { |
||||
MotionEvent.ACTION_DOWN -> { |
||||
startX = ev.x.toInt() |
||||
startY = ev.y.toInt() |
||||
parent.requestDisallowInterceptTouchEvent(true) |
||||
} |
||||
MotionEvent.ACTION_MOVE -> { |
||||
val endX = ev.x.toInt() |
||||
val endY = ev.y.toInt() |
||||
val disX = abs(endX - startX) |
||||
val disY = abs(endY - startY) |
||||
if (disX > disY) { |
||||
if (disX > 50) { |
||||
parent.requestDisallowInterceptTouchEvent(false) |
||||
} |
||||
} else { |
||||
parent.requestDisallowInterceptTouchEvent(true) |
||||
} |
||||
} |
||||
MotionEvent.ACTION_UP, |
||||
MotionEvent.ACTION_CANCEL -> parent.requestDisallowInterceptTouchEvent(false) |
||||
} |
||||
return super.dispatchTouchEvent(ev) |
||||
} |
||||
|
||||
} |
@ -1,6 +1,6 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<item android:state_enabled="false" android:color="@color/ate_button_disabled_light" /> |
||||
<item android:state_enabled="false" android:color="@color/disabled" /> |
||||
<item android:state_pressed="true" android:color="@color/btn_bg_press" /> |
||||
<item android:state_focused="true" android:color="@color/btn_bg_press_2" /> |
||||
<item android:color="@color/primaryText" /> |
||||
|
@ -0,0 +1,48 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:orientation="vertical"> |
||||
|
||||
<io.legado.app.ui.widget.TitleBar |
||||
android:id="@+id/title_bar" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
app:attachToActivity="false" |
||||
app:contentLayout="@layout/view_search" |
||||
app:layout_constraintTop_toTopOf="parent" |
||||
app:title="@string/bookshelf" /> |
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout |
||||
android:id="@+id/refresh_layout" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="0dp" |
||||
app:layout_constraintTop_toBottomOf="@+id/title_bar" |
||||
app:layout_constraintBottom_toBottomOf="parent"> |
||||
|
||||
<io.legado.app.ui.widget.recycler.RecyclerViewAtPager2 |
||||
android:id="@+id/rv_bookshelf" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
tools:listitem="@layout/item_bookshelf_list" /> |
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> |
||||
|
||||
<TextView |
||||
android:id="@+id/tv_empty_msg" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="center" |
||||
android:layout_margin="16dp" |
||||
android:gravity="center" |
||||
android:text="@string/bookshelf_empty" |
||||
android:visibility="gone" |
||||
app:layout_constraintTop_toBottomOf="@+id/title_bar" |
||||
app:layout_constraintBottom_toBottomOf="parent" |
||||
app:layout_constraintLeft_toLeftOf="parent" |
||||
app:layout_constraintRight_toRightOf="parent" |
||||
tools:text="TextView" /> |
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout> |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue