mirror of
https://github.com/fumiama/copymanga.git
synced 2026-06-27 06:10:29 +08:00
v1.1.2
1. 增加浏览已下载漫画功能 2. 细节优化提升
This commit is contained in:
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "top.fumiama.copymanga"
|
applicationId "top.fumiama.copymanga"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 3
|
versionCode 4
|
||||||
versionName '1.1.1'
|
versionName '1.1.2'
|
||||||
resConfigs "zh", "zh-rCN"
|
resConfigs "zh", "zh-rCN"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@@ -36,11 +36,11 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
debug{
|
/*debug{
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
implementation 'androidx.core:core-ktx:1.3.2'
|
implementation 'androidx.core:core-ktx:1.3.2'
|
||||||
//implementation 'androidx.appcompat:appcompat:1.2.0'
|
//implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.viewpager2:viewpager2:1.0.0'
|
implementation 'androidx.viewpager2:viewpager2:1.0.0'
|
||||||
//implementation 'com.google.android.material:material:1.2.1'
|
//implementation 'com.google.android.material:material:1.2.1'
|
||||||
@@ -59,6 +59,7 @@ dependencies {
|
|||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
||||||
implementation 'com.google.code.gson:gson:2.8.6'
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
|
//implementation 'com.liaoinstan.springview:library:1.7.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
andResGuard {
|
andResGuard {
|
||||||
@@ -93,7 +94,7 @@ andResGuard {
|
|||||||
"*.gif",
|
"*.gif",
|
||||||
]
|
]
|
||||||
sevenzip {
|
sevenzip {
|
||||||
artifact = 'com.tencent.mm:SevenZip:1.2.19'
|
artifact = 'com.tencent.mm:SevenZip:1.2.20'
|
||||||
//path = "/usr/local/bin/7za"
|
//path = "/usr/local/bin/7za"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,5 +18,6 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".activity.ViewMangaActivity"/>
|
<activity android:name=".activity.ViewMangaActivity"/>
|
||||||
<activity android:name=".activity.DlActivity"/>
|
<activity android:name=".activity.DlActivity"/>
|
||||||
|
<activity android:name=".activity.DlListActivity"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -38,6 +38,7 @@ if (typeof (loaded) == "undefined") {
|
|||||||
if (url.endsWith("/index")) invoke.pinTitle();
|
if (url.endsWith("/index")) invoke.pinTitle();
|
||||||
else if (url.indexOf("/comicContent/") > 0) setTimeout(function () { invoke.loadChapter() }, 1000);
|
else if (url.indexOf("/comicContent/") > 0) setTimeout(function () { invoke.loadChapter() }, 1000);
|
||||||
else if (url.indexOf("/details/comic/") > 0) GM.loadComic(url);
|
else if (url.indexOf("/details/comic/") > 0) GM.loadComic(url);
|
||||||
|
else if (url.indexOf("/personal") > 0) GM.enterProfile();
|
||||||
}
|
}
|
||||||
modify();
|
modify();
|
||||||
invoke.urlChangeListener(modify);
|
invoke.urlChangeListener(modify);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class DlActivity : Activity() {
|
|||||||
var tbtnUrlList = arrayListOf<String>()
|
var tbtnUrlList = arrayListOf<String>()
|
||||||
private val handler = DlHandler(this)
|
private val handler = DlHandler(this)
|
||||||
private var btnw = 0
|
private var btnw = 0
|
||||||
private var cdwnHeight = 0
|
private var cdwnWidth = 0
|
||||||
private var canDl = false
|
private var canDl = false
|
||||||
private lateinit var toolsBox: ToolsBox
|
private lateinit var toolsBox: ToolsBox
|
||||||
lateinit var mangaDlTools: MangaDlTools
|
lateinit var mangaDlTools: MangaDlTools
|
||||||
@@ -64,16 +64,14 @@ class DlActivity : Activity() {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDlCard() {
|
private fun showDlCard(){
|
||||||
//ObjectAnimator.ofFloat(csdwn, "alpha", 0.3f, 0.9f).setDuration(233).start()
|
//ObjectAnimator.ofFloat(csdwn, "alpha", 0.3f, 0.9f).setDuration(233).start()
|
||||||
ObjectAnimator.ofFloat(csdwn, "translationY", cdwnHeight.toFloat(), 0f).setDuration(233)
|
ObjectAnimator.ofFloat(csdwn, "translationX", cdwnWidth.toFloat() * 0.9f, 0f).setDuration(233).start()
|
||||||
.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hideDlCard() {
|
private fun hideDlCard(){
|
||||||
//ObjectAnimator.ofFloat(csdwn, "alpha", 0.9f, 0.3f).setDuration(233).start()
|
//ObjectAnimator.ofFloat(csdwn, "alpha", 0.9f, 0.3f).setDuration(233).start()
|
||||||
ObjectAnimator.ofFloat(csdwn, "translationY", 0f, cdwnHeight.toFloat()).setDuration(233)
|
ObjectAnimator.ofFloat(csdwn, "translationX", 0f, cdwnWidth.toFloat() * 0.9f).setDuration(233).start()
|
||||||
.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fillChapters() {
|
private fun fillChapters() {
|
||||||
@@ -87,7 +85,11 @@ class DlActivity : Activity() {
|
|||||||
sleep(2333)
|
sleep(2333)
|
||||||
for (i in tbtnlist.listIterator()) {
|
for (i in tbtnlist.listIterator()) {
|
||||||
if (i.isChecked) dlMethod(i)
|
if (i.isChecked) dlMethod(i)
|
||||||
if (!canDl) break
|
if (!canDl) {
|
||||||
|
checkedChapter -= dldChapter
|
||||||
|
dldChapter = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (canDl) {
|
if (canDl) {
|
||||||
haveDlStarted = false
|
haveDlStarted = false
|
||||||
@@ -107,34 +109,33 @@ class DlActivity : Activity() {
|
|||||||
csdwn.viewTreeObserver.addOnGlobalLayoutListener(object :
|
csdwn.viewTreeObserver.addOnGlobalLayoutListener(object :
|
||||||
ViewTreeObserver.OnGlobalLayoutListener {
|
ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
override fun onGlobalLayout() {
|
override fun onGlobalLayout() {
|
||||||
cdwnHeight = csdwn.height
|
cdwnWidth = csdwn.width
|
||||||
Log.d("MyDl", "Get csdwn height: $cdwnHeight")
|
|
||||||
csdwn.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
csdwn.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
dllazys.onScrollListener = object : LazyScrollView.OnScrollListener {
|
dllazys.onScrollListener = object : LazyScrollView.OnScrollListener {
|
||||||
override fun onBottom() {
|
override fun onBottom() {}
|
||||||
if (csdwn.translationY > 0f) showDlCard()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onScroll() {
|
override fun onScroll() {
|
||||||
if (csdwn.translationY == 0f) hideDlCard()
|
if (csdwn.translationY == 0f) hideDlCard()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTop() {
|
override fun onTop() {}
|
||||||
if (csdwn.translationY > 0f) showDlCard()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
cdwn.setOnClickListener {
|
cdwn.setOnClickListener {
|
||||||
pdwn.progress = 0
|
if (csdwn.translationX != 0f) showDlCard()
|
||||||
if (canDl || checkedChapter == 0) canDl = false
|
else if (checkedChapter == 0) hideDlCard()
|
||||||
else {
|
else {
|
||||||
haveDlStarted = true
|
pdwn.progress = 0
|
||||||
canDl = true
|
if (canDl || checkedChapter == 0) canDl = false
|
||||||
handler.sendEmptyMessage(9)
|
else {
|
||||||
Toast.makeText(this, "准备下载...", Toast.LENGTH_SHORT).show()
|
haveDlStarted = true
|
||||||
fillChapters()
|
canDl = true
|
||||||
Thread { dlThead { downloadChapterPages(it) } }.start()
|
handler.sendEmptyMessage(9)
|
||||||
|
Toast.makeText(this, "准备下载...", Toast.LENGTH_SHORT).show()
|
||||||
|
fillChapters()
|
||||||
|
Thread { dlThead { downloadChapterPages(it) } }.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cdwn.setOnLongClickListener {
|
cdwn.setOnLongClickListener {
|
||||||
@@ -177,7 +178,6 @@ class DlActivity : Activity() {
|
|||||||
handler.obtainMessage(if (succeed) 1 else -1, tbtnlist.indexOf(i), 0)
|
handler.obtainMessage(if (succeed) 1 else -1, tbtnlist.indexOf(i), 0)
|
||||||
.sendToTarget()
|
.sendToTarget()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleMessage(succeed: Boolean, pageNow: Int) {
|
override fun handleMessage(succeed: Boolean, pageNow: Int) {
|
||||||
handler.obtainMessage(
|
handler.obtainMessage(
|
||||||
5,
|
5,
|
||||||
@@ -186,6 +186,13 @@ class DlActivity : Activity() {
|
|||||||
succeed
|
succeed
|
||||||
).sendToTarget()
|
).sendToTarget()
|
||||||
}
|
}
|
||||||
|
override fun handleMessage(pageNow: Int){
|
||||||
|
handler.obtainMessage(
|
||||||
|
10,
|
||||||
|
tbtnlist.indexOf(i),
|
||||||
|
pageNow
|
||||||
|
).sendToTarget()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mangaDlTools.dlChapterAndPackIntoZip(
|
mangaDlTools.dlChapterAndPackIntoZip(
|
||||||
File("${getExternalFilesDir("")}/$comicName/${i.hint}/${i.textOn}.zip"),
|
File("${getExternalFilesDir("")}/$comicName/${i.hint}/${i.textOn}.zip"),
|
||||||
@@ -231,13 +238,12 @@ class DlActivity : Activity() {
|
|||||||
null,
|
null,
|
||||||
"取消",
|
"取消",
|
||||||
{
|
{
|
||||||
|
if (checkedChapter == 0) {
|
||||||
|
it.tbtn.isChecked = true
|
||||||
|
tdwn.text = "$dldChapter/${++checkedChapter}"
|
||||||
|
}
|
||||||
Thread {
|
Thread {
|
||||||
handler.obtainMessage(
|
handler.sendEmptyMessage(7)
|
||||||
7,
|
|
||||||
tbtnlist.indexOf(it.tbtn),
|
|
||||||
0,
|
|
||||||
zipf
|
|
||||||
).sendToTarget()
|
|
||||||
}.start()
|
}.start()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -245,11 +251,10 @@ class DlActivity : Activity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteChapters(zipf: File, index: Int) {
|
fun deleteChapters() {
|
||||||
for (i in tbtnlist) {
|
for (i in tbtnlist) {
|
||||||
if (i.isChecked) {
|
if (i.isChecked) {
|
||||||
val f =
|
val f = File("${getExternalFilesDir("")}/$comicName/${i.hint}/${i.textOn}.zip")
|
||||||
File("${getExternalFilesDir("")}/$comicName/${i.hint}/${i.textOn}.zip")
|
|
||||||
if (f.exists()) {
|
if (f.exists()) {
|
||||||
deleteChapter(f, i)
|
deleteChapter(f, i)
|
||||||
checkedChapter--
|
checkedChapter--
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package top.fumiama.copymanga.activity
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import kotlinx.android.synthetic.main.activity_dlist.*
|
||||||
|
import kotlinx.android.synthetic.main.widget_titlebar.*
|
||||||
|
import top.fumiama.copymanga.R
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class DlListActivity:Activity() {
|
||||||
|
private var exDir: File? = null
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_dlist)
|
||||||
|
val titleText = intent.getStringExtra("title")
|
||||||
|
ttitle.text = titleText?.substringAfterLast("/")
|
||||||
|
exDir = getExternalFilesDir("")
|
||||||
|
val innerDir = titleText?.substringAfter("我的下载")
|
||||||
|
File(exDir, innerDir?:"").list()?.let {
|
||||||
|
mylv.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, it)
|
||||||
|
mylv.setOnItemClickListener { _, _, position, _ ->
|
||||||
|
val chosenFile = File(exDir, "$innerDir/${it[position]}")
|
||||||
|
val newTitle = "$titleText/${it[position]}"
|
||||||
|
//Toast.makeText(this, "进入$chosenFile", Toast.LENGTH_SHORT).show()
|
||||||
|
if (chosenFile.isDirectory) startActivity(
|
||||||
|
Intent(
|
||||||
|
this,
|
||||||
|
DlListActivity::class.java
|
||||||
|
).putExtra("title", newTitle)
|
||||||
|
)
|
||||||
|
else{
|
||||||
|
ViewMangaActivity.zipFile = chosenFile
|
||||||
|
ViewMangaActivity.titleText = it[position]
|
||||||
|
startActivity(Intent(this, ViewMangaActivity::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import android.webkit.WebView
|
|||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import top.fumiama.copymanga.R
|
import top.fumiama.copymanga.R
|
||||||
import top.fumiama.copymanga.handler.MainHandler
|
import top.fumiama.copymanga.handler.MainHandler
|
||||||
|
import top.fumiama.copymanga.tool.ToolsBox
|
||||||
import top.fumiama.copymanga.view.JSWebView
|
import top.fumiama.copymanga.view.JSWebView
|
||||||
import top.fumiama.copymanga.web.JS
|
import top.fumiama.copymanga.web.JS
|
||||||
import top.fumiama.copymanga.web.JSHidden
|
import top.fumiama.copymanga.web.JSHidden
|
||||||
@@ -18,23 +19,30 @@ import java.lang.ref.WeakReference
|
|||||||
|
|
||||||
class MainActivity: Activity() {
|
class MainActivity: Activity() {
|
||||||
var wh: JSWebView? = null
|
var wh: JSWebView? = null
|
||||||
|
var toolsBox: ToolsBox? = null
|
||||||
@SuppressLint("JavascriptInterface")
|
@SuppressLint("JavascriptInterface")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
wm = WeakReference(this)
|
wm = WeakReference(this)
|
||||||
mh = Looper.myLooper()?.let { MainHandler(it) }
|
mh = MainHandler(Looper.getMainLooper())
|
||||||
|
toolsBox = ToolsBox(wm as WeakReference<Any>)
|
||||||
|
toolsBox?.netinfo?.let {
|
||||||
|
if(it == "无网络" || it == "错误"){
|
||||||
|
Thread{mh?.sendEmptyMessage(6)}.start()
|
||||||
|
}else{
|
||||||
|
WebView.setWebContentsDebuggingEnabled(true)
|
||||||
|
w.setWebViewClient("i.js")
|
||||||
|
w.webChromeClient = WebChromeClient()
|
||||||
|
w.loadJSInterface(JS())
|
||||||
|
w.loadUrl(getString(R.string.web_home))
|
||||||
|
|
||||||
WebView.setWebContentsDebuggingEnabled(true)
|
wh = JSWebView(this, getString(R.string.pc_ua))
|
||||||
w.setWebViewClient("i.js")
|
wh?.setWebViewClient("h.js")
|
||||||
w.webChromeClient = WebChromeClient()
|
wh?.loadJSInterface(JSHidden())
|
||||||
w.loadJSInterface(JS())
|
}
|
||||||
w.loadUrl(getString(R.string.web_home))
|
}
|
||||||
|
|
||||||
wh = JSWebView(this, getString(R.string.pc_ua))
|
|
||||||
wh?.setWebViewClient("h.js")
|
|
||||||
wh?.loadJSInterface(JSHidden())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -43,7 +51,10 @@ class MainActivity: Activity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onFabClicked(v: View){
|
fun onFabClicked(v: View){
|
||||||
startActivity(Intent(this, DlActivity::class.java))
|
startActivity(
|
||||||
|
Intent(this, (if(mh?.showDlList == true) DlListActivity::class else DlActivity::class).java)
|
||||||
|
.putExtra("title", "./我的下载")
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ package top.fumiama.copymanga.activity
|
|||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Message
|
import android.os.Message
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -31,6 +34,8 @@ import java.io.File
|
|||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
|
||||||
class ViewMangaActivity : Activity() {
|
class ViewMangaActivity : Activity() {
|
||||||
@@ -47,6 +52,8 @@ class ViewMangaActivity : Activity() {
|
|||||||
var infoDrawerDelta = 0f
|
var infoDrawerDelta = 0f
|
||||||
lateinit var toolsBox: ToolsBox
|
lateinit var toolsBox: ToolsBox
|
||||||
private lateinit var p: PropertiesTools
|
private lateinit var p: PropertiesTools
|
||||||
|
private var mangaZip = zipFile
|
||||||
|
private val dlZip2View = mangaZip != null
|
||||||
var pageNum = 1
|
var pageNum = 1
|
||||||
get() {
|
get() {
|
||||||
field = getPageNumber()
|
field = getPageNumber()
|
||||||
@@ -85,14 +92,15 @@ class ViewMangaActivity : Activity() {
|
|||||||
tt.canDo = true
|
tt.canDo = true
|
||||||
tt.start()
|
tt.start()
|
||||||
ttitle.text = titleText
|
ttitle.text = titleText
|
||||||
|
isearch.visibility = View.VISIBLE
|
||||||
try {
|
try {
|
||||||
count = imgUrls.size
|
count = if (mangaZip != null) countZipItems() else imgUrls.size
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
toolsBox.toastError("分析图片url错误")
|
toolsBox.toastError("分析图片url错误")
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
prepareItems(count)
|
prepareItems()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
toolsBox.toastError("准备控件错误")
|
toolsBox.toastError("准备控件错误")
|
||||||
@@ -115,10 +123,28 @@ class ViewMangaActivity : Activity() {
|
|||||||
else if (notUseVP) currentItem = num - 1 else vp.currentItem = num - 1
|
else if (notUseVP) currentItem = num - 1 else vp.currentItem = num - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getImgBitmap(position: Int): Bitmap? {
|
||||||
|
if (position >= count || position < 0) return null
|
||||||
|
else {
|
||||||
|
val zip = ZipFile(mangaZip)
|
||||||
|
//if (q == 100)
|
||||||
|
return BitmapFactory.decodeStream(zip.getInputStream(zip.getEntry("${position}.webp")))
|
||||||
|
/*else {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
try {
|
||||||
|
BitmapFactory.decodeStream(zip.getInputStream(zip.getEntry("${position}.jpg")))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return null
|
||||||
|
}?.compress(Bitmap.CompressFormat.JPEG, q, out)
|
||||||
|
return BitmapFactory.decodeStream(ByteArrayInputStream(out.toByteArray()))
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadOneImg() {
|
private fun loadOneImg() {
|
||||||
Glide.with(this@ViewMangaActivity).load(
|
if(dlZip2View) onei.setImageBitmap(getImgBitmap(currentItem))
|
||||||
imgUrls[currentItem]
|
else Glide.with(this@ViewMangaActivity).load(imgUrls[currentItem]).placeholder(R.drawable.bg_comment).into(onei)
|
||||||
).placeholder(R.drawable.bg_comment).into(onei)
|
|
||||||
updateSeekBar()
|
updateSeekBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,9 +154,9 @@ class ViewMangaActivity : Activity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
private fun prepareItems(size: Int) {
|
private fun prepareItems() {
|
||||||
prepareVP()
|
prepareVP()
|
||||||
prepareInfoBar(size)
|
prepareInfoBar(count)
|
||||||
if (notUseVP) loadOneImg() else prepareIdBtVH()
|
if (notUseVP) loadOneImg() else prepareIdBtVH()
|
||||||
toolsBox.dp2px(67)?.let { setIdPosition(it) }
|
toolsBox.dp2px(67)?.let { setIdPosition(it) }
|
||||||
prepareIdBtFullScreen()
|
prepareIdBtFullScreen()
|
||||||
@@ -233,6 +259,28 @@ class ViewMangaActivity : Activity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun countZipItems(): Int {
|
||||||
|
var c = 0
|
||||||
|
try {
|
||||||
|
val exist = mangaZip?.exists() == true
|
||||||
|
if (!exist) return 0
|
||||||
|
else {
|
||||||
|
Log.d("Myvm", "zipf: $mangaZip")
|
||||||
|
val zip = ZipInputStream(mangaZip?.inputStream()?.buffered())
|
||||||
|
var entry = zip.nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
if (!entry.isDirectory) c++
|
||||||
|
entry = zip.nextEntry
|
||||||
|
}
|
||||||
|
zip.closeEntry()
|
||||||
|
zip.close()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toolsBox.toastError("读取zip错误!")
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
fun scrollBack() {
|
fun scrollBack() {
|
||||||
pageNum--
|
pageNum--
|
||||||
}
|
}
|
||||||
@@ -278,7 +326,11 @@ class ViewMangaActivity : Activity() {
|
|||||||
@SuppressLint("ClickableViewAccessibility", "SetTextI18n")
|
@SuppressLint("ClickableViewAccessibility", "SetTextI18n")
|
||||||
override fun onBindViewHolder(holder: ViewData, position: Int) {
|
override fun onBindViewHolder(holder: ViewData, position: Int) {
|
||||||
val pos = if (r2l) count - position - 1 else position
|
val pos = if (r2l) count - position - 1 else position
|
||||||
Glide.with(this@ViewMangaActivity).load(imgUrls[pos]).placeholder(R.drawable.bg_comment).into(holder.itemView.onei)
|
if(dlZip2View) getImgBitmap(pos)?.let {
|
||||||
|
//Glide.with(this@ViewMangaActivity).load(it).placeholder(R.drawable.bg_comment).into(holder.itemView.onei)
|
||||||
|
holder.itemView.onei.setImageBitmap(it)
|
||||||
|
}
|
||||||
|
else Glide.with(this@ViewMangaActivity).load(imgUrls[pos]).placeholder(R.drawable.bg_comment).timeout(10000).into(holder.itemView.onei)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
@@ -359,8 +411,24 @@ class ViewMangaActivity : Activity() {
|
|||||||
companion object {
|
companion object {
|
||||||
var va: WeakReference<ViewMangaActivity>? = null
|
var va: WeakReference<ViewMangaActivity>? = null
|
||||||
var imgUrls = arrayOf<String>()
|
var imgUrls = arrayOf<String>()
|
||||||
|
var zipFile: File? = null
|
||||||
|
get() {
|
||||||
|
val re = field
|
||||||
|
if(field != null) field = null
|
||||||
|
return re
|
||||||
|
}
|
||||||
var titleText = "Null"
|
var titleText = "Null"
|
||||||
var nextChapterUrl: String? = null
|
var nextChapterUrl: String? = null
|
||||||
|
get() {
|
||||||
|
val re = field
|
||||||
|
if(field != null) field = null
|
||||||
|
return re
|
||||||
|
}
|
||||||
var previousChapterUrl: String? = null
|
var previousChapterUrl: String? = null
|
||||||
|
get() {
|
||||||
|
val re = field
|
||||||
|
if(field != null) field = null
|
||||||
|
return re
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package top.fumiama.copymanga.handler
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.os.Message
|
import android.os.Message
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import kotlinx.android.synthetic.main.widget_downloadbar.*
|
import kotlinx.android.synthetic.main.widget_downloadbar.*
|
||||||
@@ -38,11 +39,13 @@ class DlHandler(activity: DlActivity) : Handler() {
|
|||||||
-1 -> {
|
-1 -> {
|
||||||
d?.tbtnlist?.get(msg.arg1)?.setBackgroundResource(R.drawable.rndbg_error)
|
d?.tbtnlist?.get(msg.arg1)?.setBackgroundResource(R.drawable.rndbg_error)
|
||||||
d!!.dldChapter--
|
d!!.dldChapter--
|
||||||
|
//Looper.prepare()
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
d,
|
d,
|
||||||
"下载${d.tbtnlist[msg.arg1].textOn}失败",
|
"下载${d.tbtnlist[msg.arg1].textOn}失败",
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
|
//Looper.loop()
|
||||||
d.updateProgressBar()
|
d.updateProgressBar()
|
||||||
}
|
}
|
||||||
4 -> {
|
4 -> {
|
||||||
@@ -74,17 +77,28 @@ class DlHandler(activity: DlActivity) : Handler() {
|
|||||||
?.getImgsCountByHash(d.tbtnUrlList[msg.arg1].substringAfterLast("/")) ?: 0
|
?.getImgsCountByHash(d.tbtnUrlList[msg.arg1].substringAfterLast("/")) ?: 0
|
||||||
)
|
)
|
||||||
if (!(msg.obj as Boolean)) {
|
if (!(msg.obj as Boolean)) {
|
||||||
|
//Looper.prepare()
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
d,
|
d,
|
||||||
"下载${d?.tbtnlist?.get(msg.arg1)?.textOn}的第${msg.arg2}页失败",
|
"下载${d?.tbtnlist?.get(msg.arg1)?.textOn}的第${msg.arg2}页失败",
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
|
//Looper.loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
6 -> d?.tdwn?.text = "${d?.dldChapter}/${d?.checkedChapter}"
|
6 -> d?.tdwn?.text = "${d?.dldChapter}/${d?.checkedChapter}"
|
||||||
7 -> d?.deleteChapters(msg.obj as File, msg.arg1)
|
7 -> d?.deleteChapters()
|
||||||
8 -> d?.cdwn?.setCardBackgroundColor(d.resources.getColor(R.color.colorBlue))
|
8 -> d?.cdwn?.setCardBackgroundColor(d.resources.getColor(R.color.colorBlue))
|
||||||
9 -> d?.cdwn?.setCardBackgroundColor(d.resources.getColor(R.color.colorRed))
|
9 -> d?.cdwn?.setCardBackgroundColor(d.resources.getColor(R.color.colorRed))
|
||||||
|
10 -> {
|
||||||
|
//Looper.prepare()
|
||||||
|
Toast.makeText(
|
||||||
|
d,
|
||||||
|
"下载${d?.tbtnlist?.get(msg.arg1)?.textOn}的第${msg.arg2}页失败,尝试重新下载...",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
//Looper.loop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ import top.fumiama.copymanga.tool.MangaDlTools.Companion.wmdlt
|
|||||||
|
|
||||||
class MainHandler(looper: Looper):Handler(looper) {
|
class MainHandler(looper: Looper):Handler(looper) {
|
||||||
var saveUrlsOnly = false
|
var saveUrlsOnly = false
|
||||||
|
var showDlList = false
|
||||||
override fun handleMessage(msg: Message) {
|
override fun handleMessage(msg: Message) {
|
||||||
super.handleMessage(msg)
|
super.handleMessage(msg)
|
||||||
when(msg.what){
|
when(msg.what){
|
||||||
@@ -27,6 +28,7 @@ class MainHandler(looper: Looper):Handler(looper) {
|
|||||||
3 -> updateLoadProgress(msg.arg1)
|
3 -> updateLoadProgress(msg.arg1)
|
||||||
4 -> setFab(msg.obj as String)
|
4 -> setFab(msg.obj as String)
|
||||||
5 -> hideFab()
|
5 -> hideFab()
|
||||||
|
6 -> setFab2DlList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private fun loadUrlInHiddenWebView(url: String){wm?.get()?.wh?.loadUrl(url)}
|
private fun loadUrlInHiddenWebView(url: String){wm?.get()?.wh?.loadUrl(url)}
|
||||||
@@ -55,10 +57,16 @@ class MainHandler(looper: Looper):Handler(looper) {
|
|||||||
if(progress == 100) it.pw.postDelayed({it.pw.visibility = View.GONE}, 500)
|
if(progress == 100) it.pw.postDelayed({it.pw.visibility = View.GONE}, 500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private fun showFab() {wm?.get()?.fab?.visibility = View.VISIBLE}
|
||||||
|
private fun hideFab() {wm?.get()?.fab?.visibility = View.GONE}
|
||||||
private fun setFab(content: String){
|
private fun setFab(content: String){
|
||||||
//Log.d("MyMH", "Get chapter json: $content")
|
//Log.d("MyMH", "Get chapter json: $content")
|
||||||
|
showDlList = false
|
||||||
comicStructure = Gson().fromJson(content.reader(), Array<ComicStructure>::class.java)
|
comicStructure = Gson().fromJson(content.reader(), Array<ComicStructure>::class.java)
|
||||||
wm?.get()?.fab?.visibility = View.VISIBLE
|
showFab()
|
||||||
|
}
|
||||||
|
private fun setFab2DlList(){
|
||||||
|
showDlList = true
|
||||||
|
showFab()
|
||||||
}
|
}
|
||||||
private fun hideFab() {wm?.get()?.fab?.visibility = View.GONE}
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
package top.fumiama.copymanga.tool
|
package top.fumiama.copymanga.tool
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import java.io.File
|
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.Callable
|
import java.util.concurrent.Callable
|
||||||
import java.util.concurrent.FutureTask
|
import java.util.concurrent.FutureTask
|
||||||
|
|
||||||
class DownloadTools {
|
class DownloadTools {
|
||||||
fun getHttpContent(Url: String, refer: String? = null): ByteArray? {
|
fun getHttpContent(Url: String, refer: String? = null, ua: String? = null): ByteArray? {
|
||||||
Log.d("Mydl", "getHttp: $Url")
|
Log.d("Mydl", "getHttp: $Url")
|
||||||
var ret: ByteArray? = null
|
var ret: ByteArray? = null
|
||||||
val task = FutureTask(Callable {
|
val task = FutureTask(Callable {
|
||||||
@@ -18,6 +17,7 @@ class DownloadTools {
|
|||||||
connection.connectTimeout = 10000
|
connection.connectTimeout = 10000
|
||||||
connection.readTimeout = 10000
|
connection.readTimeout = 10000
|
||||||
refer?.let { connection.setRequestProperty("referer", it) }
|
refer?.let { connection.setRequestProperty("referer", it) }
|
||||||
|
ua?.let { connection.setRequestProperty("User-agent", it) }
|
||||||
|
|
||||||
ret = connection.inputStream.readBytes()
|
ret = connection.inputStream.readBytes()
|
||||||
connection.disconnect()
|
connection.disconnect()
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package top.fumiama.copymanga.tool
|
package top.fumiama.copymanga.tool
|
||||||
|
|
||||||
|
import android.os.Looper
|
||||||
|
import android.widget.Toast
|
||||||
import top.fumiama.copymanga.R
|
import top.fumiama.copymanga.R
|
||||||
import top.fumiama.copymanga.activity.DlActivity
|
import top.fumiama.copymanga.activity.DlActivity
|
||||||
import top.fumiama.copymanga.data.ComicStructure
|
import top.fumiama.copymanga.data.ComicStructure
|
||||||
import top.fumiama.copymanga.view.JSWebView
|
import top.fumiama.copymanga.view.JSWebView
|
||||||
import top.fumiama.copymanga.web.JSHidden
|
import top.fumiama.copymanga.web.JSHidden
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.Thread.sleep
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.zip.CRC32
|
import java.util.zip.CRC32
|
||||||
import java.util.zip.CheckedOutputStream
|
import java.util.zip.CheckedOutputStream
|
||||||
@@ -60,8 +63,16 @@ class MangaDlTools(activity: DlActivity) {
|
|||||||
var succeed = true
|
var succeed = true
|
||||||
for (i in it.indices) {
|
for (i in it.indices) {
|
||||||
zip.putNextEntry(ZipEntry("$i.webp"))
|
zip.putNextEntry(ZipEntry("$i.webp"))
|
||||||
val s = dl.getHttpContent(it[i])?.let { zip.write(it); true } ?: false
|
var tryTimes = 3
|
||||||
if (!s) succeed = s
|
var s = false
|
||||||
|
while (!s && tryTimes-- > 0){
|
||||||
|
s = dl.getHttpContent(it[i], d?.getString(R.string.web_home_www), d?.getString(R.string.pc_ua))?.let { zip.write(it); true } ?: false
|
||||||
|
if (!s) {
|
||||||
|
onDownloadedListener?.handleMessage(i + 1)
|
||||||
|
sleep(2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(tryTimes == 0) succeed = false
|
||||||
onDownloadedListener?.handleMessage(s, i + 1)
|
onDownloadedListener?.handleMessage(s, i + 1)
|
||||||
zip.flush()
|
zip.flush()
|
||||||
if (exit) break
|
if (exit) break
|
||||||
@@ -76,6 +87,7 @@ class MangaDlTools(activity: DlActivity) {
|
|||||||
interface OnDownloadedListener {
|
interface OnDownloadedListener {
|
||||||
fun handleMessage(succeed: Boolean)
|
fun handleMessage(succeed: Boolean)
|
||||||
fun handleMessage(succeed: Boolean, pageNow: Int)
|
fun handleMessage(succeed: Boolean, pageNow: Int)
|
||||||
|
fun handleMessage(pageNow: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -22,4 +22,8 @@ class JS {
|
|||||||
fun hideFab(){
|
fun hideFab(){
|
||||||
Thread{mh?.sendEmptyMessage(5)}.start()
|
Thread{mh?.sendEmptyMessage(5)}.start()
|
||||||
}
|
}
|
||||||
|
@JavascriptInterface
|
||||||
|
fun enterProfile(){
|
||||||
|
Thread{mh?.sendEmptyMessage(6)}.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
26
app/src/main/res/layout/activity_dlist.xml
Normal file
26
app/src/main/res/layout/activity_dlist.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#FCFCFF">
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/myt"
|
||||||
|
layout="@layout/widget_titlebar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/mylv"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/myt" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@@ -23,13 +24,16 @@
|
|||||||
android:layout_width="64dp"
|
android:layout_width="64dp"
|
||||||
android:layout_height="64dp"
|
android:layout_height="64dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:layout_marginBottom="24dp"
|
android:alpha="0.8"
|
||||||
android:background="@drawable/rndbg_round"
|
android:background="@drawable/rndbg_round"
|
||||||
android:foreground="@drawable/ic_dl"
|
android:foreground="@drawable/ic_dl"
|
||||||
android:foregroundGravity="center"
|
android:foregroundGravity="center"
|
||||||
android:onClick="onFabClicked"
|
android:onClick="onFabClicked"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/w" />
|
app:layout_constraintEnd_toEndOf="@+id/w"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.275"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
android:layout_height="28dp"
|
android:layout_height="28dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:src="@drawable/ic_edit"
|
android:src="@drawable/ic_edit"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.4.10"
|
ext.kotlin_version = '1.4.21'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
classpath 'com.android.tools.build:gradle:4.1.1'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.19'
|
classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.20'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|||||||
Reference in New Issue
Block a user