Download added
This commit is contained in:
@@ -29,6 +29,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0"
|
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0"
|
||||||
|
|||||||
@@ -25,7 +25,8 @@
|
|||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<activity android:name=".ReaderActivity"
|
<activity android:name=".ReaderActivity"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"/>
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:configChanges="keyboardHidden|orientation|screenSize"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:label="@string/settings_title" />
|
android:label="@string/settings_title" />
|
||||||
|
|||||||
@@ -218,7 +218,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
with(main_recyclerview) {
|
with(main_recyclerview) {
|
||||||
adapter = GalleryBlockAdapter(galleries)
|
adapter = GalleryBlockAdapter(galleries).apply {
|
||||||
|
|
||||||
|
}
|
||||||
addOnScrollListener(
|
addOnScrollListener(
|
||||||
object: RecyclerView.OnScrollListener() {
|
object: RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
@@ -235,8 +237,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
ItemClickSupport.addTo(this).setOnItemClickListener { _, position, _ ->
|
ItemClickSupport.addTo(this).setOnItemClickListener { _, position, _ ->
|
||||||
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
|
||||||
val gallery = galleries[position].first
|
val gallery = galleries[position].first
|
||||||
intent.putExtra("GALLERY_ID", gallery.id)
|
intent.putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery))
|
||||||
intent.putExtra("GALLERY_TITLE", gallery.title)
|
|
||||||
|
|
||||||
//TODO: Maybe sprinke some transitions will be nice :D
|
//TODO: Maybe sprinke some transitions will be nice :D
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
|
|||||||
@@ -1,21 +1,40 @@
|
|||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
import android.content.Intent
|
import android.app.Notification
|
||||||
import android.os.Process
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
import android.util.SparseArray
|
||||||
import com.finotes.android.finotescore.Fn
|
import com.finotes.android.finotescore.Fn
|
||||||
import com.finotes.android.finotescore.ObservableApplication
|
import com.finotes.android.finotescore.ObservableApplication
|
||||||
import com.finotes.android.finotescore.Severity
|
import com.finotes.android.finotescore.Severity
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
|
||||||
class Pupil : ObservableApplication() {
|
class Pupil : ObservableApplication() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Fn.init(this)
|
Fn.init(this)
|
||||||
|
|
||||||
Fn.enableFrameDetection()
|
Fn.enableFrameDetection()
|
||||||
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
if (!preference.getBoolean("channel_created", false)) {
|
||||||
Fn.reportException(t, Exception(e), Severity.FATAL)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
|
||||||
|
description = getString(R.string.channel_download_description)
|
||||||
|
enableLights(false)
|
||||||
|
enableVibration(false)
|
||||||
|
lockscreenVisibility = Notification.VISIBILITY_SECRET
|
||||||
|
}
|
||||||
|
manager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
preference.edit().putBoolean("channel_created", true).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -10,37 +10,33 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||||
import androidx.recyclerview.widget.PagerSnapHelper
|
import androidx.recyclerview.widget.PagerSnapHelper
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import kotlinx.android.synthetic.main.activity_reader.*
|
import kotlinx.android.synthetic.main.activity_reader.*
|
||||||
import kotlinx.android.synthetic.main.activity_reader.view.*
|
import kotlinx.android.synthetic.main.activity_reader.view.*
|
||||||
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.io.IOException
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
import kotlinx.serialization.list
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
|
||||||
import xyz.quaver.hitomi.ReaderItem
|
|
||||||
import xyz.quaver.hitomi.getReader
|
|
||||||
import xyz.quaver.hitomi.getReferer
|
|
||||||
import xyz.quaver.pupil.adapters.ReaderAdapter
|
import xyz.quaver.pupil.adapters.ReaderAdapter
|
||||||
|
import xyz.quaver.pupil.util.GalleryDownloader
|
||||||
import xyz.quaver.pupil.util.ItemClickSupport
|
import xyz.quaver.pupil.util.ItemClickSupport
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.net.URL
|
|
||||||
import javax.net.ssl.HttpsURLConnection
|
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val images = ArrayList<String>()
|
private val images = ArrayList<String>()
|
||||||
private var galleryID = 0
|
private lateinit var galleryBlock: GalleryBlock
|
||||||
private var gallerySize: Int = 0
|
private var gallerySize = 0
|
||||||
private var currentPage: Int = 0
|
private var currentPage = 0
|
||||||
private lateinit var reader: Deferred<Reader>
|
|
||||||
private var loadJob: Job? = null
|
|
||||||
|
|
||||||
private var isScroll = true
|
private var isScroll = true
|
||||||
private var isFullscreen = false
|
private var isFullscreen = false
|
||||||
|
|
||||||
|
private lateinit var downloader: GalleryDownloader
|
||||||
|
|
||||||
private val snapHelper = PagerSnapHelper()
|
private val snapHelper = PagerSnapHelper()
|
||||||
|
|
||||||
private var menu: Menu? = null
|
private var menu: Menu? = null
|
||||||
@@ -54,54 +50,20 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
setContentView(R.layout.activity_reader)
|
setContentView(R.layout.activity_reader)
|
||||||
|
|
||||||
supportActionBar?.title = intent.getStringExtra("GALLERY_TITLE")
|
galleryBlock = Json(JsonConfiguration.Stable).parse(
|
||||||
|
GalleryBlock.serializer(),
|
||||||
|
intent.getStringExtra("galleryblock")
|
||||||
|
)
|
||||||
|
|
||||||
galleryID = intent.getIntExtra("GALLERY_ID", 0)
|
supportActionBar?.title = galleryBlock.title
|
||||||
reader = CoroutineScope(Dispatchers.IO).async {
|
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
val json = Json(JsonConfiguration.Stable)
|
|
||||||
val serializer = ReaderItem.serializer().list
|
|
||||||
val preference = PreferenceManager.getDefaultSharedPreferences(this@ReaderActivity)
|
|
||||||
val isHiyobi = preference.getBoolean("use_hiyobi", false)
|
|
||||||
|
|
||||||
val cache = when {
|
initDownloader()
|
||||||
isHiyobi -> File(cacheDir, "imageCache/$galleryID/reader-hiyobi.json")
|
|
||||||
else -> File(cacheDir, "imageCache/$galleryID/reader.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cache.exists()) {
|
|
||||||
val cached = json.parse(serializer, cache.readText())
|
|
||||||
|
|
||||||
if (cached.isNotEmpty())
|
|
||||||
return@async cached
|
|
||||||
}
|
|
||||||
|
|
||||||
val reader = when {
|
|
||||||
isHiyobi -> {
|
|
||||||
xyz.quaver.hiyobi.getReader(galleryID).let {
|
|
||||||
when {
|
|
||||||
it.isEmpty() -> getReader(galleryID)
|
|
||||||
else -> it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
getReader(galleryID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reader.isEmpty())
|
|
||||||
finish()
|
|
||||||
|
|
||||||
if (!cache.parentFile.exists())
|
|
||||||
cache.parentFile.mkdirs()
|
|
||||||
|
|
||||||
cache.writeText(json.stringify(serializer, reader))
|
|
||||||
|
|
||||||
reader
|
|
||||||
}
|
|
||||||
|
|
||||||
initView()
|
initView()
|
||||||
loadImages()
|
|
||||||
|
if (!downloader.notify)
|
||||||
|
downloader.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -149,6 +111,9 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
|
if (!downloader.notify)
|
||||||
|
downloader.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
@@ -166,6 +131,85 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initDownloader() {
|
||||||
|
var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id)
|
||||||
|
|
||||||
|
if (d == null) {
|
||||||
|
d = GalleryDownloader(this, galleryBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
downloader = d.apply {
|
||||||
|
onReaderLoadedHandler = {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
with(reader_progressbar) {
|
||||||
|
max = it.size
|
||||||
|
progress = 0
|
||||||
|
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
gallerySize = it.size
|
||||||
|
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.size}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onProgressHandler = {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
reader_progressbar.progress = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onDownloadedHandler = {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
if (images.isEmpty()) {
|
||||||
|
images.addAll(it)
|
||||||
|
reader_recyclerview.adapter?.notifyDataSetChanged()
|
||||||
|
} else {
|
||||||
|
images.add(it.last())
|
||||||
|
reader_recyclerview.adapter?.notifyItemInserted(images.size-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onErrorHandler = {
|
||||||
|
downloader.notify = false
|
||||||
|
}
|
||||||
|
onCompleteHandler = {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
reader_progressbar.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onNotifyChangedHandler = { notify ->
|
||||||
|
val fab = reader_fab_download
|
||||||
|
|
||||||
|
if (notify) {
|
||||||
|
val icon = AnimatedVectorDrawableCompat.create(this, R.drawable.ic_downloading)
|
||||||
|
icon?.registerAnimationCallback(object: Animatable2Compat.AnimationCallback() {
|
||||||
|
override fun onAnimationEnd(drawable: Drawable?) {
|
||||||
|
if (downloader.notify)
|
||||||
|
fab.post {
|
||||||
|
icon.start()
|
||||||
|
fab.labelText = getString(R.string.reader_fab_download_cancel)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fab.post {
|
||||||
|
fab.setImageResource(R.drawable.ic_download)
|
||||||
|
fab.labelText = getString(R.string.reader_fab_download)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fab.setImageDrawable(icon)
|
||||||
|
icon?.start()
|
||||||
|
} else {
|
||||||
|
fab.setImageResource(R.drawable.ic_download)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloader.notify) {
|
||||||
|
downloader.invokeOnReaderLoaded()
|
||||||
|
downloader.invokeOnNotifyChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
with(reader_recyclerview) {
|
with(reader_recyclerview) {
|
||||||
adapter = ReaderAdapter(images)
|
adapter = ReaderAdapter(images)
|
||||||
@@ -208,6 +252,13 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
reader_fab.close(true)
|
reader_fab.close(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader_fab_download.setOnClickListener {
|
||||||
|
downloader.notify = !downloader.notify
|
||||||
|
|
||||||
|
if (!downloader.notify)
|
||||||
|
downloader.clearNotification()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fullscreen(isFullscreen: Boolean) {
|
private fun fullscreen(isFullscreen: Boolean) {
|
||||||
@@ -237,68 +288,4 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
|
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadImages() {
|
|
||||||
fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
|
|
||||||
|
|
||||||
loadJob = CoroutineScope(Dispatchers.Default).launch {
|
|
||||||
val reader = reader.await()
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
with(reader_progressbar) {
|
|
||||||
max = reader.size
|
|
||||||
progress = 0
|
|
||||||
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
gallerySize = reader.size
|
|
||||||
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/$gallerySize"
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.chunked(4).forEach { chunked ->
|
|
||||||
chunked.map {
|
|
||||||
async(Dispatchers.IO) {
|
|
||||||
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
|
||||||
|
|
||||||
val fileName: String
|
|
||||||
|
|
||||||
with(url) {
|
|
||||||
fileName = substring(lastIndexOf('/')+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
val cache = File(cacheDir, "/imageCache/$galleryID/$fileName")
|
|
||||||
|
|
||||||
if (!cache.exists())
|
|
||||||
try {
|
|
||||||
with(URL(url).openConnection() as HttpsURLConnection) {
|
|
||||||
setRequestProperty("Referer", getReferer(galleryID))
|
|
||||||
|
|
||||||
if (!cache.parentFile.exists())
|
|
||||||
cache.parentFile.mkdirs()
|
|
||||||
|
|
||||||
inputStream.copyTo(FileOutputStream(cache))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
cache.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.absolutePath
|
|
||||||
}
|
|
||||||
}.forEach {
|
|
||||||
val cache = it.await()
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
images.add(cache)
|
|
||||||
reader_recyclerview.adapter?.notifyItemInserted(images.size - 1)
|
|
||||||
reader_progressbar.progress++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
reader_progressbar.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.util.SparseArray
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -14,9 +15,17 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
|
import kotlinx.serialization.list
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
|
import xyz.quaver.hitomi.ReaderItem
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
|
import java.io.File
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
@@ -35,9 +44,12 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
}
|
}
|
||||||
|
|
||||||
var noMore = false
|
var noMore = false
|
||||||
|
private val refreshTasks = SparseArray<TimerTask>()
|
||||||
|
|
||||||
class ViewHolder(val view: CardView) : RecyclerView.ViewHolder(view)
|
private var onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
|
||||||
class ProgressViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
|
|
||||||
|
class ViewHolder(val view: CardView, var galleryID: Int? = null) : RecyclerView.ViewHolder(view)
|
||||||
|
class ProgressViewHolder(val view: LinearLayout) : RecyclerView.ViewHolder(view)
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
when(viewType) {
|
when(viewType) {
|
||||||
@@ -71,16 +83,62 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
}.toMap()
|
}.toMap()
|
||||||
val (gallery, thumbnail) = galleries[position]
|
val (gallery, thumbnail) = galleries[position]
|
||||||
|
|
||||||
|
holder.galleryID = gallery.id
|
||||||
|
|
||||||
val artists = gallery.artists
|
val artists = gallery.artists
|
||||||
val series = gallery.series
|
val series = gallery.series
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Default).launch {
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
val cache = thumbnail.await()
|
||||||
|
|
||||||
|
if (!File(cache).exists())
|
||||||
|
return@launch
|
||||||
|
|
||||||
val bitmap = BitmapFactory.decodeFile(thumbnail.await())
|
val bitmap = BitmapFactory.decodeFile(thumbnail.await())
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
galleryblock_thumbnail.setImageBitmap(bitmap)
|
galleryblock_thumbnail.setImageBitmap(bitmap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check cache
|
||||||
|
val readerCache = File(context.cacheDir, "imageCache/${gallery.id}/reader.json")
|
||||||
|
val imageCache = File(context.cacheDir, "imageCache/${gallery.id}/images")
|
||||||
|
|
||||||
|
if (readerCache.exists()) {
|
||||||
|
val reader = Json(JsonConfiguration.Stable)
|
||||||
|
.parse(ReaderItem.serializer().list, readerCache.readText())
|
||||||
|
|
||||||
|
with(galleryblock_progressbar) {
|
||||||
|
max = reader.size
|
||||||
|
progress = imageCache.list()?.size ?: 0
|
||||||
|
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
galleryblock_progressbar.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refreshTasks.get(gallery.id) == null) {
|
||||||
|
val refresh = Timer(false).schedule(0, 1000) {
|
||||||
|
this@with.post {
|
||||||
|
val size = imageCache.list()?.size ?: return@post
|
||||||
|
|
||||||
|
with(galleryblock_progressbar) {
|
||||||
|
progress = size
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
val reader = Json(JsonConfiguration.Stable)
|
||||||
|
.parse(ReaderItem.serializer().list, readerCache.readText())
|
||||||
|
max = reader.size
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshTasks.put(gallery.id, refresh)
|
||||||
|
}
|
||||||
|
|
||||||
galleryblock_title.text = gallery.title
|
galleryblock_title.text = gallery.title
|
||||||
with(galleryblock_artist) {
|
with(galleryblock_artist) {
|
||||||
text = artists.joinToString(", ") { it.wordCapitalize() }
|
text = artists.joinToString(", ") { it.wordCapitalize() }
|
||||||
@@ -112,8 +170,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
galleryblock_tag_group.removeAllViews()
|
galleryblock_tag_group.removeAllViews()
|
||||||
gallery.relatedTags.forEach {
|
gallery.relatedTags.forEach {
|
||||||
val tag = Tag.parse(it)
|
val tag = Tag.parse(it)
|
||||||
val chip = LayoutInflater
|
|
||||||
.from(context)
|
val chip = LayoutInflater.from(context)
|
||||||
.inflate(R.layout.tag_chip, holder.view, false) as Chip
|
.inflate(R.layout.tag_chip, holder.view, false) as Chip
|
||||||
|
|
||||||
val icon = when(tag.area) {
|
val icon = when(tag.area) {
|
||||||
@@ -131,15 +189,37 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
|
|||||||
}
|
}
|
||||||
|
|
||||||
chip.chipIcon = icon
|
chip.chipIcon = icon
|
||||||
chip.text = Tag.parse(it).tag.wordCapitalize()
|
chip.text = tag.tag.wordCapitalize()
|
||||||
|
chip.setOnClickListener {
|
||||||
|
for (callback in onChipClickedHandler)
|
||||||
|
callback.invoke(tag)
|
||||||
|
}
|
||||||
|
|
||||||
galleryblock_tag_group.addView(chip)
|
galleryblock_tag_group.addView(chip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (holder is ProgressViewHolder) {
|
||||||
|
holder.view.visibility = when(noMore) {
|
||||||
|
true -> View.GONE
|
||||||
|
false -> View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = if (galleries.isEmpty()) 0 else galleries.size+(if (noMore) 0 else 1)
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||||
|
super.onViewDetachedFromWindow(holder)
|
||||||
|
|
||||||
|
if (holder is ViewHolder) {
|
||||||
|
val galleryID = holder.galleryID ?: return
|
||||||
|
val task = refreshTasks.get(galleryID) ?: return
|
||||||
|
|
||||||
|
refreshTasks.remove(galleryID)
|
||||||
|
task.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = if (galleries.isEmpty()) 0 else galleries.size+1
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return when {
|
return when {
|
||||||
|
|||||||
232
app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
Normal file
232
app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.SparseArray
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.app.TaskStackBuilder
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.io.IOException
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
|
import kotlinx.serialization.list
|
||||||
|
import xyz.quaver.hitomi.*
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.ReaderActivity
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.URL
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
|
class GalleryDownloader(
|
||||||
|
base: Context,
|
||||||
|
private val galleryBlock: GalleryBlock
|
||||||
|
) : ContextWrapper(base) {
|
||||||
|
|
||||||
|
var notify: Boolean = false
|
||||||
|
set(value) {
|
||||||
|
if (value) {
|
||||||
|
field = true
|
||||||
|
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
||||||
|
|
||||||
|
if (downloadJob?.isActive != true)
|
||||||
|
field = false
|
||||||
|
} else {
|
||||||
|
field = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifyChangedHandler?.invoke(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val reader: Deferred<Reader>
|
||||||
|
private var downloadJob: Job? = null
|
||||||
|
|
||||||
|
private lateinit var notificationBuilder: NotificationCompat.Builder
|
||||||
|
private lateinit var notificationManager: NotificationManagerCompat
|
||||||
|
|
||||||
|
var onReaderLoadedHandler: ((Reader) -> Unit)? = null
|
||||||
|
var onProgressHandler: ((Int) -> Unit)? = null
|
||||||
|
var onDownloadedHandler: ((List<String>) -> Unit)? = null
|
||||||
|
var onErrorHandler: (() -> Unit)? = null
|
||||||
|
var onCompleteHandler: (() -> Unit)? = null
|
||||||
|
var onNotifyChangedHandler: ((Boolean) -> Unit)? = null
|
||||||
|
|
||||||
|
companion object : SparseArray<GalleryDownloader>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
put(galleryBlock.id, this)
|
||||||
|
|
||||||
|
initNotification()
|
||||||
|
|
||||||
|
reader = CoroutineScope(Dispatchers.IO).async {
|
||||||
|
val json = Json(JsonConfiguration.Stable)
|
||||||
|
val serializer = ReaderItem.serializer().list
|
||||||
|
val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryDownloader)
|
||||||
|
val useHiyobi = preference.getBoolean("use_hiyobi", false)
|
||||||
|
|
||||||
|
//Check cache
|
||||||
|
val cache = File(cacheDir, "imageCache/${galleryBlock.id}/reader.json")
|
||||||
|
|
||||||
|
if (cache.exists()) {
|
||||||
|
val cached = json.parse(serializer, cache.readText())
|
||||||
|
|
||||||
|
if (cached.isNotEmpty())
|
||||||
|
return@async cached
|
||||||
|
}
|
||||||
|
|
||||||
|
//Cache doesn't exist. Load from internet
|
||||||
|
val reader = when {
|
||||||
|
useHiyobi -> {
|
||||||
|
xyz.quaver.hiyobi.getReader(galleryBlock.id).let {
|
||||||
|
when {
|
||||||
|
it.isEmpty() -> getReader(galleryBlock.id)
|
||||||
|
else -> it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
getReader(galleryBlock.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Could not retrieve reader
|
||||||
|
if (reader.isEmpty())
|
||||||
|
throw IOException("Can't retrieve Reader")
|
||||||
|
|
||||||
|
//Save cache
|
||||||
|
if (!cache.parentFile.exists())
|
||||||
|
cache.parentFile.mkdirs()
|
||||||
|
|
||||||
|
cache.writeText(json.stringify(serializer, reader))
|
||||||
|
|
||||||
|
reader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
downloadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
val reader = reader.await()
|
||||||
|
|
||||||
|
val list = ArrayList<String>()
|
||||||
|
|
||||||
|
onReaderLoadedHandler?.invoke(reader)
|
||||||
|
|
||||||
|
notificationBuilder
|
||||||
|
.setProgress(reader.size, 0, false)
|
||||||
|
.setContentText("0/${reader.size}")
|
||||||
|
|
||||||
|
reader.chunked(4).forEachIndexed { chunkIndex, chunked ->
|
||||||
|
chunked.mapIndexed { i, it ->
|
||||||
|
val index = chunkIndex*4+i
|
||||||
|
|
||||||
|
onProgressHandler?.invoke(index)
|
||||||
|
|
||||||
|
notificationBuilder
|
||||||
|
.setProgress(reader.size, index, false)
|
||||||
|
.setContentText("$index/${reader.size}")
|
||||||
|
|
||||||
|
if (notify)
|
||||||
|
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
||||||
|
|
||||||
|
async(Dispatchers.IO) {
|
||||||
|
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
|
||||||
|
|
||||||
|
val name = "$index".padStart(4, '0')
|
||||||
|
val ext = url.split('.').last()
|
||||||
|
|
||||||
|
val cache = File(cacheDir, "/imageCache/${galleryBlock.id}/images/$name.$ext")
|
||||||
|
|
||||||
|
if (!cache.exists())
|
||||||
|
try {
|
||||||
|
with(URL(url).openConnection() as HttpsURLConnection) {
|
||||||
|
setRequestProperty("Referer", getReferer(galleryBlock.id))
|
||||||
|
|
||||||
|
if (!cache.parentFile.exists())
|
||||||
|
cache.parentFile.mkdirs()
|
||||||
|
|
||||||
|
inputStream.copyTo(FileOutputStream(cache))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
cache.delete()
|
||||||
|
|
||||||
|
onErrorHandler?.invoke()
|
||||||
|
|
||||||
|
notificationBuilder
|
||||||
|
.setContentTitle(galleryBlock.title)
|
||||||
|
.setContentText(getString(R.string.reader_notification_error))
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
|
||||||
|
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.absolutePath
|
||||||
|
}
|
||||||
|
}.forEach {
|
||||||
|
list.add(it.await())
|
||||||
|
onDownloadedHandler?.invoke(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCompleteHandler?.invoke()
|
||||||
|
|
||||||
|
notificationBuilder
|
||||||
|
.setContentTitle(galleryBlock.title)
|
||||||
|
.setContentText(getString(R.string.reader_notification_complete))
|
||||||
|
.setProgress(0, 0, false)
|
||||||
|
|
||||||
|
if (notify)
|
||||||
|
notificationManager.notify(galleryBlock.id, notificationBuilder.build())
|
||||||
|
|
||||||
|
notify = false
|
||||||
|
|
||||||
|
remove(galleryBlock.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
downloadJob?.cancel()
|
||||||
|
|
||||||
|
remove(galleryBlock.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invokeOnReaderLoaded() {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
onReaderLoadedHandler?.invoke(reader.await())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearNotification() {
|
||||||
|
notificationManager.cancel(galleryBlock.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invokeOnNotifyChanged() {
|
||||||
|
onNotifyChangedHandler?.invoke(notify)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initNotification() {
|
||||||
|
val intent = Intent(this, ReaderActivity::class.java).apply {
|
||||||
|
putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), galleryBlock))
|
||||||
|
}
|
||||||
|
val pendingIntent = TaskStackBuilder.create(this).run {
|
||||||
|
addNextIntentWithParentStack(intent)
|
||||||
|
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
|
||||||
|
setContentTitle(galleryBlock.title)
|
||||||
|
setContentText(getString(R.string.reader_notification_text))
|
||||||
|
setSmallIcon(R.drawable.ic_download)
|
||||||
|
setContentIntent(pendingIntent)
|
||||||
|
setProgress(0, 0, true)
|
||||||
|
priority = NotificationCompat.PRIORITY_LOW
|
||||||
|
}
|
||||||
|
notificationManager = NotificationManagerCompat.from(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import kotlinx.io.IOException
|
|
||||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonConfiguration
|
import kotlinx.serialization.json.JsonConfiguration
|
||||||
@@ -8,7 +7,6 @@ import kotlinx.serialization.parseList
|
|||||||
import kotlinx.serialization.stringify
|
import kotlinx.serialization.stringify
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
class Histories(private val file: File) : ArrayList<Int>() {
|
class Histories(private val file: File) : ArrayList<Int>() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
package xyz.quaver.pupil.util
|
|
||||||
|
|
||||||
55
app/src/main/res/drawable-anydpi/ic_downloading.xml
Normal file
55
app/src/main/res/drawable-anydpi/ic_downloading.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<animated-vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
tools:ignore="NewApi">
|
||||||
|
<aapt:attr name="android:drawable">
|
||||||
|
<vector
|
||||||
|
android:name="vector"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:name="path"
|
||||||
|
android:pathData="M 19 9 L 15 9 L 15 3 L 9 3 L 9 9 L 5 9 L 12 16 L 19 9 Z"
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:strokeWidth="1"/>
|
||||||
|
<path
|
||||||
|
android:name="path_2"
|
||||||
|
android:pathData="M 5 19 L 19 19"
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:strokeColor="#fff"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeLineCap="butt"/>
|
||||||
|
</vector>
|
||||||
|
</aapt:attr>
|
||||||
|
<target android:name="path_2">
|
||||||
|
<aapt:attr name="android:animation">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="trimPathEnd"
|
||||||
|
android:duration="500"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="0.8"
|
||||||
|
android:valueType="floatType"
|
||||||
|
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="trimPathStart"
|
||||||
|
android:startOffset="500"
|
||||||
|
android:duration="500"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="0.8"
|
||||||
|
android:valueType="floatType"
|
||||||
|
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="trimPathOffset"
|
||||||
|
android:duration="1000"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:valueTo="0.2"
|
||||||
|
android:valueType="floatType"
|
||||||
|
android:interpolator="@android:anim/linear_interpolator"/>
|
||||||
|
</set>
|
||||||
|
</aapt:attr>
|
||||||
|
</target>
|
||||||
|
</animated-vector>
|
||||||
5
app/src/main/res/drawable/ic_download.xml
Normal file
5
app/src/main/res/drawable/ic_download.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#fff"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#ff000000" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
|
||||||
|
</vector>
|
||||||
@@ -41,6 +41,15 @@
|
|||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
app:menu_colorNormal="@color/colorAccent">
|
app:menu_colorNormal="@color/colorAccent">
|
||||||
|
|
||||||
|
<com.github.clans.fab.FloatingActionButton
|
||||||
|
android:id="@+id/reader_fab_download"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_downloading"
|
||||||
|
android:tint="@android:color/white"
|
||||||
|
app:fab_label="@string/reader_fab_download"
|
||||||
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
<com.github.clans.fab.FloatingActionButton
|
<com.github.clans.fab.FloatingActionButton
|
||||||
android:id="@+id/reader_fab_fullscreen"
|
android:id="@+id/reader_fab_fullscreen"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -49,11 +58,6 @@
|
|||||||
app:fab_label="@string/reader_fab_fullscreen"
|
app:fab_label="@string/reader_fab_fullscreen"
|
||||||
app:fab_size="mini"/>
|
app:fab_size="mini"/>
|
||||||
|
|
||||||
<com.github.clans.fab.FloatingActionButton
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:fab_size="mini"/>
|
|
||||||
|
|
||||||
</com.github.clans.fab.FloatingActionMenu>
|
</com.github.clans.fab.FloatingActionMenu>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -18,6 +18,14 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
|
android:id="@+id/galleryblock_progressbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="4dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/galleryblock_thumbnail"
|
android:id="@+id/galleryblock_thumbnail"
|
||||||
android:layout_width="150dp"
|
android:layout_width="150dp"
|
||||||
@@ -25,7 +33,7 @@
|
|||||||
android:contentDescription="@string/galleryblock_thumbnail_description"
|
android:contentDescription="@string/galleryblock_thumbnail_description"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.chip.Chip
|
||||||
|
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"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="24dp"
|
android:layout_height="24dp"
|
||||||
app:chipIconSize="16dp"
|
app:chipIconSize="16dp"
|
||||||
app:chipStartPadding="8dp"
|
app:chipStartPadding="8dp"
|
||||||
app:chipCornerRadius="100dp">
|
app:chipCornerRadius="100dp"/>
|
||||||
|
|
||||||
</com.google.android.material.chip.Chip>
|
|
||||||
@@ -44,4 +44,11 @@
|
|||||||
<string name="help_dialog_title">準備中</string>
|
<string name="help_dialog_title">準備中</string>
|
||||||
<string name="help_dialog_message">準備中です。</string>
|
<string name="help_dialog_message">準備中です。</string>
|
||||||
<string name="reader_fab_fullscreen">フルスクリーン</string>
|
<string name="reader_fab_fullscreen">フルスクリーン</string>
|
||||||
|
<string name="channel_download">ダウンロード</string>
|
||||||
|
<string name="channel_download_description">ダウンロードの進行を通知</string>
|
||||||
|
<string name="reader_fab_download">バックグラウンドダウンロード</string>
|
||||||
|
<string name="reader_notification_text">ダウンロード中…</string>
|
||||||
|
<string name="reader_notification_complete">ダウンロード完了</string>
|
||||||
|
<string name="reader_notification_error">ダウンロードエラー</string>
|
||||||
|
<string name="reader_fab_download_cancel">バックグラウンドダウンロード中止</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -44,4 +44,11 @@
|
|||||||
<string name="help_dialog_title">준비 중</string>
|
<string name="help_dialog_title">준비 중</string>
|
||||||
<string name="help_dialog_message">준비중입니다.</string>
|
<string name="help_dialog_message">준비중입니다.</string>
|
||||||
<string name="reader_fab_fullscreen">전체 화면</string>
|
<string name="reader_fab_fullscreen">전체 화면</string>
|
||||||
|
<string name="channel_download">다운로드</string>
|
||||||
|
<string name="channel_download_description">다운로드 상태 알림</string>
|
||||||
|
<string name="reader_fab_download">백그라운드 다운로드</string>
|
||||||
|
<string name="reader_notification_text">다운로드 중…</string>
|
||||||
|
<string name="reader_notification_complete">다운로드 완료</string>
|
||||||
|
<string name="reader_notification_error">다운로드 오류</string>
|
||||||
|
<string name="reader_fab_download_cancel">백그라운드 다운로드 취소</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string name="app_name" translatable="false">Pupil</string>
|
<string name="app_name" translatable="false" tools:override="true">Pupil</string>
|
||||||
|
|
||||||
<string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil-issue/releases</string>
|
<string name="release_url" translatable="false">https://api.github.com/repos/tom5079/Pupil-issue/releases</string>
|
||||||
<string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string>
|
<string name="release_name" translatable="false">Pupil-v(\\d+\\.)+\\d+\\.apk</string>
|
||||||
@@ -22,6 +22,9 @@
|
|||||||
|
|
||||||
<string name="permission_explain">Denying any permission can deactivate some functions</string>
|
<string name="permission_explain">Denying any permission can deactivate some functions</string>
|
||||||
|
|
||||||
|
<string name="channel_download">Download</string>
|
||||||
|
<string name="channel_download_description">Shows download status</string>
|
||||||
|
|
||||||
<string name="main_search">Search</string>
|
<string name="main_search">Search</string>
|
||||||
<string name="main_no_result">No result</string>
|
<string name="main_no_result">No result</string>
|
||||||
|
|
||||||
@@ -49,6 +52,11 @@
|
|||||||
|
|
||||||
<string name="reader_go_to_page">Go to page</string>
|
<string name="reader_go_to_page">Go to page</string>
|
||||||
<string name="reader_fab_fullscreen">Fullscreen</string>
|
<string name="reader_fab_fullscreen">Fullscreen</string>
|
||||||
|
<string name="reader_fab_download">Background download</string>
|
||||||
|
<string name="reader_fab_download_cancel">Cancel background download</string>
|
||||||
|
<string name="reader_notification_text">Downloading…</string>
|
||||||
|
<string name="reader_notification_complete">Download complete</string>
|
||||||
|
<string name="reader_notification_error">Download error</string>
|
||||||
|
|
||||||
<string name="settings_title">Settings</string>
|
<string name="settings_title">Settings</string>
|
||||||
<string name="settings_search_title">Search Settings</string>
|
<string name="settings_search_title">Search Settings</string>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.junit.Test
|
|||||||
class ExampleUnitTest {
|
class ExampleUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ImplicitReflectionSerializer
|
|
||||||
fun test() {
|
fun test() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import java.net.URL
|
|||||||
class UnitTest {
|
class UnitTest {
|
||||||
@Test
|
@Test
|
||||||
fun test() {
|
fun test() {
|
||||||
val f = File("C:/Users/tom50/Workspace/Pupil/nodir/nodir/asdf.txt")
|
print(File("C:\\asdf").list()?.size ?: 0)
|
||||||
|
|
||||||
f.delete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user