diff --git a/app/build.gradle b/app/build.gradle index c75cda98..c057f840 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,7 +123,7 @@ dependencies { implementation "ru.noties.markwon:core:3.1.0" - implementation "xyz.quaver:libpupil:1.8.16" + implementation "xyz.quaver:libpupil:1.9.0" implementation "xyz.quaver:documentfilex:0.4-alpha02" implementation "xyz.quaver:floatingsearchview:1.0.7" diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index 0faec406..ac5ce1ed 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -51,9 +51,9 @@ import kotlin.reflect.KClass typealias PupilInterceptor = (Interceptor.Chain) -> Response -lateinit var histories: SavedSet +lateinit var histories: SavedSet private set -lateinit var favorites: SavedSet +lateinit var favorites: SavedSet private set lateinit var favoriteTags: SavedSet private set @@ -108,8 +108,6 @@ class Pupil : Application() { if (!FileX(this, it).canWrite()) throw Exception() - - DownloadManager.getInstance(this).migrate() } } catch (e: Exception) { Preferences.remove("download_folder") @@ -120,8 +118,8 @@ class Pupil : Application() { Preferences["reset_secure"] = true } - histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0) - favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0) + histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), "") + favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), "") favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse("")) searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "") diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt deleted file mode 100644 index adcd2a36..00000000 --- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Pupil, Hitomi.la viewer for Android - * Copyright (C) 2019 tom5079 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package xyz.quaver.pupil.adapters - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.graphics.drawable.Drawable -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Toast -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.RecyclerView -import androidx.vectordrawable.graphics.drawable.Animatable2Compat -import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat -import com.daimajia.swipe.SwipeLayout -import com.daimajia.swipe.adapters.RecyclerSwipeAdapter -import com.daimajia.swipe.interfaces.SwipeAdapterInterface -import com.github.piasy.biv.loader.ImageLoader -import kotlinx.coroutines.* -import xyz.quaver.hitomi.getGallery -import xyz.quaver.hitomi.getReader -import xyz.quaver.io.util.getChild -import xyz.quaver.pupil.R -import xyz.quaver.pupil.databinding.GalleryblockItemBinding -import xyz.quaver.pupil.favoriteTags -import xyz.quaver.pupil.favorites -import xyz.quaver.pupil.types.Tag -import xyz.quaver.pupil.ui.view.ProgressCard -import xyz.quaver.pupil.util.Preferences -import xyz.quaver.pupil.util.downloader.Cache -import xyz.quaver.pupil.util.downloader.DownloadManager -import xyz.quaver.pupil.util.wordCapitalize -import java.io.File - -class GalleryBlockAdapter(private val galleries: List) : RecyclerSwipeAdapter(), SwipeAdapterInterface { - - var updateAll = true - var thin: Boolean = Preferences["thin"] - - inner class GalleryViewHolder(val binding: GalleryblockItemBinding) : RecyclerView.ViewHolder(binding.root) { - private var galleryID: Int = 0 - - init { - CoroutineScope(Dispatchers.Main).launch { - while (updateAll) { - updateProgress(itemView.context) - delay(1000) - } - } - } - - private fun updateProgress(context: Context) = CoroutineScope(Dispatchers.Main).launch { - with(binding.galleryblockCard) { - val imageList = Cache.getInstance(context, galleryID).metadata.imageList - - if (imageList == null) { - max = 0 - return@with - } - - progress = imageList.count { it != null } - max = imageList.size - - this@GalleryViewHolder.binding.galleryblockId.setOnClickListener { - (context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip( - ClipData.newPlainText("gallery_id", galleryID.toString()) - ) - Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() - } - - type = if (!imageList.contains(null)) { - val downloadManager = DownloadManager.getInstance(context) - - if (downloadManager.getDownloadFolder(galleryID) == null) - ProgressCard.Type.CACHE - else - ProgressCard.Type.DOWNLOAD - } else - ProgressCard.Type.LOADING - } - } - - fun bind(galleryID: Int) { - this.galleryID = galleryID - updateProgress(itemView.context) - - val cache = Cache.getInstance(itemView.context, galleryID) - - val galleryBlock = runBlocking { - cache.getGalleryBlock() - } ?: return - - val resources = itemView.context.resources - val languages = resources.getStringArray(R.array.languages).map { - it.split("|").let { split -> - Pair(split[0], split[1]) - } - }.toMap() - - val artists = galleryBlock.artists - val series = galleryBlock.series - - binding.galleryblockThumbnail.apply { - setOnClickListener { - itemView.performClick() - } - setOnLongClickListener { - itemView.performLongClick() - } - setFailureImage(ContextCompat.getDrawable(context, R.drawable.image_broken_variant)) - setImageLoaderCallback(object: ImageLoader.Callback { - override fun onFail(error: Exception?) { - Cache.getInstance(context, galleryID).let { cache -> - cache.cacheFolder.getChild(".thumbnail").let { if (it.exists()) it.delete() } - cache.downloadFolder?.getChild(".thumbnail")?.let { if (it.exists()) it.delete() } - } - } - - override fun onCacheHit(imageType: Int, image: File?) {} - override fun onCacheMiss(imageType: Int, image: File?) {} - override fun onFinish() {} - override fun onProgress(progress: Int) {} - override fun onStart() {} - override fun onSuccess(image: File?) {} - }) - ssiv?.recycle() - CoroutineScope(Dispatchers.IO).launch { - cache.getThumbnail().let { launch(Dispatchers.Main) { - showImage(it) - } } - } - } - - binding.galleryblockTitle.text = galleryBlock.title - with(binding.galleryblockArtist) { - text = artists.joinToString { it.wordCapitalize() } - visibility = when { - artists.isNotEmpty() -> View.VISIBLE - else -> View.GONE - } - - CoroutineScope(Dispatchers.IO).launch { - val gallery = runCatching { - getGallery(galleryID) - }.getOrNull() - - if (gallery?.groups?.isNotEmpty() != true) - return@launch - - launch(Dispatchers.Main) { - text = context.getString( - R.string.galleryblock_artist_with_group, - artists.joinToString { it.wordCapitalize() }, - gallery.groups.joinToString { it.wordCapitalize() } - ) - } - } - } - with(binding.galleryblockSeries) { - text = - resources.getString( - R.string.galleryblock_series, - series.joinToString(", ") { it.wordCapitalize() }) - visibility = when { - series.isNotEmpty() -> View.VISIBLE - else -> View.GONE - } - } - binding.galleryblockType.text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize() - with(binding.galleryblockLanguage) { - text = - resources.getString(R.string.galleryblock_language, languages[galleryBlock.language]) - visibility = when { - galleryBlock.language.isNotEmpty() -> View.VISIBLE - else -> View.GONE - } - } - - with(binding.galleryblockTagGroup) { - onClickListener = { - onChipClickedHandler.forEach { callback -> - callback.invoke(it) - } - } - - tags.clear() - - CoroutineScope(Dispatchers.IO).launch { - tags.addAll( - galleryBlock.relatedTags.sortedBy { - val tag = Tag.parse(it) - - if (favoriteTags.contains(tag)) - -1 - else - when(Tag.parse(it).area) { - "female" -> 0 - "male" -> 1 - else -> 2 - } - }.map { - Tag.parse(it) - } - ) - - launch(Dispatchers.Main) { - refresh() - } - } - } - - binding.galleryblockId.text = galleryBlock.id.toString() - binding.galleryblockPagecount.text = "-" - CoroutineScope(Dispatchers.IO).launch { - val pageCount = kotlin.runCatching { - getReader(galleryBlock.id).galleryInfo.files.size - }.getOrNull() ?: return@launch - withContext(Dispatchers.Main) { - binding.galleryblockPagecount.text = itemView.context.getString(R.string.galleryblock_pagecount, pageCount) - } - } - - with(binding.galleryblockFavorite) { - setImageResource(if (favorites.contains(galleryBlock.id)) R.drawable.ic_star_filled else R.drawable.ic_star_empty) - setOnClickListener { - when { - favorites.contains(galleryBlock.id) -> { - favorites.remove(galleryBlock.id) - - setImageResource(R.drawable.ic_star_empty) - } - else -> { - favorites.add(galleryBlock.id) - - setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star).apply { - this ?: return@apply - - registerAnimationCallback(object: Animatable2Compat.AnimationCallback() { - override fun onAnimationEnd(drawable: Drawable?) { - setImageResource(R.drawable.ic_star_filled) - } - }) - start() - }) - } - } - } - } - - - // Make some views invisible to make it thinner - if (thin) { - binding.galleryblockTagGroup.visibility = View.GONE - } - } - } - - val onChipClickedHandler = ArrayList<((Tag) -> Unit)>() - var onDownloadClickedHandler: ((Int) -> Unit)? = null - var onDeleteClickedHandler: ((Int) -> Unit)? = null - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return GalleryViewHolder(GalleryblockItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is GalleryViewHolder) { - val galleryID = galleries[position] - - holder.bind(galleryID) - - holder.binding.galleryblockCard.binding.download.setOnClickListener { - onDownloadClickedHandler?.invoke(position) - } - - holder.binding.galleryblockCard.binding.delete.setOnClickListener { - onDeleteClickedHandler?.invoke(position) - } - - mItemManger.bindView(holder.binding.root, position) - - holder.binding.galleryblockCard.binding.swipeLayout.addSwipeListener(object: SwipeLayout.SwipeListener { - override fun onStartOpen(layout: SwipeLayout?) { - mItemManger.closeAllExcept(layout) - - holder.binding.galleryblockCard.binding.download.text = - if (DownloadManager.getInstance(holder.binding.root.context).isDownloading(galleryID)) - holder.binding.root.context.getString(android.R.string.cancel) - else - holder.binding.root.context.getString(R.string.main_download) - } - - override fun onClose(layout: SwipeLayout?) {} - override fun onHandRelease(layout: SwipeLayout?, xvel: Float, yvel: Float) {} - override fun onOpen(layout: SwipeLayout?) {} - override fun onStartClose(layout: SwipeLayout?) {} - override fun onUpdate(layout: SwipeLayout?, leftOffset: Int, topOffset: Int) {} - }) - } - } - - override fun getItemCount() = galleries.size - - override fun getSwipeLayoutResourceId(position: Int) = R.id.swipe_layout -} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt index b815518b..d25a420d 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt @@ -40,7 +40,7 @@ import com.github.piasy.biv.view.BigImageView import com.github.piasy.biv.view.ImageShownCallback import com.github.piasy.biv.view.ImageViewFactory import kotlinx.coroutines.* -import xyz.quaver.hitomi.Reader +import xyz.quaver.hitomi.GalleryInfo import xyz.quaver.pupil.R import xyz.quaver.pupil.databinding.ReaderItemBinding import xyz.quaver.pupil.ui.ReaderActivity @@ -50,9 +50,9 @@ import kotlin.math.roundToInt class ReaderAdapter( private val activity: ReaderActivity, - private val galleryID: Int + private val galleryID: String ) : RecyclerView.Adapter() { - var reader: Reader? = null + var reader: GalleryInfo? = null var isFullScreen = false @@ -101,7 +101,7 @@ class ReaderAdapter( binding.image.updateLayoutParams { height = 0 dimensionRatio = - "${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}" + "${reader!!.files[position].width}:${reader!!.files[position].height}" } } else { binding.root.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT @@ -158,7 +158,7 @@ class ReaderAdapter( holder.bind(position) } - override fun getItemCount() = reader?.galleryInfo?.files?.size ?: 0 + override fun getItemCount() = reader?.files?.size ?: 0 override fun onViewRecycled(holder: ViewHolder) { holder.clear() diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/SearchResultsAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/SearchResultsAdapter.kt new file mode 100644 index 00000000..af341930 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/adapters/SearchResultsAdapter.kt @@ -0,0 +1,151 @@ +/* + * Pupil, Hitomi.la viewer for Android + * Copyright (C) 2019 tom5079 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package xyz.quaver.pupil.adapters + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.net.Uri +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.Toast +import androidx.recyclerview.widget.RecyclerView +import com.daimajia.swipe.SwipeLayout +import com.daimajia.swipe.adapters.RecyclerSwipeAdapter +import com.daimajia.swipe.interfaces.SwipeAdapterInterface +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import xyz.quaver.pupil.R +import xyz.quaver.pupil.databinding.SearchResultItemBinding +import xyz.quaver.pupil.sources.SearchResult +import xyz.quaver.pupil.types.Tag +import xyz.quaver.pupil.ui.view.ProgressCardView +import xyz.quaver.pupil.util.downloader.Cache +import xyz.quaver.pupil.util.downloader.DownloadManager +import xyz.quaver.pupil.util.wordCapitalize + +class SearchResultsAdapter(private val results: List) : RecyclerSwipeAdapter(), SwipeAdapterInterface { + + val onChipClickedHandler = ArrayList<((Tag) -> Unit)>() + var onDownloadClickedHandler: ((String) -> Unit)? = null + var onDeleteClickedHandler: ((String) -> Unit)? = null + + inner class ViewHolder(private val binding: SearchResultItemBinding) : RecyclerView.ViewHolder(binding.root) { + var itemID: String = "" + var update = true + + init { + CoroutineScope(Dispatchers.Main).launch { + while (update) { + updateProgress() + delay(1000) + } + } + + binding.root.binding.download.setOnClickListener { + onDownloadClickedHandler?.invoke(itemID) + } + + binding.root.binding.delete.setOnClickListener { + onDeleteClickedHandler?.invoke(itemID) + } + + binding.idView.setOnClickListener { + (itemView.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip( + ClipData.newPlainText("item_id", itemID) + ) + Toast.makeText(itemView.context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show() + } + + binding.root.binding.swipeLayout.addSwipeListener(object: SwipeLayout.SwipeListener { + override fun onStartOpen(layout: SwipeLayout?) { + mItemManger.closeAllExcept(layout) + + binding.root.binding.download.text = + if (DownloadManager.getInstance(itemView.context).isDownloading(itemID)) + itemView.context.getString(android.R.string.cancel) + else + itemView.context.getString(R.string.main_download) + } + + override fun onOpen(layout: SwipeLayout?) {} + override fun onStartClose(layout: SwipeLayout?) {} + override fun onClose(layout: SwipeLayout?) {} + override fun onHandRelease(layout: SwipeLayout?, xvel: Float, yvel: Float) {} + override fun onUpdate(layout: SwipeLayout?, leftOffset: Int, topOffset: Int) {} + }) + } + + fun updateProgress() = CoroutineScope(Dispatchers.Main).launch { + with (itemView as ProgressCardView) { + val imageList = Cache.getInstance(context, itemID).metadata.imageList + + if (imageList == null) { + max = 0 + return@with + } + + progress = imageList.count { it != null } + max = imageList.size + + type = if (!imageList.contains(null)) { + val downloadManager = DownloadManager.getInstance(context) + + if (downloadManager.getDownloadFolder(itemID) == null) + ProgressCardView.Type.CACHE + else + ProgressCardView.Type.DOWNLOAD + } else + ProgressCardView.Type.LOADING + } + } + + fun bind(result: SearchResult) { + itemID = result.id + + binding.thumbnail.ssiv?.recycle() + binding.thumbnail.showImage(Uri.parse(result.thumbnail)) + + updateProgress() + + binding.title.text = result.title + binding.idView.text = result.id + binding.artist.text = result.artists.joinToString { it.wordCapitalize() } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = + ViewHolder(SearchResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + mItemManger.bindView(holder.itemView, position) + holder.bind(results[position]) + } + + override fun onViewDetachedFromWindow(holder: ViewHolder) { + holder.update = false + } + + override fun getItemCount(): Int = results.size + + override fun getSwipeLayoutResourceId(position: Int): Int = R.id.swipe_layout + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt b/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt index f38ec3e0..453307b6 100644 --- a/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt +++ b/app/src/main/java/xyz/quaver/pupil/services/DownloadService.kt @@ -51,7 +51,7 @@ import kotlin.math.log10 private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit class DownloadService : Service() { - data class Tag(val galleryID: Int, val index: Int, val startId: Int? = null) + data class Tag(val galleryID: String, val index: Int, val startId: Int? = null) //region Notification private val notificationManager by lazy { @@ -66,15 +66,15 @@ class DownloadService : Service() { .setOngoing(true) } - private val notification = ConcurrentHashMap() + private val notification = ConcurrentHashMap() - private fun initNotification(galleryID: Int) { + private fun initNotification(galleryID: String) { val intent = Intent(this, ReaderActivity::class.java) .putExtra("galleryID", galleryID) val pendingIntent = TaskStackBuilder.create(this).run { addNextIntentWithParentStack(intent) - getPendingIntent(galleryID, PendingIntent.FLAG_UPDATE_CURRENT) + getPendingIntent(galleryID.hashCode(), PendingIntent.FLAG_UPDATE_CURRENT) } val action = NotificationCompat.Action.Builder(0, getText(android.R.string.cancel), @@ -101,7 +101,7 @@ class DownloadService : Service() { } @SuppressLint("RestrictedApi") - private fun notify(galleryID: Int) { + private fun notify(galleryID: String) { val max = progress[galleryID]?.size ?: 0 val progress = progress[galleryID]?.count { it == Float.POSITIVE_INFINITY } ?: 0 @@ -114,16 +114,16 @@ class DownloadService : Service() { .setOngoing(false) .mActions.clear() - notificationManager.cancel(galleryID) + notificationManager.cancel(galleryID.hashCode()) } else notification .setProgress(max, progress, false) .setContentText("$progress/$max") if (DownloadManager.getInstance(this).getDownloadFolder(galleryID) != null || galleryID == priority) - notification.let { notificationManager.notify(galleryID, it.build()) } + notification.let { notificationManager.notify(galleryID.hashCode(), it.build()) } else - notificationManager.cancel(galleryID) + notificationManager.cancel(galleryID.hashCode()) } //endregion @@ -194,10 +194,10 @@ class DownloadService : Service() { * 0 <= value < 100 -> Download in progress * Float.POSITIVE_INFINITY -> Download completed */ - val progress = ConcurrentHashMap>() - var priority = 0 + val progress = ConcurrentHashMap>() + var priority = "" - fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true + fun isCompleted(galleryID: String) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true private val callback = object: Callback { @@ -266,7 +266,7 @@ class DownloadService : Service() { startId?.let { stopSelf(it) } } - fun cancel(galleryID: Int, startId: Int? = null) { + fun cancel(galleryID: String, startId: Int? = null) { client.dispatcher().queuedCalls().filter { (it.request().tag() as? Tag)?.galleryID == galleryID }.forEach { @@ -282,12 +282,12 @@ class DownloadService : Service() { progress.remove(galleryID) notification.remove(galleryID) - notificationManager.cancel(galleryID) + notificationManager.cancel(galleryID.hashCode()) startId?.let { stopSelf(it) } } - fun delete(galleryID: Int, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch { + fun delete(galleryID: String, startId: Int? = null) = CoroutineScope(Dispatchers.IO).launch { cancel(galleryID) DownloadManager.getInstance(this@DownloadService).deleteDownloadFolder(galleryID) Cache.delete(this@DownloadService, galleryID) @@ -295,7 +295,7 @@ class DownloadService : Service() { startId?.let { stopSelf(it) } } - fun download(galleryID: Int, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch { + fun download(galleryID: String, priority: Boolean = false, startId: Int? = null): Job = CoroutineScope(Dispatchers.IO).launch { if (DownloadManager.getInstance(this@DownloadService).isDownloading(galleryID)) return@launch @@ -316,7 +316,7 @@ class DownloadService : Service() { histories.add(galleryID) - progress[galleryID] = MutableList(reader.galleryInfo.files.size) { 0F } + progress[galleryID] = MutableList(reader.files.size) { 0F } cache.metadata.imageList?.let { it.forEachIndexed { index, image -> @@ -329,15 +329,15 @@ class DownloadService : Service() { .getDownloadFolder(galleryID) != null ) Cache.getInstance(this@DownloadService, galleryID).moveToDownload() - notificationManager.cancel(galleryID) + notificationManager.cancel(galleryID.hashCode()) startId?.let { stopSelf(it) } return@launch } - notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30)) + notification[galleryID]?.setContentTitle(reader.title?.ellipsize(30)) notify(galleryID) - val queued = mutableSetOf() + val queued = mutableSetOf() if (priority) { client.dispatcher().queuedCalls().forEach { @@ -372,7 +372,7 @@ class DownloadService : Service() { ContextCompat.startForegroundService(context, Intent(context, DownloadService::class.java).apply(extras)) } - fun download(context: Context, galleryID: Int, priority: Boolean = false) { + fun download(context: Context, galleryID: String, priority: Boolean = false) { command(context) { putExtra(KEY_COMMAND, COMMAND_DOWNLOAD) putExtra(KEY_PRIORITY, priority) @@ -380,14 +380,14 @@ class DownloadService : Service() { } } - fun cancel(context: Context, galleryID: Int? = null) { + fun cancel(context: Context, galleryID: String? = null) { command(context) { putExtra(KEY_COMMAND, COMMAND_CANCEL) galleryID?.let { putExtra(KEY_ID, it) } } } - fun delete(context: Context, galleryID: Int) { + fun delete(context: Context, galleryID: String) { command(context) { putExtra(KEY_COMMAND, COMMAND_DELETE) putExtra(KEY_ID, galleryID) @@ -399,11 +399,11 @@ class DownloadService : Service() { startForeground(R.id.downloader_notification_id, serviceNotification.build()) when (intent?.getStringExtra(KEY_COMMAND)) { - COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) + COMMAND_DOWNLOAD -> intent.getStringExtra(KEY_ID).let { if (!it.isNullOrEmpty()) download(it, intent.getBooleanExtra(KEY_PRIORITY, false), startId) } - COMMAND_CANCEL -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) cancel(it, startId) else cancel(startId = startId) } - COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it, startId) } + COMMAND_CANCEL -> intent.getStringExtra(KEY_ID).let { if (!it.isNullOrEmpty()) cancel(it, startId) else cancel(startId = startId) } + COMMAND_DELETE -> intent.getStringExtra(KEY_ID).let { if (!it.isNullOrEmpty()) delete(it, startId) } } return START_NOT_STICKY diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt new file mode 100644 index 00000000..f7bf95c7 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt @@ -0,0 +1,37 @@ +/* + * Pupil, Hitomi.la viewer for Android + * Copyright (C) 2020 tom5079 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package xyz.quaver.pupil.sources + +import com.google.android.gms.vision.L +import kotlin.reflect.KClass + +interface SearchResult { + val id: String + val title: String + val thumbnail: String + val artists: List +} + +// Might be better to use channel on Query_Result +interface Source, Query_Result: SearchResult> { + val querySortModeClass: KClass + val queryResultClass: KClass + + suspend fun query(query: String, range: IntRange, sortMode: Query_SortMode? = null) : Pair, Int> +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt new file mode 100644 index 00000000..b7516fdc --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt @@ -0,0 +1,73 @@ +/* + * Pupil, Hitomi.la viewer for Android + * Copyright (C) 2020 tom5079 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package xyz.quaver.pupil.sources.hitomi + +import kotlinx.coroutines.yield +import xyz.quaver.hitomi.doSearch +import xyz.quaver.hitomi.getGalleryBlock +import xyz.quaver.pupil.sources.Source +import kotlin.math.min +import kotlin.math.max + +class Hitomi : Source { + + override val querySortModeClass = SortMode::class + override val queryResultClass = SearchResult::class + + enum class SortMode { + NEWEST, + POPULAR + } + + data class SearchResult( + override val id: String, + override val title: String, + override val thumbnail: String, + override val artists: List, + ) : xyz.quaver.pupil.sources.SearchResult + + var cachedQuery: String? = null + val cache = mutableListOf() + + override suspend fun query(query: String, range: IntRange, sortMode: SortMode?): Pair, Int> { + if (cachedQuery != query) { + cachedQuery = null + cache.clear() + yield() + doSearch(query, sortMode == SortMode.POPULAR).let { + yield() + cache.addAll(it) + } + cachedQuery = query + } + + val sanitizedRange = max(0, range.first) .. min(range.last, cache.size-1) + return Pair(cache.slice(sanitizedRange).map { + getGalleryBlock(it).let { gallery -> + SearchResult( + gallery.id.toString(), + gallery.title, + gallery.thumbnails.first(), + gallery.artists + ) + } + }, cache.size) + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 8eca9aff..4b588937 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -31,35 +31,33 @@ import android.view.animation.DecelerateInterpolator import android.widget.EditText import android.widget.TextView import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatDelegate import androidx.cardview.widget.CardView import androidx.core.view.GravityCompat import androidx.core.view.ViewCompat import androidx.recyclerview.widget.RecyclerView import com.google.android.material.navigation.NavigationView import com.google.android.material.snackbar.Snackbar -import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.coroutines.* import xyz.quaver.floatingsearchview.FloatingSearchView import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.util.view.MenuView import xyz.quaver.floatingsearchview.util.view.SearchInputView -import xyz.quaver.hitomi.doSearch -import xyz.quaver.hitomi.getGalleryIDsFromNozomi import xyz.quaver.hitomi.getSuggestionsForQuery import xyz.quaver.pupil.* -import xyz.quaver.pupil.adapters.GalleryBlockAdapter +import xyz.quaver.pupil.adapters.SearchResultsAdapter import xyz.quaver.pupil.databinding.MainActivityBinding import xyz.quaver.pupil.services.DownloadService +import xyz.quaver.pupil.sources.SearchResult +import xyz.quaver.pupil.sources.Source +import xyz.quaver.pupil.sources.hitomi.Hitomi import xyz.quaver.pupil.types.* import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment import xyz.quaver.pupil.ui.dialog.GalleryDialog -import xyz.quaver.pupil.ui.view.MainView -import xyz.quaver.pupil.ui.view.ProgressCard +import xyz.quaver.pupil.ui.view.ProgressCardView +import xyz.quaver.pupil.ui.view.SwipePageTurnView import xyz.quaver.pupil.util.ItemClickSupport import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.checkUpdate -import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.DownloadManager import xyz.quaver.pupil.util.restore import java.util.regex.Pattern @@ -69,20 +67,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { - - enum class Mode { - SEARCH, - HISTORY, - DOWNLOAD, - FAVORITE - } - - enum class SortMode { - NEWEST, - POPULAR - } - - private val galleries = ArrayList() + private val searchResults = mutableListOf() private var query = "" set(value) { @@ -94,13 +79,13 @@ class MainActivity : } private var queryStack = mutableListOf() - private var mode = Mode.SEARCH - private var sortMode = SortMode.NEWEST + @Suppress("UNCHECKED_CAST") + private var source: Source, SearchResult> = Hitomi() as Source, SearchResult> + private var sortMode = Hitomi.SortMode.NEWEST - private var galleryIDs: Deferred>? = null + private var searchJob: Deferred, Int>>? = null private var totalItems = 0 - private var loadingJob: Job? = null - private var currentPage = 0 + private var currentPage = 1 private lateinit var binding: MainActivityBinding @@ -136,35 +121,23 @@ class MainActivity : queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread { query = queryStack.last() - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() + query() } else -> super.onBackPressed() } } - override fun onDestroy() { - super.onDestroy() - - (binding.contents.recyclerview.adapter as? GalleryBlockAdapter)?.updateAll = false - } - override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { val perPage = Preferences["per_page", "25"].toInt() val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt() return when(keyCode) { KeyEvent.KEYCODE_VOLUME_UP -> { - if (currentPage > 0) { + if (currentPage > 1) { runOnUiThread { currentPage-- - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() + query() } } @@ -175,10 +148,7 @@ class MainActivity : runOnUiThread { currentPage++ - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() + query() } } @@ -229,18 +199,14 @@ class MainActivity : setTitle(R.string.main_jump_title) setMessage(getString( R.string.main_jump_message, - currentPage+1, + currentPage, ceil(totalItems / perPage.toDouble()).roundToInt() )) setPositiveButton(android.R.string.ok) { _, _ -> - currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1 + currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton) - runOnUiThread { - cancelFetch() - clearGalleries() - loadBlocks() - } + query() } }.show() } @@ -251,23 +217,18 @@ class MainActivity : setOnClickListener { runBlocking { withTimeoutOrNull(100) { - galleryIDs?.await() + searchJob?.await() } }.let { - if (it?.isEmpty() == false) { - val galleryID = it.random() + if (it?.first?.isEmpty() == false) { + val random = it.first.random() - GalleryDialog(this@MainActivity, galleryID).apply { + GalleryDialog(this@MainActivity, random.id).apply { onChipClickedHandler.add { - runOnUiThread { - query = it.toQuery() - currentPage = 0 + query = it.toQuery() + currentPage = 1 - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() - } + query() dismiss() } }.show() @@ -288,19 +249,14 @@ class MainActivity : setTitle(R.string.main_open_gallery_by_id) setPositiveButton(android.R.string.ok) { _, _ -> - val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton + val galleryID = editText.text.toString() GalleryDialog(this@MainActivity, galleryID).apply { onChipClickedHandler.add { - runOnUiThread { - query = it.toQuery() - currentPage = 0 + query = it.toQuery() + currentPage = 1 - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() - } + query() dismiss() } }.show() @@ -309,8 +265,8 @@ class MainActivity : } } - with(binding.contents.view) { - setOnPageTurnListener(object: MainView.OnPageTurnListener { + with(binding.contents.swipePageTurnView) { + setOnPageTurnListener(object: SwipePageTurnView.OnPageTurnListener { override fun onPrev(page: Int) { currentPage-- @@ -322,10 +278,7 @@ class MainActivity : .setInterpolator(DecelerateInterpolator()) .translationY(0F) - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() + query() } override fun onNext(page: Int) { @@ -339,95 +292,70 @@ class MainActivity : .setInterpolator(DecelerateInterpolator()) .translationY(0F) - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() + query() } }) } setupSearchBar() setupRecyclerView() - fetchGalleries(query, sortMode) - loadBlocks() + query() } @SuppressLint("ClickableViewAccessibility") private fun setupRecyclerView() { with(binding.contents.recyclerview) { - adapter = GalleryBlockAdapter(galleries).apply { + adapter = SearchResultsAdapter(searchResults).apply { onChipClickedHandler.add { - runOnUiThread { - query = it.toQuery() - currentPage = 0 + query = it.toQuery() + currentPage = 0 - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() - } + query() } - onDownloadClickedHandler = { position -> - val galleryID = galleries[position] - - if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress - DownloadService.cancel(this@MainActivity, galleryID) + onDownloadClickedHandler = { id -> + if (DownloadManager.getInstance(context).isDownloading(id)) { //download in progress + DownloadService.cancel(this@MainActivity, id) } else { - DownloadManager.getInstance(context).addDownloadFolder(galleryID) - DownloadService.download(this@MainActivity, galleryID) + DownloadManager.getInstance(context).addDownloadFolder(id) + DownloadService.download(this@MainActivity, id) } closeAllItems() } - onDeleteClickedHandler = { position -> - val galleryID = galleries[position] - DownloadService.delete(this@MainActivity, galleryID) + onDeleteClickedHandler = { id -> + DownloadService.delete(this@MainActivity, id) - histories.remove(galleryID) - - if (this@MainActivity.mode != Mode.SEARCH) - runOnUiThread { - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() - } + histories.remove(id) closeAllItems() } } ItemClickSupport.addTo(this).apply { onItemClickListener = listener@{ _, position, v -> - if (v !is ProgressCard) + if (v !is ProgressCardView) return@listener val intent = Intent(this@MainActivity, ReaderActivity::class.java) - intent.putExtra("galleryID", galleries[position]) + intent.putExtra("galleryID", searchResults[position].id) //TODO: Maybe sprinkling some transitions will be nice :D startActivity(intent) } onItemLongClickListener = listener@{ _, position, v -> - if (v !is ProgressCard) + if (v !is ProgressCardView) return@listener false - val galleryID = galleries.getOrNull(position) ?: return@listener true + val result = searchResults.getOrNull(position) ?: return@listener true - GalleryDialog(this@MainActivity, galleryID).apply { + GalleryDialog(this@MainActivity, result.id).apply { onChipClickedHandler.add { - runOnUiThread { - query = it.toQuery() - currentPage = 0 + query = it.toQuery() + currentPage = 0 - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() - } + query() dismiss() } }.show() @@ -458,7 +386,7 @@ class MainActivity : with(binding.contents.searchview) { onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener { override fun onMenuOpened() { - (binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems() + (binding.contents.recyclerview.adapter as SearchResultsAdapter).closeAllItems() } override fun onMenuClosed() { @@ -532,13 +460,8 @@ class MainActivity : override fun onFocusCleared() { suggestionJob?.cancel() - runOnUiThread { - cancelFetch() - clearGalleries() - currentPage = 0 - fetchGalleries(query, sortMode) - loadBlocks() - } + currentPage = 1 + query() } } @@ -550,43 +473,26 @@ class MainActivity : when(item?.itemId) { R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java)) R.id.main_menu_thin -> { - val thin = !item.isChecked - - item.isChecked = thin - binding.contents.recyclerview.apply { - (adapter as GalleryBlockAdapter).apply { - this.thin = thin - - Preferences["thin"] = thin - } - - adapter = adapter // Force to redraw - } + // TODO } R.id.main_menu_sort_newest -> { - sortMode = SortMode.NEWEST + sortMode = Hitomi.SortMode.NEWEST item.isChecked = true runOnUiThread { - currentPage = 0 + currentPage = 1 - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() + query() } } R.id.main_menu_sort_popular -> { - sortMode = SortMode.POPULAR + sortMode = Hitomi.SortMode.POPULAR item.isChecked = true runOnUiThread { - currentPage = 0 + currentPage = 1 - cancelFetch() - clearGalleries() - fetchGalleries(query, sortMode) - loadBlocks() + query() } } } @@ -597,46 +503,6 @@ class MainActivity : binding.drawer.closeDrawers() when(item.itemId) { - R.id.main_drawer_home -> { - cancelFetch() - clearGalleries() - currentPage = 0 - query = "" - queryStack.clear() - mode = Mode.SEARCH - fetchGalleries(query, sortMode) - loadBlocks() - } - R.id.main_drawer_history -> { - cancelFetch() - clearGalleries() - currentPage = 0 - query = "" - queryStack.clear() - mode = Mode.HISTORY - fetchGalleries(query, sortMode) - loadBlocks() - } - R.id.main_drawer_downloads -> { - cancelFetch() - clearGalleries() - currentPage = 0 - query = "" - queryStack.clear() - mode = Mode.DOWNLOAD - fetchGalleries(query, sortMode) - loadBlocks() - } - R.id.main_drawer_favorite -> { - cancelFetch() - clearGalleries() - currentPage = 0 - query = "" - queryStack.clear() - mode = Mode.FAVORITE - fetchGalleries(query, sortMode) - loadBlocks() - } R.id.main_drawer_help -> { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help)))) } @@ -659,161 +525,47 @@ class MainActivity : } private fun cancelFetch() { - galleryIDs?.cancel() - loadingJob?.cancel() + searchJob?.cancel() } private fun clearGalleries() = CoroutineScope(Dispatchers.Main).launch { - galleries.clear() + searchResults.clear() - with(binding.contents.recyclerview.adapter as GalleryBlockAdapter?) { - this ?: return@with - - this.notifyDataSetChanged() - } + binding.contents.recyclerview.adapter?.notifyDataSetChanged() binding.contents.noresult.visibility = View.INVISIBLE binding.contents.progressbar.show() } - - private fun fetchGalleries(query: String, sortMode: SortMode) { - val defaultQuery: String = Preferences["default_query"] - - if (query.isNotBlank()) - searchHistory.add(query) - - if (query != queryStack.lastOrNull()) { - queryStack.remove(query) - queryStack.add(query) - } - - if (query.isNotEmpty() && mode != Mode.SEARCH) { - Snackbar.make(binding.contents.recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply { - setAction(android.R.string.ok) { - cancelFetch() - clearGalleries() - currentPage = 0 - mode = Mode.SEARCH - queryStack.clear() - fetchGalleries(query, sortMode) - loadBlocks() - } - }.show() - } - - galleryIDs = null - - if (galleryIDs?.isActive == true) - return - - galleryIDs = CoroutineScope(Dispatchers.IO).async { - when(mode) { - Mode.SEARCH -> { - when { - query.isEmpty() and defaultQuery.isEmpty() -> { - when(sortMode) { - SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all") - else -> getGalleryIDsFromNozomi(null, "index", "all") - }.also { - totalItems = it.size - } - } - else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also { - totalItems = it.size - } - } - } - Mode.HISTORY -> { - when { - query.isEmpty() -> { - histories.reversed().also { - totalItems = it.size - } - } - else -> { - val result = doSearch(query).sorted() - histories.reversed().filter { result.binarySearch(it) >= 0 }.also { - totalItems = it.size - } - } - } - } - Mode.DOWNLOAD -> { - val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList() - - when { - query.isEmpty() -> downloads.reversed().also { - totalItems = it.size - } - else -> { - val result = doSearch(query).sorted() - downloads.reversed().filter { result.binarySearch(it) >= 0 }.also { - totalItems = it.size - } - } - } - } - Mode.FAVORITE -> { - when { - query.isEmpty() -> favorites.reversed().also { - totalItems = it.size - } - else -> { - val result = doSearch(query).sorted() - favorites.reversed().filter { result.binarySearch(it) >= 0 }.also { - totalItems = it.size - } - } - } - } - }.toList() - } - } - - private fun loadBlocks() { + private fun query() { val perPage = Preferences["per_page", "25"].toInt() - loadingJob = CoroutineScope(Dispatchers.IO).launch { - val galleryIDs = try { - galleryIDs!!.await().also { - if (it.isEmpty()) - throw Exception("No result") + cancelFetch() + clearGalleries() + + CoroutineScope(Dispatchers.Main).launch { + searchJob = async(Dispatchers.IO) { + source.query( + query + Preferences["default_query", ""], + (currentPage - 1) * perPage until currentPage * perPage, + sortMode + ) + }.also { + val results: List + + it.await().let { r -> + results = r.first + totalItems = r.second } - } catch (e: Exception) { - if (e.message != "No result") - FirebaseCrashlytics.getInstance().recordException(e) + binding.contents.progressbar.hide() + binding.contents.swipePageTurnView.setCurrentPage(currentPage, totalItems > currentPage*perPage) - withContext(Dispatchers.Main) { + if (results.isEmpty()) { binding.contents.noresult.visibility = View.VISIBLE - binding.contents.progressbar.hide() + } else { + searchResults.addAll(results) + binding.contents.recyclerview.adapter?.notifyDataSetChanged() } - - return@launch - } - - launch(Dispatchers.Main) { - binding.contents.view.setCurrentPage(currentPage + 1, galleryIDs.size > (currentPage+1)*perPage) - } - - galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks -> - for (chunk in chunks) - chunk.map { galleryID -> - async { - Cache.getInstance(this@MainActivity, galleryID).getGalleryBlock()?.let { - galleryID - } - } - }.forEach { - it.await()?.also { - withContext(Dispatchers.Main) { - binding.contents.progressbar.hide() - - galleries.add(it) - binding.contents.recyclerview.adapter!!.notifyItemInserted(galleries.size - 1) - } - } - } } } } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index 758f8bff..6570ce5e 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -49,7 +49,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import xyz.quaver.Code import xyz.quaver.pupil.R import xyz.quaver.pupil.adapters.ReaderAdapter import xyz.quaver.pupil.databinding.NumberpickerDialogBinding @@ -65,7 +64,7 @@ import xyz.quaver.pupil.util.startCamera class ReaderActivity : BaseActivity() { - private var galleryID = 0 + private var galleryID = "" private var currentPage = 0 private var isScroll = true @@ -81,7 +80,7 @@ class ReaderActivity : BaseActivity() { private val conn = object: ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { downloader = (service as DownloadService.Binder).service.also { - it.priority = 0 + it.priority = "" if (!it.progress.containsKey(galleryID)) DownloadService.download(this@ReaderActivity, galleryID, true) @@ -130,7 +129,7 @@ class ReaderActivity : BaseActivity() { cache = Cache.getInstance(this, galleryID) FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID) - if (galleryID == 0) { + if (galleryID.isEmpty()) { onBackPressed() return } @@ -151,14 +150,14 @@ class ReaderActivity : BaseActivity() { if (uri != null && lastPathSegment != null) { galleryID = when (uri.host) { "hitomi.la" -> - Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1)?.toIntOrNull() ?: 0 - "hiyobi.me" -> lastPathSegment.toInt() - "e-hentai.org" -> uri.pathSegments[1].toInt() - else -> 0 + Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1) ?: "" + "hiyobi.me" -> lastPathSegment + "e-hentai.org" -> uri.pathSegments[1] + else -> "" } } } else { - galleryID = intent.getIntExtra("galleryID", 0) + galleryID = intent.getStringExtra("galleryID") ?: "" } } @@ -184,7 +183,7 @@ class ReaderActivity : BaseActivity() { with(binding.numberPicker) { minValue = 1 - maxValue = cache.metadata.reader?.galleryInfo?.files?.size ?: 0 + maxValue = cache.metadata.reader?.files?.size ?: 0 value = currentPage } val dialog = AlertDialog.Builder(this).apply { @@ -307,17 +306,19 @@ class ReaderActivity : BaseActivity() { notifyDataSetChanged() } - title = reader.galleryInfo.title + title = reader.title menu?.findItem(R.id.reader_menu_page_indicator)?.title = - "$currentPage/${reader.galleryInfo.files.size}" + "$currentPage/${reader.files.size}" menu?.findItem(R.id.reader_type)?.icon = ContextCompat.getDrawable( this@ReaderActivity, + R.drawable.hitomi + /* when (reader.code) { Code.HITOMI -> R.drawable.hitomi Code.HIYOBI -> R.drawable.ic_hiyobi else -> android.R.color.transparent - } + }*/ ) } } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadFolderNameDialogFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadFolderNameDialogFragment.kt index 673b4769..94a82097 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadFolderNameDialogFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadFolderNameDialogFragment.kt @@ -55,7 +55,7 @@ class DownloadFolderNameDialogFragment : DialogFragment() { } private fun initView() { - val galleryID = Cache.instances.let { if (it.size == 0) 1199708 else it.keys.elementAt((0 until it.size).random()) } + val galleryID = Cache.instances.let { if (it.size == 0) "1199708" else it.keys.elementAt((0 until it.size).random()) } val galleryBlock = runBlocking { Cache.getInstance(requireContext(), galleryID).getGalleryBlock() } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialogFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialogFragment.kt index e9b83ef2..cc370f13 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialogFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DownloadLocationDialogFragment.kt @@ -39,7 +39,6 @@ import xyz.quaver.pupil.databinding.DownloadLocationItemBinding import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.byteToString import xyz.quaver.pupil.util.downloader.DownloadManager -import xyz.quaver.pupil.util.migrate import java.io.File class DownloadLocationDialogFragment : DialogFragment() { @@ -181,8 +180,6 @@ class DownloadLocationDialogFragment : DialogFragment() { setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ -> if (Preferences["download_folder", ""].isEmpty()) Preferences["download_folder"] = context.getExternalFilesDir(null)?.toUri()?.toString() ?: "" - - DownloadManager.getInstance(requireContext()).migrate() } isCancelable = false diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt index 693c208d..fe935813 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt @@ -38,10 +38,12 @@ import kotlinx.coroutines.withContext import xyz.quaver.hitomi.Gallery import xyz.quaver.hitomi.getGallery import xyz.quaver.pupil.R -import xyz.quaver.pupil.adapters.GalleryBlockAdapter +import xyz.quaver.pupil.adapters.SearchResultsAdapter import xyz.quaver.pupil.adapters.ThumbnailPageAdapter import xyz.quaver.pupil.databinding.* import xyz.quaver.pupil.favoriteTags +import xyz.quaver.pupil.sources.hitomi.Hitomi +import xyz.quaver.pupil.sources.SearchResult import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.ui.view.TagChip @@ -51,7 +53,7 @@ import xyz.quaver.pupil.util.wordCapitalize import java.util.* import kotlin.collections.ArrayList -class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog(context) { +class GalleryDialog(context: Context, private val galleryID: String) : AlertDialog(context) { val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>() @@ -80,7 +82,7 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog( CoroutineScope(Dispatchers.IO).launch { try { - val gallery = getGallery(galleryID) + val gallery = getGallery(galleryID.toInt()) launch (Dispatchers.Main) { binding.progressbar.visibility = View.GONE @@ -203,9 +205,9 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog( } private fun addRelated(gallery: Gallery) { - val galleries = ArrayList() + val galleries = mutableListOf() - val adapter = GalleryBlockAdapter(galleries).apply { + val adapter = SearchResultsAdapter(galleries).apply { onChipClickedHandler.add { tag -> this@GalleryDialog.onChipClickedHandler.forEach { handler -> handler.invoke(tag) @@ -223,11 +225,11 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog( ItemClickSupport.addTo(this).apply { onItemClickListener = { _, position, _ -> context.startActivity(Intent(context, ReaderActivity::class.java).apply { - putExtra("galleryID", galleries[position]) + putExtra("galleryID", galleries[position].id) }) } onItemLongClickListener = { _, position, _ -> - GalleryDialog(context, galleries[position]).apply { + GalleryDialog(context, galleries[position].id).apply { onChipClickedHandler.add { tag -> this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) } } @@ -240,13 +242,20 @@ class GalleryDialog(context: Context, private val galleryID: Int) : AlertDialog( CoroutineScope(Dispatchers.IO).launch { gallery.related.forEach { galleryID -> - Cache.getInstance(context, galleryID).getGalleryBlock()?.let { - galleries.add(galleryID) + Cache.getInstance(context, galleryID.toString()).getGalleryBlock()?.let { + galleries.add( + Hitomi.SearchResult( + it.id.toString(), + it.title, + it.thumbnails.first(), + it.artists + ) + ) } - } - withContext(Dispatchers.Main) { - adapter.notifyDataSetChanged() + withContext(Dispatchers.Main) { + adapter.notifyDataSetChanged() + } } } } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCard.kt b/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt similarity index 91% rename from app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCard.kt rename to app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt index 0b5a32b1..585894b5 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCard.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt @@ -11,7 +11,7 @@ import androidx.core.graphics.drawable.DrawableCompat import xyz.quaver.pupil.R import xyz.quaver.pupil.databinding.ProgressCardViewBinding -class ProgressCard @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, defStyle: Int = R.attr.cardViewStyle) : CardView(context, attr, defStyle) { +class ProgressCardView @JvmOverloads constructor(context: Context, attr: AttributeSet? = null, defStyle: Int = R.attr.cardViewStyle) : CardView(context, attr, defStyle) { enum class Type { LOADING, diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/MainView.java b/app/src/main/java/xyz/quaver/pupil/ui/view/SwipePageTurnView.java similarity index 97% rename from app/src/main/java/xyz/quaver/pupil/ui/view/MainView.java rename to app/src/main/java/xyz/quaver/pupil/ui/view/SwipePageTurnView.java index 2c37eb83..fe42c007 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/view/MainView.java +++ b/app/src/main/java/xyz/quaver/pupil/ui/view/SwipePageTurnView.java @@ -35,7 +35,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.content.res.AppCompatResources; import androidx.core.content.ContextCompat; import androidx.core.view.NestedScrollingChild; @@ -48,7 +47,7 @@ import androidx.core.widget.TextViewCompat; import xyz.quaver.pupil.R; @SuppressWarnings("NullableProblems") -public class MainView extends ViewGroup implements NestedScrollingChild, NestedScrollingParent { +public class SwipePageTurnView extends ViewGroup implements NestedScrollingChild, NestedScrollingParent { private static final int PAGE_TURN_LAYOUT_SIZE = 48; private static final int PAGE_TURN_ANIM_DURATION = 500; @@ -84,15 +83,15 @@ public class MainView extends ViewGroup implements NestedScrollingChild, NestedS private OnPageTurnListener mOnPageTurnListener; - public MainView(@NonNull Context context) { + public SwipePageTurnView(@NonNull Context context) { this(context, null); } - public MainView(@NonNull Context context, AttributeSet attr) { + public SwipePageTurnView(@NonNull Context context, AttributeSet attr) { this(context, attr, 0); } - public MainView(@NonNull Context context, AttributeSet attr, int defStyle) { + public SwipePageTurnView(@NonNull Context context, AttributeSet attr, int defStyle) { super(context, attr, defStyle); setWillNotDraw(false); diff --git a/app/src/main/java/xyz/quaver/pupil/util/downloader/Cache.kt b/app/src/main/java/xyz/quaver/pupil/util/downloader/Cache.kt index 60d8ef25..247e1711 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/downloader/Cache.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/downloader/Cache.kt @@ -32,9 +32,8 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import okhttp3.Request -import xyz.quaver.Code import xyz.quaver.hitomi.GalleryBlock -import xyz.quaver.hitomi.Reader +import xyz.quaver.hitomi.GalleryInfo import xyz.quaver.io.FileX import xyz.quaver.io.util.* import xyz.quaver.pupil.client @@ -46,24 +45,24 @@ import java.util.concurrent.ConcurrentHashMap @Serializable data class Metadata( var galleryBlock: GalleryBlock? = null, - var reader: Reader? = null, + var reader: GalleryInfo? = null, var imageList: MutableList? = null ) { fun copy(): Metadata = Metadata(galleryBlock, reader, imageList?.let { MutableList(it.size) { i -> it[i] } }) } -class Cache private constructor(context: Context, val galleryID: Int) : ContextWrapper(context) { +class Cache private constructor(context: Context, val galleryID: String) : ContextWrapper(context) { companion object { - val instances = ConcurrentHashMap() + val instances = ConcurrentHashMap() - fun getInstance(context: Context, galleryID: Int) = + fun getInstance(context: Context, galleryID: String) = instances[galleryID] ?: synchronized(this) { - instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) } + instances[galleryID] ?: Cache(context, galleryID).also { instances[galleryID] = it } } @Synchronized - fun delete(context: Context, galleryID: Int) { + fun delete(context: Context, galleryID: String) { File(context.cacheDir, "imageCache/$galleryID").deleteRecursively() instances.remove(galleryID) } @@ -111,8 +110,8 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW suspend fun getGalleryBlock(): GalleryBlock? { val sources = listOf( - { xyz.quaver.hitomi.getGalleryBlock(galleryID) }, - { xyz.quaver.hiyobi.getGalleryBlock(galleryID) } + { xyz.quaver.hitomi.getGalleryBlock(galleryID.toInt()) } + // { xyz.quaver.hiyobi.getGalleryBlock(galleryID) } ) return metadata.galleryBlock @@ -154,22 +153,17 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW }.getOrNull()?.uri } } } ?: Uri.EMPTY - suspend fun getReader(): Reader? { + suspend fun getReader(): GalleryInfo? { val mirrors = Preferences.get("mirrors").let { if (it.isEmpty()) emptyList() else it.split('>') } val sources = mapOf( - Code.HITOMI to { xyz.quaver.hitomi.getReader(galleryID) }, - Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) } - ).let { - if (mirrors.isNotEmpty()) - it.toSortedMap{ o1, o2 -> mirrors.indexOf(o1.name) - mirrors.indexOf(o2.name) } - else - it - } + "hitomi" to { xyz.quaver.hitomi.getGalleryInfo(galleryID.toInt()) }, + //Code.HIYOBI to { xyz.quaver.hiyobi.getReader(galleryID) } + ) return metadata.reader ?: withContext(Dispatchers.IO) { - var reader: Reader? = null + var reader: GalleryInfo? = null for (source in sources) { reader = try { @@ -187,7 +181,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW metadata.reader = it if (metadata.imageList == null) - metadata.imageList = MutableList(reader.galleryInfo.files.size) { null } + metadata.imageList = MutableList(reader.files.size) { null } } } } @@ -206,7 +200,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW setMetadata { metadata -> metadata.imageList!![index] = fileName } } - private val lock = ConcurrentHashMap() + private val lock = ConcurrentHashMap() @Suppress("BlockingMethodInNonBlockingContext") fun moveToDownload() = CoroutineScope(Dispatchers.IO).launch { val downloadFolder = downloadFolder ?: return@launch diff --git a/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadManager.kt b/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadManager.kt index 9b2e209c..e57fdf73 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadManager.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/downloader/DownloadManager.kt @@ -57,8 +57,8 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con }.invoke() private var prevDownloadFolder: FileX? = null - private var downloadFolderMapInstance: MutableMap? = null - val downloadFolderMap: MutableMap + private var downloadFolderMapInstance: MutableMap? = null + val downloadFolderMap: MutableMap @Synchronized get() { if (prevDownloadFolder != downloadFolder) { @@ -68,14 +68,14 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con val data = if (file.exists()) kotlin.runCatching { - file.readText()?.let { Json.decodeFromString>(it) } + file.readText()?.let { Json.decodeFromString>(it) } }.onFailure { file.delete() }.getOrNull() else null data ?: { file.createNewFile() - mutableMapOf() + mutableMapOf() }.invoke() }.invoke() } @@ -85,7 +85,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con @Synchronized - fun isDownloading(galleryID: Int): Boolean { + fun isDownloading(galleryID: String): Boolean { val isThisGallery: (Call) -> Boolean = { (it.request().tag() as? DownloadService.Tag)?.galleryID == galleryID } return downloadFolderMap.containsKey(galleryID) @@ -93,11 +93,11 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con } @Synchronized - fun getDownloadFolder(galleryID: Int): FileX? = + fun getDownloadFolder(galleryID: String): FileX? = downloadFolderMap[galleryID]?.let { downloadFolder.getChild(it) } @Synchronized - fun addDownloadFolder(galleryID: Int) { + fun addDownloadFolder(galleryID: String) { val name = runBlocking { Cache.getInstance(this@DownloadManager, galleryID).getGalleryBlock() }?.formatDownloadFolder() ?: return @@ -116,7 +116,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con } @Synchronized - fun deleteDownloadFolder(galleryID: Int) { + fun deleteDownloadFolder(galleryID: String) { downloadFolderMap[galleryID]?.let { kotlin.runCatching { downloadFolder.getChild(it).deleteRecursively() diff --git a/app/src/main/java/xyz/quaver/pupil/util/misc.kt b/app/src/main/java/xyz/quaver/pupil/util/misc.kt index 50f1b862..c4272cff 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt @@ -26,9 +26,8 @@ import androidx.core.content.ContextCompat import kotlinx.serialization.json.* import okhttp3.OkHttpClient import okhttp3.Request -import xyz.quaver.Code import xyz.quaver.hitomi.GalleryBlock -import xyz.quaver.hitomi.Reader +import xyz.quaver.hitomi.GalleryInfo import xyz.quaver.hitomi.getReferer import xyz.quaver.hitomi.imageUrlFromImage import xyz.quaver.hiyobi.createImgList @@ -103,11 +102,17 @@ fun GalleryBlock.formatDownloadFolderTest(format: String): String = } }.replace(Regex("""[*\\|"?><:/]"""), "").ellipsize(127) -val Reader.requestBuilders: List +val GalleryInfo.requestBuilders: List get() { - val galleryID = this.galleryInfo.id ?: 0 + val galleryID = this.id ?: 0 val lowQuality = Preferences["low_quality", true] + return this.files.map { + Request.Builder() + .url(imageUrlFromImage(galleryID, it, !lowQuality)) + .header("Referer", getReferer(galleryID)) + } +/* return when(code) { Code.HITOMI -> { this.galleryInfo.files.map { @@ -122,9 +127,8 @@ val Reader.requestBuilders: List .url(it.path) } } - } + }*/ } - fun String.ellipsize(n: Int): String = if (this.length > n) this.slice(0 until n) + "…" diff --git a/app/src/main/java/xyz/quaver/pupil/util/update.kt b/app/src/main/java/xyz/quaver/pupil/util/update.kt index dab3ba5f..7f755973 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/update.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt @@ -46,7 +46,6 @@ import okhttp3.Request import okhttp3.Response import ru.noties.markwon.Markwon import xyz.quaver.hitomi.GalleryBlock -import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.getGalleryBlock import xyz.quaver.hitomi.getReader import xyz.quaver.io.FileX @@ -196,7 +195,7 @@ fun checkUpdate(context: Context, force: Boolean = false) { } } -fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List) -> Unit)? = null) { +fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List) -> Unit)? = null) { if (!URLUtil.isValidUrl(url)) { onFailure?.invoke(IllegalArgumentException()) return @@ -214,7 +213,7 @@ fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: (( override fun onResponse(call: Call, response: Response) { kotlin.runCatching { - Json.decodeFromString>(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]").let { + Json.decodeFromString>(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]").let { favorites.addAll(it) onSuccess?.invoke(it) } @@ -237,111 +236,4 @@ private val receiver = object: BroadcastReceiver() { } } } -} -@SuppressLint("RestrictedApi") -fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() { - registerReceiver(receiver, IntentFilter().apply { addAction(receiver.ACTION_CANCEL) }) - - val notificationManager = NotificationManagerCompat.from(this) - val action = NotificationCompat.Action.Builder(0, getText(android.R.string.cancel), - PendingIntent.getBroadcast(this, R.id.notification_import_cancel_action.normalizeID(), Intent(receiver.ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT) - ).build() - val notification = NotificationCompat.Builder(this, "import") - .setContentTitle(getText(R.string.import_old_galleries_notification)) - .setProgress(0, 0, true) - .addAction(action) - .setSmallIcon(R.drawable.ic_notification) - .setOngoing(true) - - DownloadService.cancel(this) - - job?.cancel() - job = CoroutineScope(Dispatchers.IO).launch { - val images = listOf( - "jpg", - "png", - "gif", - "webp" - ) - - val downloadFolders = downloadFolder.listFiles { folder -> - folder.isDirectory && !downloadFolderMap.values.contains(folder.name) - }?.map { - if (it !is FileX) - FileX(this@migrate, it) - else - it - } - - if (downloadFolders.isNullOrEmpty()) return@launch - - downloadFolders.forEachIndexed { index, folder -> - notification - .setContentText(getString(R.string.import_old_galleries_notification_text, index, downloadFolders.size)) - .setProgress(index, downloadFolders.size, false) - notificationManager.notify(R.id.notification_id_import, notification.build()) - - val metadata = kotlin.runCatching { - folder.getChild(".metadata").readText()?.let { Json.parseToJsonElement(it) } - }.getOrNull() - - val galleryID = metadata?.get("reader")?.get("galleryInfo")?.get("id")?.content?.toIntOrNull() - ?: folder.name.toIntOrNull() ?: return@forEachIndexed - - val galleryBlock: GalleryBlock? = kotlin.runCatching { - metadata?.get("galleryBlock")?.let { Json.decodeFromJsonElement(it) } - }.getOrNull() ?: kotlin.runCatching { - getGalleryBlock(galleryID) - }.getOrNull() ?: kotlin.runCatching { - xyz.quaver.hiyobi.getGalleryBlock(galleryID) - }.getOrNull() - - val reader: Reader? = kotlin.runCatching { - metadata?.get("reader")?.let { Json.decodeFromJsonElement(it) } - }.getOrNull() ?: kotlin.runCatching { - getReader(galleryID) - }.getOrNull() ?: kotlin.runCatching { - xyz.quaver.hiyobi.getReader(galleryID) - }.getOrNull() - - metadata?.get("thumbnail")?.jsonPrimitive?.contentOrNull?.also { thumbnail -> - val file = folder.getChild(".thumbnail").also { - if (it.exists()) - it.delete() - it.createNewFile() - } - - file.writeBytes(Base64.decode(thumbnail, Base64.DEFAULT)) - } - - val list: MutableList = - MutableList(reader!!.galleryInfo.files.size) { null } - - folder.list { _, name -> - name?.substringAfterLast('.') in images - }?.sorted()?.take(list.size)?.forEachIndexed { i, name -> - list[i] = name - } - - folder.getChild(".metadata").also { if (it.exists()) it.delete(); it.createNewFile() }.writeText( - Json.encodeToString(Metadata(galleryBlock, reader, list)) - ) - - Cache.delete(this@migrate, galleryID) - downloadFolderMap[galleryID] = folder.name - - downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile(); it.writeText(Json.encodeToString(downloadFolderMap)) } - } - - notification - .setContentText(getText(R.string.import_old_galleries_notification_done)) - .setProgress(0, 0, false) - .setOngoing(false) - .mActions.clear() - notificationManager.notify(R.id.notification_id_import, notification.build()) - - kotlin.runCatching { - unregisterReceiver(receiver) - } - } } \ No newline at end of file diff --git a/app/src/main/res/layout/main_activity_content.xml b/app/src/main/res/layout/main_activity_content.xml index abbf1aca..29150271 100644 --- a/app/src/main/res/layout/main_activity_content.xml +++ b/app/src/main/res/layout/main_activity_content.xml @@ -24,8 +24,8 @@ android:layout_height="match_parent" tools:context=".ui.MainActivity"> - @@ -52,7 +52,7 @@ - + . --> - + app:layout_constraintTop_toBottomOf="@id/title" /> + app:layout_constraintTop_toBottomOf="@id/series" + app:layout_constraintLeft_toRightOf="@id/thumbnail" /> + app:layout_constraintTop_toBottomOf="@id/type" + app:layout_constraintLeft_toRightOf="@id/thumbnail" /> + app:constraint_referenced_ids="thumbnail, tag_group"/> - \ No newline at end of file + \ No newline at end of file