/* * 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.ui import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.graphics.drawable.Animatable import android.net.Uri import android.os.Build import android.os.Bundle import android.text.* import android.text.style.AlignmentSpan import android.util.TypedValue import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.view.WindowManager import android.view.inputmethod.EditorInfo import android.widget.* import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.view.GravityCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.arlib.floatingsearchview.FloatingSearchView import com.arlib.floatingsearchview.FloatingSearchViewDayNight import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion import com.arlib.floatingsearchview.util.view.SearchInputView import com.bumptech.glide.Glide import com.google.android.material.appbar.AppBarLayout import com.google.android.material.snackbar.Snackbar import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main_content.* import kotlinx.android.synthetic.main.settings_activity.* import kotlinx.coroutines.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import net.rdrei.android.dirchooser.DirectoryChooserActivity import xyz.quaver.hitomi.doSearch import xyz.quaver.hitomi.getGalleryIDsFromNozomi import xyz.quaver.hitomi.getSuggestionsForQuery import xyz.quaver.io.FileX import xyz.quaver.pupil.R import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.favorites import xyz.quaver.pupil.histories import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.types.TagSuggestion import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.ui.dialog.DownloadLocationDialog import xyz.quaver.pupil.ui.dialog.GalleryDialog import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.downloader.Cache import xyz.quaver.pupil.util.downloader.DownloadManager import java.io.File import java.util.* import kotlin.collections.ArrayList import kotlin.math.abs import kotlin.math.ceil import kotlin.math.min import kotlin.math.roundToInt class MainActivity : AppCompatActivity() { enum class Mode { SEARCH, HISTORY, DOWNLOAD, FAVORITE } enum class SortMode { NEWEST, POPULAR } private val galleries = ArrayList() private var query = "" set(value) { field = value with(findViewById(R.id.search_bar_text)) { if (text.toString() != value) setText(query, TextView.BufferType.EDITABLE) } } private var queryStack = mutableListOf() private var mode = Mode.SEARCH private var sortMode = SortMode.NEWEST private var galleryIDs: Deferred>? = null private var totalItems = 0 private var loadingJob: Job? = null private var currentPage = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val lockManager = try { LockManager(this) } catch (e: Exception) { android.app.AlertDialog.Builder(this).apply { setTitle(R.string.warning) setMessage(R.string.lock_corrupted) setPositiveButton(android.R.string.ok) { _, _ -> finish() } }.show() return } if (lockManager.isNotEmpty()) startActivityForResult(Intent(this, LockActivity::class.java), R.id.request_lock.normalizeID()) if (intent.action == Intent.ACTION_VIEW) { intent.dataString?.let { url -> restore(favorites, url, onFailure = { Snackbar.make(this.main_recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show() }, onSuccess = { Snackbar.make(this.main_recyclerview, getString(R.string.settings_restore_success, it.size), Snackbar.LENGTH_LONG).show() } ) } } setContentView(R.layout.activity_main) if (Preferences["download_folder", ""].isEmpty()) DownloadLocationDialog(this).show() checkUpdate(this) initView() } @OptIn(ExperimentalStdlibApi::class) override fun onBackPressed() { when { main_drawer_layout.isDrawerOpen(GravityCompat.START) -> main_drawer_layout.closeDrawer(GravityCompat.START) queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread { query = queryStack.last() cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } else -> super.onBackPressed() } } override fun onDestroy() { super.onDestroy() (main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel() } override fun onResume() { if (Preferences["security_mode"]) window.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) else window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) super.onResume() } 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) { runOnUiThread { currentPage-- cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } } true } KeyEvent.KEYCODE_VOLUME_DOWN -> { if (currentPage < maxPage) { runOnUiThread { currentPage++ cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } } true } else -> super.onKeyDown(keyCode, event) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when(requestCode) { R.id.request_settings.normalizeID() -> { runOnUiThread { cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } } R.id.request_lock.normalizeID() -> { if (resultCode != Activity.RESULT_OK) finish() } R.id.request_download_folder.normalizeID() -> { if (resultCode == Activity.RESULT_OK) { data?.data?.also { uri -> val takeFlags: Int = intent.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) contentResolver.takePersistableUriPermission(uri, takeFlags) if (FileX(this, uri).canWrite()) Preferences["download_folder"] = uri.toString() else Snackbar.make( settings, R.string.settings_download_folder_not_writable, Snackbar.LENGTH_LONG ).show() } } } R.id.request_download_folder_old.normalizeID() -> { if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { val directory = data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!! if (!File(directory).canWrite()) Snackbar.make( settings, R.string.settings_download_folder_not_writable, Snackbar.LENGTH_LONG ).show() else Preferences["download_folder"] = File(directory).canonicalPath } } } } private fun initView() { var prevP1 = 0 main_appbar_layout.addOnOffsetChangedListener( AppBarLayout.OnOffsetChangedListener { _, p1 -> main_searchview.translationY = p1.toFloat() main_recyclerview.scrollBy(0, prevP1 - p1) with(main_fab) { if (prevP1 > p1) hideMenuButton(true) else if (prevP1 < p1) showMenuButton(true) } prevP1 = p1 } ) //NavigationView main_nav_view.setNavigationItemSelectedListener { runOnUiThread { main_drawer_layout.closeDrawers() when(it.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)))) } R.id.main_drawer_github -> { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github)))) } R.id.main_drawer_homepage -> { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page)))) } R.id.main_drawer_email -> { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email)))) } R.id.main_drawer_kakaotalk -> { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord)))) } } } true } with(main_fab_cancel) { setImageResource(R.drawable.cancel) setOnClickListener { DownloadService.cancel(this@MainActivity) } } with(main_fab_jump) { setImageResource(R.drawable.ic_jump) setOnClickListener { val perPage = Preferences["per_page", "25"].toInt() val editText = EditText(context) AlertDialog.Builder(context).apply { setView(editText) setTitle(R.string.main_jump_title) setMessage(getString( R.string.main_jump_message, currentPage+1, ceil(totalItems / perPage.toDouble()).roundToInt() )) setPositiveButton(android.R.string.ok) { _, _ -> currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1 runOnUiThread { cancelFetch() clearGalleries() loadBlocks() } } }.show() } } with(main_fab_random) { setImageResource(R.drawable.shuffle_variant) setOnClickListener { runBlocking { withTimeoutOrNull(100) { galleryIDs?.await() } }.let { if (it?.isEmpty() == false) { val galleryID = it.random() GalleryDialog( this@MainActivity, Glide.with(this@MainActivity), galleryID ).apply { onChipClickedHandler.add { runOnUiThread { query = it.toQuery() currentPage = 0 cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } dismiss() } }.show() } } } } with(main_fab_id) { setImageResource(R.drawable.numeric) setOnClickListener { val editText = EditText(context).apply { inputType = InputType.TYPE_CLASS_NUMBER } AlertDialog.Builder(context).apply { setView(editText) setTitle(R.string.main_open_gallery_by_id) setPositiveButton(android.R.string.ok) { _, _ -> val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton val intent = Intent(this@MainActivity, ReaderActivity::class.java).apply { putExtra("galleryID", galleryID) } startActivity(intent) } }.show() } } setupSearchBar() setupRecyclerView() fetchGalleries(query, sortMode) loadBlocks() } @SuppressLint("ClickableViewAccessibility") private fun setupRecyclerView() { with(main_recyclerview) { adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply { onChipClickedHandler.add { runOnUiThread { query = it.toQuery() currentPage = 0 cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } } onDownloadClickedHandler = { position -> val galleryID = galleries[position] if (Preferences["cache_disable"]) Toast.makeText(context, R.string.settings_download_when_cache_disable_warning, Toast.LENGTH_SHORT).show() else { if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress DownloadService.cancel(this@MainActivity, galleryID) } else { DownloadManager.getInstance(context).addDownloadFolder(galleryID) DownloadService.download(this@MainActivity, galleryID) } } closeAllItems() } onDeleteClickedHandler = { position -> val galleryID = galleries[position] DownloadService.delete(this@MainActivity, galleryID) histories.remove(galleryID) if (this@MainActivity.mode != Mode.SEARCH) runOnUiThread { cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } completeFlag.put(galleryID, false) closeAllItems() } } ItemClickSupport.addTo(this).apply { onItemClickListener = listener@{ _, position, v -> if (v !is CardView) return@listener val intent = Intent(this@MainActivity, ReaderActivity::class.java) intent.putExtra("galleryID", galleries[position]) //TODO: Maybe sprinkling some transitions will be nice :D startActivity(intent) } onItemLongClickListener = listener@{ _, position, v -> if (v !is CardView) return@listener false val galleryID = galleries[position] GalleryDialog( this@MainActivity, Glide.with(this@MainActivity), galleryID ).apply { onChipClickedHandler.add { runOnUiThread { query = it.toQuery() currentPage = 0 cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } dismiss() } }.show() true } } var origin = 0f var target = -1 val perPage = Preferences["per_page", "25"].toInt() setOnTouchListener { _, event -> when(event.action) { MotionEvent.ACTION_UP -> { origin = 0f with(main_recyclerview.adapter as GalleryBlockAdapter) { if(showPrev) { showPrev = false val prev = main_recyclerview.layoutManager?.getChildAt(0) if (prev is LinearLayout) { val icon = prev.findViewById(R.id.icon_prev) prev.layoutParams.height = 1 icon.layoutParams.height = 1 icon.rotation = 180f } prev?.requestLayout() notifyItemRemoved(0) } if(showNext) { showNext = false val next = main_recyclerview.layoutManager?.let { getChildAt(childCount-1) } if (next is LinearLayout) { val icon = next.findViewById(R.id.icon_next) next.layoutParams.height = 1 icon.layoutParams.height = 1 icon.rotation = 0f } next?.requestLayout() notifyItemRemoved(itemCount) } } if (target != -1) { currentPage = target runOnUiThread { cancelFetch() clearGalleries() loadBlocks() } target = -1 } } MotionEvent.ACTION_DOWN -> origin = event.y MotionEvent.ACTION_MOVE -> { if (origin == 0f) origin = event.y val dist = event.y - origin when { !canScrollVertically(-1) -> { //TOP //Scrolling UP if (dist > 0 && currentPage != 0) { with(main_recyclerview.adapter as GalleryBlockAdapter) { if(!showPrev) { showPrev = true notifyItemInserted(0) } } val prev = main_recyclerview.layoutManager?.getChildAt(0) if (prev is LinearLayout) { val icon = prev.findViewById(R.id.icon_prev) val text = prev.findViewById(R.id.text_prev).apply { text = getString(R.string.main_move, currentPage) } if (dist < 360) { prev.layoutParams.height = (dist/2).roundToInt() icon.layoutParams.height = (dist/2).roundToInt() icon.rotation = dist+180 text.layoutParams.width = dist.roundToInt() target = -1 } else { prev.layoutParams.height = 180 icon.layoutParams.height = 180 icon.rotation = 180f text.layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT target = currentPage-1 } } prev?.requestLayout() return@setOnTouchListener true } else { with(main_recyclerview.adapter as GalleryBlockAdapter) { if(showPrev) { showPrev = false val prev = main_recyclerview.layoutManager?.getChildAt(0) if (prev is LinearLayout) { val icon = prev.findViewById(R.id.icon_prev) prev.layoutParams.height = 1 icon.layoutParams.height = 1 icon.rotation = 180f } prev?.requestLayout() notifyItemRemoved(0) } } } } !canScrollVertically(1) -> { //BOTTOM //Scrolling DOWN if (dist < 0 && currentPage != ceil(totalItems.toDouble()/perPage).roundToInt()-1) { with(main_recyclerview.adapter as GalleryBlockAdapter) { if(!showNext) { showNext = true notifyItemInserted(itemCount-1) } } val next = main_recyclerview.layoutManager?.let { getChildAt(childCount-1) } val absDist = abs(dist) if (next is LinearLayout) { val icon = next.findViewById(R.id.icon_next) val text = next.findViewById(R.id.text_next).apply { text = getString(R.string.main_move, currentPage+2) } if (absDist < 360) { next.layoutParams.height = (absDist/2).roundToInt() icon.layoutParams.height = (absDist/2).roundToInt() icon.rotation = -absDist text.layoutParams.width = absDist.roundToInt() target = -1 } else { next.layoutParams.height = 180 icon.layoutParams.height = 180 icon.rotation = 0f text.layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT target = currentPage+1 } } next?.requestLayout() return@setOnTouchListener true } else { with(main_recyclerview.adapter as GalleryBlockAdapter) { if(showNext) { showNext = false val next = main_recyclerview.layoutManager?.let { getChildAt(childCount-1) } if (next is LinearLayout) { val icon = next.findViewById(R.id.icon_next) next.layoutParams.height = 1 icon.layoutParams.height = 1 icon.rotation = 180f } next?.requestLayout() notifyItemRemoved(itemCount) } } } } } } } false } } } private var suggestionJob : Job? = null private fun setupSearchBar() { val searchInputView = findViewById(R.id.search_bar_text) //Change upper case letters to lower case searchInputView.addTextChangedListener(object: TextWatcher { override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { } override fun afterTextChanged(s: Editable?) { s ?: return if (s.any { it.isUpperCase() }) s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault())) } }) searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI with(main_searchview as FloatingSearchViewDayNight) { val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json") if (!favoritesFile.exists()) { favoritesFile.createNewFile() favoritesFile.writeText("[]") } setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener { override fun onMenuOpened() { (this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems() } override fun onMenuClosed() { //Do Nothing } }) setOnMenuItemClickListener { when(it.itemId) { R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), R.id.request_settings.normalizeID()) R.id.main_menu_thin -> { main_recyclerview.apply { (adapter as GalleryBlockAdapter).apply { isThin = !isThin } adapter = adapter // Force to redraw } } R.id.main_menu_sort_newest -> { sortMode = SortMode.NEWEST it.isChecked = true runOnUiThread { currentPage = 0 cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } } R.id.main_menu_sort_popular -> { sortMode = SortMode.POPULAR it.isChecked = true runOnUiThread { currentPage = 0 cancelFetch() clearGalleries() fetchGalleries(query, sortMode) loadBlocks() } } } } setOnQueryChangeListener { _, query -> this@MainActivity.query = query suggestionJob?.cancel() clearSuggestions() if (query.isEmpty() or query.endsWith(' ')) { swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map { TagSuggestion(it.tag, -1, "", it.area ?: "tag") }) return@setOnQueryChangeListener } val currentQuery = query.split(" ").last().replace('_', ' ') suggestionJob = CoroutineScope(Dispatchers.IO).launch { val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }) suggestions.filter { val tag = "${it.n}:${it.s.replace(Regex("\\s"), "_")}" Tags(Json.decodeFromString(favoritesFile.readText())).contains(tag) }.reversed().forEach { suggestions.remove(it) suggestions.add(0, it) } withContext(Dispatchers.Main) { swapSuggestions(suggestions) } } } setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ -> item as TagSuggestion val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}" val color = TypedValue() theme.resolveAttribute(R.attr.colorControlNormal, color, true) leftIcon.setImageDrawable( ResourcesCompat.getDrawable( resources, when(item.n) { "female" -> R.drawable.gender_female "male" -> R.drawable.gender_male "language" -> R.drawable.translate "group" -> R.drawable.account_group "character" -> R.drawable.account_star "series" -> R.drawable.book_open "artist" -> R.drawable.brush else -> R.drawable.tag }, context.theme) ) with(suggestionView.findViewById(R.id.right_icon)) { if (Tags(Json.decodeFromString(favoritesFile.readText())).contains(tag)) setImageResource(R.drawable.ic_star_filled) else setImageResource(R.drawable.ic_star_empty) visibility = View.VISIBLE rotation = 0f isEnabled = true isClickable = true setOnClickListener { val favorites = Tags(Json.decodeFromString(favoritesFile.readText())) if (favorites.contains(tag)) { setImageResource(R.drawable.ic_star_empty) favorites.remove(tag) } else { setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star )) (drawable as Animatable).start() favorites.add(tag) } favoritesFile.writeText(Json.encodeToString(favorites.tags)) } } if (item.t == -1) { textView.text = item.s } else { val text = "${item.s}\n ${item.t}" val len = text.length val left = item.s.length textView.text = SpannableString(text).apply { val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE) setSpan(s, left, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) setSpan(SetLineOverlap(true), 1, len-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) setSpan(SetLineOverlap(false), len-1, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } } setOnSearchListener(object : FloatingSearchView.OnSearchListener { override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) { if (searchSuggestion !is TagSuggestion) return with(searchInputView.text) { delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length) append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ") } } override fun onSearchAction(currentQuery: String?) { //Do search on onFocusCleared() } }) setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener { override fun onFocus() { if (query.isEmpty() or query.endsWith(' ')) swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map { TagSuggestion(it.tag, -1, "", it.area ?: "tag") }) } override fun onFocusCleared() { suggestionJob?.cancel() runOnUiThread { cancelFetch() clearGalleries() currentPage = 0 fetchGalleries(query, sortMode) loadBlocks() } } }) attachNavigationDrawerToMenuButton(main_drawer_layout) } } private fun cancelFetch() { galleryIDs?.cancel() loadingJob?.cancel() } private fun clearGalleries() { galleries.clear() with(main_recyclerview.adapter as GalleryBlockAdapter?) { this ?: return@with this.completeFlag.clear() this.notifyDataSetChanged() } main_appbar_layout.setExpanded(true) main_noresult.visibility = View.INVISIBLE main_progressbar.show() } private fun fetchGalleries(query: String, sortMode: SortMode) { val defaultQuery: String = Preferences["default_query"] if (query != queryStack.lastOrNull()) { queryStack.remove(query) queryStack.add(query) } if (query.isNotEmpty() && mode != Mode.SEARCH) { Snackbar.make(this@MainActivity.main_recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply { setAction(android.R.string.yes) { 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.also { totalItems = it.size } else -> { val result = doSearch(query).sorted() downloads.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.filter { result.binarySearch(it) >= 0 }.also { totalItems = it.size } } } } } } } private fun loadBlocks() { 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") } } catch (e: Exception) { if (e.message != "No result") FirebaseCrashlytics.getInstance().recordException(e) withContext(Dispatchers.Main) { main_noresult.visibility = View.VISIBLE main_progressbar.hide() } return@launch } 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) { main_progressbar.hide() galleries.add(it) main_recyclerview.adapter!!.notifyItemInserted(galleries.size - 1) } } } } } } }