- table.select("div.manga")
- }.mapNotNull { it.selectFirst("a") }.reversed().mapIndexed { i, a ->
- val href = a.relUrl("href")
- MangaChapter(
- id = generateUid(href),
- name = a.text().trim(),
- number = i + 1,
- url = href,
- uploadDate = 0L,
- source = source,
- scanlator = null,
- branch = null,
- )
- }
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
index 4c1768438..8583a7adc 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
@@ -14,8 +14,10 @@ import com.google.android.material.color.DynamicColors
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.callbackFlow
-import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.ZoomMode
+import org.koitharu.kotatsu.parsers.model.MangaSource
+import org.koitharu.kotatsu.utils.ext.getEnumValue
+import org.koitharu.kotatsu.utils.ext.putEnumValue
import org.koitharu.kotatsu.utils.ext.toUriOrNull
import java.io.File
import java.text.DateFormat
@@ -27,12 +29,12 @@ class AppSettings(context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
var listMode: ListMode
- get() = prefs.getString(KEY_LIST_MODE, null)?.findEnumValue(ListMode.values()) ?: ListMode.DETAILED_LIST
- set(value) = prefs.edit { putString(KEY_LIST_MODE, value.name) }
+ get() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.DETAILED_LIST)
+ set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) }
var defaultSection: AppSection
- get() = prefs.getString(KEY_APP_SECTION, null)?.findEnumValue(AppSection.values()) ?: AppSection.HISTORY
- set(value) = prefs.edit { putString(KEY_APP_SECTION, value.name) }
+ get() = prefs.getEnumValue(KEY_APP_SECTION, AppSection.HISTORY)
+ set(value) = prefs.edit { putEnumValue(KEY_APP_SECTION, value) }
val theme: Int
get() = prefs.getString(KEY_THEME, null)?.toIntOrNull() ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
@@ -43,9 +45,6 @@ class AppSettings(context: Context) {
val isAmoledTheme: Boolean
get() = prefs.getBoolean(KEY_THEME_AMOLED, false)
- val isToolbarHideWhenScrolling: Boolean
- get() = prefs.getBoolean(KEY_HIDE_TOOLBAR, true)
-
var gridSize: Int
get() = prefs.getInt(KEY_GRID_SIZE, 100)
set(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }
@@ -96,7 +95,7 @@ class AppSettings(context: Context) {
set(value) = prefs.edit { putBoolean(KEY_REVERSE_CHAPTERS, value) }
val zoomMode: ZoomMode
- get() = prefs.getString(KEY_ZOOM_MODE, null)?.findEnumValue(ZoomMode.values()) ?: ZoomMode.FIT_CENTER
+ get() = prefs.getEnumValue(KEY_ZOOM_MODE, ZoomMode.FIT_CENTER)
val trackSources: Set
get() = prefs.getStringSet(KEY_TRACK_SOURCES, null) ?: arraySetOf(TRACK_FAVOURITES, TRACK_HISTORY)
@@ -148,6 +147,10 @@ class AppSettings(context: Context) {
val isSuggestionsExcludeNsfw: Boolean
get() = prefs.getBoolean(KEY_SUGGESTIONS_EXCLUDE_NSFW, false)
+ var isSearchSingleSource: Boolean
+ get() = prefs.getBoolean(KEY_SEARCH_SINGLE_SOURCE, false)
+ set(value) = prefs.edit { putBoolean(KEY_SEARCH_SINGLE_SOURCE, value) }
+
fun isPagesPreloadAllowed(cm: ConnectivityManager): Boolean {
return when (prefs.getString(KEY_PAGES_PRELOAD, null)?.toIntOrNull()) {
NETWORK_ALWAYS -> true
@@ -162,6 +165,18 @@ class AppSettings(context: Context) {
else -> SimpleDateFormat(format, Locale.getDefault())
}
+ fun getSuggestionsTagsBlacklistRegex(): Regex? {
+ val string = prefs.getString(KEY_SUGGESTIONS_EXCLUDE_TAGS, null)?.trimEnd(' ', ',')
+ if (string.isNullOrEmpty()) {
+ return null
+ }
+ val tags = string.split(',')
+ val regex = tags.joinToString(prefix = "(", separator = "|", postfix = ")") { tag ->
+ Regex.escape(tag.trim())
+ }
+ return Regex(regex, RegexOption.IGNORE_CASE)
+ }
+
fun getMangaSources(includeHidden: Boolean): List {
val list = MangaSource.values().toMutableList()
list.remove(MangaSource.LOCAL)
@@ -195,10 +210,6 @@ class AppSettings(context: Context) {
}
}
- private fun > String.findEnumValue(values: Array): E? {
- return values.find { it.name == this }
- }
-
companion object {
const val PAGE_SWITCH_TAPS = "taps"
@@ -213,7 +224,6 @@ class AppSettings(context: Context) {
const val KEY_DYNAMIC_THEME = "dynamic_theme"
const val KEY_THEME_AMOLED = "amoled_theme"
const val KEY_DATE_FORMAT = "date_format"
- const val KEY_HIDE_TOOLBAR = "hide_toolbar"
const val KEY_SOURCES_ORDER = "sources_order"
const val KEY_SOURCES_HIDDEN = "sources_hidden"
const val KEY_TRAFFIC_WARNING = "traffic_warning"
@@ -249,17 +259,17 @@ class AppSettings(context: Context) {
const val KEY_PAGES_PRELOAD = "pages_preload"
const val KEY_SUGGESTIONS = "suggestions"
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"
+ const val KEY_SUGGESTIONS_EXCLUDE_TAGS = "suggestions_exclude_tags"
+ const val KEY_SEARCH_SINGLE_SOURCE = "search_single_source"
const val KEY_SHIKIMORI = "shikimori"
// About
const val KEY_APP_UPDATE = "app_update"
const val KEY_APP_UPDATE_AUTO = "app_update_auto"
const val KEY_APP_TRANSLATION = "about_app_translation"
- const val KEY_APP_GRATITUDES = "about_gratitudes"
const val KEY_FEEDBACK_4PDA = "about_feedback_4pda"
const val KEY_FEEDBACK_DISCORD = "about_feedback_discord"
const val KEY_FEEDBACK_GITHUB = "about_feedback_github"
- const val KEY_SUPPORT_DEVELOPER = "about_support_developer"
private const val NETWORK_NEVER = 0
private const val NETWORK_ALWAYS = 1
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/SourceSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/SourceSettings.kt
index 7a6036d06..ea14c7342 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/SourceSettings.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/SourceSettings.kt
@@ -1,32 +1,29 @@
package org.koitharu.kotatsu.core.prefs
import android.content.Context
-import org.koitharu.kotatsu.core.model.MangaSource
+import androidx.core.content.edit
+import org.koitharu.kotatsu.parsers.config.ConfigKey
+import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
+import org.koitharu.kotatsu.parsers.model.MangaSource
+import org.koitharu.kotatsu.parsers.model.SortOrder
+import org.koitharu.kotatsu.utils.ext.getEnumValue
+import org.koitharu.kotatsu.utils.ext.ifNullOrEmpty
+import org.koitharu.kotatsu.utils.ext.putEnumValue
-interface SourceSettings {
+private const val KEY_SORT_ORDER = "sort_order"
- fun getDomain(defaultValue: String): String
+class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig {
- fun isUseSsl(defaultValue: Boolean): Boolean
+ private val prefs = context.getSharedPreferences(source.name, Context.MODE_PRIVATE)
- private class PrefSourceSettings(context: Context, source: MangaSource) : SourceSettings {
+ var defaultSortOrder: SortOrder?
+ get() = prefs.getEnumValue(KEY_SORT_ORDER, SortOrder::class.java)
+ set(value) = prefs.edit { putEnumValue(KEY_SORT_ORDER, value) }
- private val prefs = context.getSharedPreferences(source.name, Context.MODE_PRIVATE)
-
- override fun getDomain(defaultValue: String) = prefs.getString(KEY_DOMAIN, defaultValue)
- ?.takeUnless(String::isBlank)
- ?: defaultValue
-
- override fun isUseSsl(defaultValue: Boolean) = prefs.getBoolean(KEY_USE_SSL, defaultValue)
- }
-
- companion object {
-
- operator fun invoke(context: Context, source: MangaSource): SourceSettings =
- PrefSourceSettings(context, source)
-
- const val KEY_DOMAIN = "domain"
- const val KEY_USE_SSL = "ssl"
- const val KEY_AUTH = "auth"
+ @Suppress("UNCHECKED_CAST")
+ override fun get(key: ConfigKey): T {
+ return when (key) {
+ is ConfigKey.Domain -> prefs.getString(key.key, key.defaultValue).ifNullOrEmpty { key.defaultValue }
+ } as T
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/AppCrashHandler.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/AppCrashHandler.kt
index 002b8b05e..20a7bf0c3 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/ui/AppCrashHandler.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/AppCrashHandler.kt
@@ -3,20 +3,12 @@ package org.koitharu.kotatsu.core.ui
import android.content.Context
import android.content.Intent
import android.util.Log
-import java.io.PrintWriter
-import java.io.StringWriter
import kotlin.system.exitProcess
class AppCrashHandler(private val applicationContext: Context) : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread, e: Throwable) {
- val crashInfo = buildString {
- val writer = StringWriter()
- e.printStackTrace(PrintWriter(writer))
- append(writer.toString().trimIndent())
- }
- val intent = Intent(applicationContext, CrashActivity::class.java)
- intent.putExtra(Intent.EXTRA_TEXT, crashInfo)
+ val intent = CrashActivity.newIntent(applicationContext, e)
intent.flags = (Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
try {
applicationContext.startActivity(intent)
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/CrashActivity.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/CrashActivity.kt
index 4ea8222bf..7d4d31878 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/ui/CrashActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/CrashActivity.kt
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.ui
import android.app.Activity
import android.content.ActivityNotFoundException
+import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@@ -11,6 +12,7 @@ import android.view.View
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ActivityCrashBinding
import org.koitharu.kotatsu.main.ui.MainActivity
+import org.koitharu.kotatsu.parsers.util.ellipsize
import org.koitharu.kotatsu.utils.ShareHelper
class CrashActivity : Activity(), View.OnClickListener {
@@ -63,4 +65,19 @@ class CrashActivity : Activity(), View.OnClickListener {
}
}
}
+
+ companion object {
+
+ private const val MAX_TRACE_SIZE = 131071
+
+ fun newIntent(context: Context, error: Throwable): Intent {
+ val crashInfo = error
+ .stackTraceToString()
+ .trimIndent()
+ .ellipsize(MAX_TRACE_SIZE)
+ val intent = Intent(context, CrashActivity::class.java)
+ intent.putExtra(Intent.EXTRA_TEXT, crashInfo)
+ return intent
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/SortOrder.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/SortOrder.kt
new file mode 100644
index 000000000..92b9fd9ef
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/SortOrder.kt
@@ -0,0 +1,15 @@
+package org.koitharu.kotatsu.core.ui
+
+import androidx.annotation.StringRes
+import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.parsers.model.SortOrder
+
+@get:StringRes
+val SortOrder.titleRes: Int
+ get() = when (this) {
+ SortOrder.UPDATED -> R.string.updated
+ SortOrder.POPULARITY -> R.string.popular
+ SortOrder.RATING -> R.string.by_rating
+ SortOrder.NEWEST -> R.string.newest
+ SortOrder.ALPHABETICAL -> R.string.by_name
+ }
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt
index 060e748ac..26ccbf44a 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt
@@ -12,11 +12,14 @@ import org.koitharu.kotatsu.local.data.CbzFetcher
val uiModule
get() = module {
single {
- val httpClient = get().newBuilder()
- .cache(CoilUtils.createDefaultCache(androidContext()))
- .build()
+ val httpClientFactory = {
+ get().newBuilder()
+ .cache(CoilUtils.createDefaultCache(androidContext()))
+ .build()
+ }
ImageLoader.Builder(androidContext())
- .okHttpClient(httpClient)
+ .okHttpClient(httpClientFactory)
+ .launchInterceptorChainOnMainThread(false)
.componentRegistry(
ComponentRegistry.Builder()
.add(CbzFetcher())
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt
index 6ea44a8ac..96f369c56 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt
@@ -4,31 +4,37 @@ import android.app.ActivityOptions
import android.os.Bundle
import android.view.*
import android.widget.AdapterView
+import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
+import androidx.appcompat.widget.SearchView
import androidx.core.graphics.Insets
+import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.divider.MaterialDividerItemDecoration
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
-import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.download.ui.service.DownloadService
+import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
+import org.koitharu.kotatsu.utils.RecyclerViewScrollCallback
+import kotlin.math.roundToInt
-class ChaptersFragment : BaseFragment(),
+class ChaptersFragment :
+ BaseFragment(),
OnListItemClickListener,
ActionMode.Callback,
- AdapterView.OnItemSelectedListener {
+ AdapterView.OnItemSelectedListener,
+ MenuItem.OnActionExpandListener,
+ SearchView.OnQueryTextListener {
private val viewModel by sharedViewModel()
@@ -51,46 +57,44 @@ class ChaptersFragment : BaseFragment(),
chaptersAdapter = ChaptersAdapter(this)
selectionDecoration = ChaptersSelectionDecoration(view.context)
with(binding.recyclerViewChapters) {
- addItemDecoration(MaterialDividerItemDecoration(view.context, RecyclerView.VERTICAL))
addItemDecoration(selectionDecoration!!)
setHasFixedSize(true)
adapter = chaptersAdapter
}
- val branchesAdapter = BranchesAdapter()
- binding.spinnerBranches.adapter = branchesAdapter
- binding.spinnerBranches.onItemSelectedListener = this
-
+ binding.spinnerBranches?.let(::initSpinner)
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged)
- viewModel.branches.observe(viewLifecycleOwner) {
- branchesAdapter.setItems(it)
- binding.spinnerBranches.isVisible = it.size > 1
- }
- viewModel.selectedBranchIndex.observe(viewLifecycleOwner) {
- if (it != -1 && it != binding.spinnerBranches.selectedItemPosition) {
- binding.spinnerBranches.setSelection(it)
- }
- }
viewModel.isChaptersReversed.observe(viewLifecycleOwner) {
activity?.invalidateOptionsMenu()
}
+ viewModel.hasChapters.observe(viewLifecycleOwner) {
+ binding.textViewHolder.isGone = it
+ activity?.invalidateOptionsMenu()
+ }
}
override fun onDestroyView() {
chaptersAdapter = null
selectionDecoration = null
- binding.spinnerBranches.adapter = null
+ binding.spinnerBranches?.adapter = null
super.onDestroyView()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.opt_chapters, menu)
+ val searchMenuItem = menu.findItem(R.id.action_search)
+ searchMenuItem.setOnActionExpandListener(this)
+ val searchView = searchMenuItem.actionView as SearchView
+ searchView.setOnQueryTextListener(this)
+ searchView.setIconifiedByDefault(false)
+ searchView.queryHint = searchMenuItem.title
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.action_reversed).isChecked = viewModel.isChaptersReversed.value == true
+ menu.findItem(R.id.action_search).isVisible = viewModel.hasChapters.value == true
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
@@ -125,10 +129,11 @@ class ChaptersFragment : BaseFragment(),
)
startActivity(
ReaderActivity.newIntent(
- view.context,
- viewModel.manga.value ?: return,
- ReaderState(item.chapter.id, 0, 0)
- ), options.toBundle()
+ context = view.context,
+ manga = viewModel.manga.value ?: return,
+ state = ReaderState(item.chapter.id, 0, 0),
+ ),
+ options.toBundle()
)
}
@@ -166,15 +171,14 @@ class ChaptersFragment : BaseFragment(),
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
- viewModel.setSelectedBranch(binding.spinnerBranches.selectedItem as String?)
+ val spinner = binding.spinnerBranches ?: return
+ viewModel.setSelectedBranch(spinner.selectedItem as String?)
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
- val manga = viewModel.manga.value
mode.menuInflater.inflate(R.menu.mode_chapters, menu)
- mode.title = manga?.title
return true
}
@@ -184,12 +188,7 @@ class ChaptersFragment : BaseFragment(),
menu.findItem(R.id.action_save).isVisible = items.none { x ->
x.chapter.source == MangaSource.LOCAL
}
- mode.subtitle = resources.getQuantityString(
- R.plurals.chapters_from_x,
- items.size,
- items.size,
- chaptersAdapter?.itemCount ?: 0
- )
+ mode.title = items.size.toString()
return true
}
@@ -199,16 +198,55 @@ class ChaptersFragment : BaseFragment(),
actionMode = null
}
+ override fun onMenuItemActionExpand(item: MenuItem?): Boolean = true
+
+ override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
+ (item?.actionView as? SearchView)?.setQuery("", false)
+ viewModel.performChapterSearch(null)
+ return true
+ }
+
+ override fun onQueryTextSubmit(query: String?): Boolean = false
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ viewModel.performChapterSearch(newText)
+ return true
+ }
+
override fun onWindowInsetsChanged(insets: Insets) {
binding.recyclerViewChapters.updatePadding(
- left = insets.left,
- right = insets.right,
- bottom = insets.bottom + binding.spinnerBranches.height
+ bottom = insets.bottom + (binding.spinnerBranches?.height ?: 0),
)
}
+ private fun initSpinner(spinner: Spinner) {
+ val branchesAdapter = BranchesAdapter()
+ spinner.adapter = branchesAdapter
+ spinner.onItemSelectedListener = this
+ viewModel.branches.observe(viewLifecycleOwner) {
+ branchesAdapter.setItems(it)
+ spinner.isVisible = it.size > 1
+ }
+ viewModel.selectedBranchIndex.observe(viewLifecycleOwner) {
+ if (it != -1 && it != spinner.selectedItemPosition) {
+ spinner.setSelection(it)
+ }
+ }
+ }
+
private fun onChaptersChanged(list: List) {
- chaptersAdapter?.items = list
+ val adapter = chaptersAdapter ?: return
+ if (adapter.itemCount == 0) {
+ val position = list.indexOfFirst { it.hasFlag(ChapterListItem.FLAG_CURRENT) } - 1
+ if (position > 0) {
+ val offset = (resources.getDimensionPixelSize(R.dimen.chapter_list_item_height) * 0.6).roundToInt()
+ adapter.setItems(list, RecyclerViewScrollCallback(binding.recyclerViewChapters, position, offset))
+ } else {
+ adapter.items = list
+ }
+ } else {
+ adapter.items = list
+ }
}
private fun onLoadingStateChanged(isLoading: Boolean) {
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
index d8cb87ecf..ef427f091 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
@@ -1,66 +1,92 @@
package org.koitharu.kotatsu.details.ui
+import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import android.net.Uri
+import android.content.IntentFilter
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
+import android.view.View
import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.Spinner
import android.widget.Toast
import androidx.appcompat.view.ActionMode
-import androidx.appcompat.widget.Toolbar
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.Insets
import androidx.core.net.toFile
+import androidx.core.net.toUri
+import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
+import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.coroutines.launch
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
-import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.browser.BrowserActivity
-import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
-import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
-import org.koitharu.kotatsu.core.model.Manga
-import org.koitharu.kotatsu.core.model.MangaSource
+import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
+import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.os.ShortcutsRepository
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
+import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
import org.koitharu.kotatsu.download.ui.service.DownloadService
+import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
import org.koitharu.kotatsu.utils.ShareHelper
-import org.koitharu.kotatsu.utils.ext.buildAlertDialog
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
-class DetailsActivity : BaseActivity(),
- TabLayoutMediator.TabConfigurationStrategy {
+class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrategy,
+ AdapterView.OnItemSelectedListener {
private val viewModel by viewModel {
parametersOf(MangaIntent(intent))
}
+ private val downloadReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val downloadedManga = DownloadService.getDownloadedManga(intent) ?: return
+ viewModel.onDownloadComplete(downloadedManga)
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityDetailsBinding.inflate(layoutInflater))
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
- binding.pager.adapter = MangaDetailsAdapter(this)
- TabLayoutMediator(binding.tabs, binding.pager, this).attach()
+ supportActionBar?.run {
+ setDisplayHomeAsUpEnabled(true)
+ setDisplayShowTitleEnabled(false)
+ }
+ val pager = binding.pager
+ if (pager != null) {
+ pager.adapter = MangaDetailsAdapter(this)
+ TabLayoutMediator(checkNotNull(binding.tabs), pager, this).attach()
+ }
+ gcFragments()
+ binding.spinnerBranches?.let(::initSpinner)
viewModel.manga.observe(this, ::onMangaUpdated)
viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged)
viewModel.onMangaRemoved.observe(this, ::onMangaRemoved)
viewModel.onError.observe(this, ::onError)
+
+ registerReceiver(downloadReceiver, IntentFilter(DownloadService.ACTION_DOWNLOAD_COMPLETE))
+ }
+
+ override fun onDestroy() {
+ unregisterReceiver(downloadReceiver)
+ super.onDestroy()
}
private fun onMangaUpdated(manga: Manga) {
@@ -78,9 +104,8 @@ class DetailsActivity : BaseActivity(),
private fun onError(e: Throwable) {
when {
- e is CloudFlareProtectedException -> {
- CloudFlareDialog.newInstance(e.url)
- .show(supportFragmentManager, CloudFlareDialog.TAG)
+ ExceptionResolver.canResolve(e) -> {
+ resolveError(e)
}
viewModel.manga.value == null -> {
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
@@ -96,25 +121,17 @@ class DetailsActivity : BaseActivity(),
binding.snackbar.updatePadding(
bottom = insets.bottom
)
- with(binding.toolbar) {
- updatePadding(
- left = insets.left,
- right = insets.right
- )
- updateLayoutParams {
- topMargin = insets.top
- }
- }
- if (binding.tabs.parent !is Toolbar) {
- binding.tabs.updatePadding(
- left = insets.left,
- right = insets.right
- )
+ binding.toolbar.updateLayoutParams