From 41fb351fe00f054838f9f27e882e7047ac79d5c2 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 2 Apr 2022 09:09:01 +0300 Subject: [PATCH 01/14] Use master-detals pattern for settings --- .../kotatsu/base/ui/BasePreferenceFragment.kt | 18 +- .../kotatsu/core/prefs/AppSettings.kt | 2 - .../reader/ui/SimpleSettingsActivity.kt | 3 +- .../settings/AppearanceSettingsFragment.kt | 100 ++++++++++ .../settings/ContentSettingsFragment.kt | 96 +++++++++ .../kotatsu/settings/MainSettingsFragment.kt | 182 ------------------ .../kotatsu/settings/RootSettingsFragment.kt | 12 ++ .../kotatsu/settings/SettingsActivity.kt | 48 +++-- .../settings/SettingsHeadersFragment.kt | 49 +++++ .../settings/SourceSettingsFragment.kt | 2 +- .../settings/TrackerSettingsFragment.kt | 5 +- .../settings/about/AboutSettingsFragment.kt | 56 +++--- .../kotatsu/settings/about/LicenseFragment.kt | 41 ---- .../sources/SourcesSettingsFragment.kt | 6 +- .../settings/utils/SliderPreference.kt | 18 +- app/src/main/res/drawable/ic_appearance.xml | 12 ++ app/src/main/res/drawable/ic_copyleft.xml | 16 -- .../res/layout-w600dp/activity_settings.xml | 32 +-- .../main/res/layout/fragment_copyright.xml | 15 -- app/src/main/res/raw/copyright | 24 --- app/src/main/res/values-be/strings.xml | 8 +- app/src/main/res/values-de/strings.xml | 8 +- app/src/main/res/values-es/strings.xml | 10 +- app/src/main/res/values-fi/strings.xml | 8 +- app/src/main/res/values-fr/strings.xml | 8 +- app/src/main/res/values-it/strings.xml | 8 +- app/src/main/res/values-ja/strings.xml | 14 +- app/src/main/res/values-nb-rNO/strings.xml | 8 +- app/src/main/res/values-pt-rBR/strings.xml | 10 +- app/src/main/res/values-pt/strings.xml | 8 +- app/src/main/res/values-ru/strings.xml | 10 +- app/src/main/res/values-sw360dp/bools.xml | 6 + app/src/main/res/values-tr/strings.xml | 14 +- app/src/main/res/values/bools.xml | 2 +- app/src/main/res/values/constants.xml | 4 + app/src/main/res/values/strings.xml | 12 +- app/src/main/res/values/styles.xml | 2 +- app/src/main/res/xml/pref_about.xml | 86 +++------ app/src/main/res/xml/pref_appearance.xml | 53 +++++ app/src/main/res/xml/pref_backup.xml | 6 +- app/src/main/res/xml/pref_content.xml | 27 +++ app/src/main/res/xml/pref_history.xml | 25 +-- app/src/main/res/xml/pref_main.xml | 102 ---------- app/src/main/res/xml/pref_notifications.xml | 12 +- app/src/main/res/xml/pref_reader.xml | 18 +- app/src/main/res/xml/pref_root.xml | 37 ++++ app/src/main/res/xml/pref_source.xml | 5 +- app/src/main/res/xml/pref_suggestions.xml | 6 +- app/src/main/res/xml/pref_tracker.xml | 12 +- 49 files changed, 578 insertions(+), 688 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/RootSettingsFragment.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/SettingsHeadersFragment.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/about/LicenseFragment.kt create mode 100644 app/src/main/res/drawable/ic_appearance.xml delete mode 100644 app/src/main/res/drawable/ic_copyleft.xml delete mode 100644 app/src/main/res/layout/fragment_copyright.xml delete mode 100644 app/src/main/res/raw/copyright create mode 100644 app/src/main/res/values-sw360dp/bools.xml create mode 100644 app/src/main/res/xml/pref_appearance.xml create mode 100644 app/src/main/res/xml/pref_content.xml delete mode 100644 app/src/main/res/xml/pref_main.xml create mode 100644 app/src/main/res/xml/pref_root.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt index 9abebf0fb..2125d044c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt @@ -6,14 +6,18 @@ import androidx.annotation.CallSuper import androidx.annotation.StringRes import androidx.core.graphics.Insets import androidx.core.view.updatePadding +import androidx.fragment.app.Fragment import androidx.preference.PreferenceFragmentCompat import androidx.recyclerview.widget.RecyclerView import org.koin.android.ext.android.inject import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.settings.SettingsActivity +import org.koitharu.kotatsu.settings.SettingsHeadersFragment -abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : PreferenceFragmentCompat(), +abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : + PreferenceFragmentCompat(), WindowInsetsDelegate.WindowInsetsListener, RecyclerViewOwner { @@ -39,16 +43,20 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : Pre override fun onResume() { super.onResume() if (titleId != 0) { - activity?.setTitle(titleId) + setTitle(getString(titleId)) } } @CallSuper override fun onWindowInsetsChanged(insets: Insets) { listView.updatePadding( - left = insets.left, - right = insets.right, bottom = insets.bottom ) } -} + + @Suppress("UsePropertyAccessSyntax") + protected fun setTitle(title: CharSequence) { + (parentFragment as? SettingsHeadersFragment)?.setTitle(title) + ?: activity?.setTitle(title) + } +} \ 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 bbffd9b7e..ac199619b 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 @@ -253,11 +253,9 @@ class AppSettings(context: Context) { 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/reader/ui/SimpleSettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt index 049a9de5b..54a5201d2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt @@ -17,6 +17,7 @@ import org.koitharu.kotatsu.main.ui.AppBarOwner import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.settings.* +@Deprecated("") class SimpleSettingsActivity : BaseActivity(), AppBarOwner { override val appBar: AppBarLayout @@ -36,7 +37,7 @@ class SimpleSettingsActivity : BaseActivity(), Ap ACTION_SOURCE -> SourceSettingsFragment.newInstance( intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL ) - else -> MainSettingsFragment() + else -> SettingsHeadersFragment() } ) } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt new file mode 100644 index 000000000..aca880ea1 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt @@ -0,0 +1,100 @@ +package org.koitharu.kotatsu.settings + +import android.content.Intent +import android.content.SharedPreferences +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatDelegate +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.TwoStatePreference +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.core.prefs.ListMode +import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity +import org.koitharu.kotatsu.settings.utils.SliderPreference +import org.koitharu.kotatsu.utils.ext.names +import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat +import java.util.* + +class AppearanceSettingsFragment : + BasePreferenceFragment(R.string.appearance), + SharedPreferences.OnSharedPreferenceChangeListener { + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.pref_appearance) + findPreference(AppSettings.KEY_GRID_SIZE)?.run { + summary = "%d%%".format(value) + setOnPreferenceChangeListener { preference, newValue -> + preference.summary = "%d%%".format(newValue) + true + } + } + preferenceScreen?.findPreference(AppSettings.KEY_LIST_MODE)?.run { + entryValues = ListMode.values().names() + setDefaultValueCompat(ListMode.GRID.name) + } + findPreference(AppSettings.KEY_DYNAMIC_THEME)?.isVisible = AppSettings.isDynamicColorAvailable + findPreference(AppSettings.KEY_DATE_FORMAT)?.run { + entryValues = resources.getStringArray(R.array.date_formats) + val now = Date().time + entries = entryValues.map { value -> + val formattedDate = settings.getDateFormat(value.toString()).format(now) + if (value == "") { + "${context.getString(R.string.system_default)} ($formattedDate)" + } else { + formattedDate + } + }.toTypedArray() + setDefaultValueCompat("") + summary = "%s" + } + findPreference(AppSettings.KEY_PROTECT_APP) + ?.isChecked = !settings.appPassword.isNullOrEmpty() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + settings.subscribe(this) + } + + override fun onDestroyView() { + settings.unsubscribe(this) + super.onDestroyView() + } + + override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) { + when (key) { + AppSettings.KEY_THEME -> { + AppCompatDelegate.setDefaultNightMode(settings.theme) + } + AppSettings.KEY_DYNAMIC_THEME -> { + findPreference(key)?.setSummary(R.string.restart_required) + } + AppSettings.KEY_THEME_AMOLED -> { + findPreference(key)?.setSummary(R.string.restart_required) + } + AppSettings.KEY_APP_PASSWORD -> { + findPreference(AppSettings.KEY_PROTECT_APP) + ?.isChecked = !settings.appPassword.isNullOrEmpty() + } + } + } + + override fun onPreferenceTreeClick(preference: Preference): Boolean { + return when (preference.key) { + AppSettings.KEY_PROTECT_APP -> { + val pref = (preference as? TwoStatePreference ?: return false) + if (pref.isChecked) { + pref.isChecked = false + startActivity(Intent(preference.context, ProtectSetupActivity::class.java)) + } else { + settings.appPassword = null + } + true + } + else -> super.onPreferenceTreeClick(preference) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt new file mode 100644 index 000000000..5e4c7555b --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/ContentSettingsFragment.kt @@ -0,0 +1,96 @@ +package org.koitharu.kotatsu.settings + +import android.content.SharedPreferences +import android.os.Bundle +import android.view.View +import androidx.preference.Preference +import java.io.File +import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment +import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog +import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.local.data.LocalStorageManager +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.utils.ext.getStorageName +import org.koitharu.kotatsu.utils.ext.viewLifecycleScope + +class ContentSettingsFragment : + BasePreferenceFragment(R.string.content), + SharedPreferences.OnSharedPreferenceChangeListener, + StorageSelectDialog.OnStorageSelectListener { + + private val storageManager by inject() + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.pref_content) + + findPreference(AppSettings.KEY_SUGGESTIONS)?.setSummary( + if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled + ) + bindRemoteSourcesSummary() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + findPreference(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName() + settings.subscribe(this) + } + + override fun onDestroyView() { + settings.unsubscribe(this) + super.onDestroyView() + } + + override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) { + when (key) { + AppSettings.KEY_LOCAL_STORAGE -> { + findPreference(key)?.bindStorageName() + } + AppSettings.KEY_SUGGESTIONS -> { + findPreference(AppSettings.KEY_SUGGESTIONS)?.setSummary( + if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled + ) + } + AppSettings.KEY_SOURCES_HIDDEN -> { + bindRemoteSourcesSummary() + } + } + } + + override fun onPreferenceTreeClick(preference: Preference): Boolean { + return when (preference.key) { + AppSettings.KEY_LOCAL_STORAGE -> { + val ctx = context ?: return false + StorageSelectDialog.Builder(ctx, storageManager, this) + .setTitle(preference.title ?: "") + .setNegativeButton(android.R.string.cancel) + .create() + .show() + true + } + else -> super.onPreferenceTreeClick(preference) + } + } + + override fun onStorageSelected(file: File) { + settings.mangaStorageDir = file + } + + private fun Preference.bindStorageName() { + viewLifecycleScope.launch { + val storage = storageManager.getDefaultWriteableDir() + summary = storage?.getStorageName(context) ?: getString(R.string.not_available) + } + } + + private fun bindRemoteSourcesSummary() { + findPreference(AppSettings.KEY_REMOTE_SOURCES)?.run { + val total = MangaSource.values().size - 1 + summary = getString( + R.string.enabled_d_of_d, total - settings.hiddenSources.size, total + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt deleted file mode 100644 index b0a9518dd..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/settings/MainSettingsFragment.kt +++ /dev/null @@ -1,182 +0,0 @@ -package org.koitharu.kotatsu.settings - -import android.content.ComponentName -import android.content.Intent -import android.content.SharedPreferences -import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import androidx.appcompat.app.AppCompatDelegate -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceScreen -import androidx.preference.TwoStatePreference -import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.ui.BasePreferenceFragment -import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog -import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.core.prefs.ListMode -import org.koitharu.kotatsu.local.data.LocalStorageManager -import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity -import org.koitharu.kotatsu.settings.utils.SliderPreference -import org.koitharu.kotatsu.utils.ext.getStorageName -import org.koitharu.kotatsu.utils.ext.names -import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat -import org.koitharu.kotatsu.utils.ext.viewLifecycleScope -import java.io.File -import java.util.* - - -class MainSettingsFragment : BasePreferenceFragment(R.string.settings), - SharedPreferences.OnSharedPreferenceChangeListener, - StorageSelectDialog.OnStorageSelectListener { - - private val storageManager by inject() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.pref_main) - findPreference(AppSettings.KEY_GRID_SIZE)?.run { - summary = "%d%%".format(value) - setOnPreferenceChangeListener { preference, newValue -> - preference.summary = "%d%%".format(newValue) - true - } - } - preferenceScreen?.findPreference(AppSettings.KEY_LIST_MODE)?.run { - entryValues = ListMode.values().names() - setDefaultValueCompat(ListMode.GRID.name) - } - findPreference(AppSettings.KEY_DYNAMIC_THEME)?.isVisible = - AppSettings.isDynamicColorAvailable - findPreference(AppSettings.KEY_DATE_FORMAT)?.run { - entryValues = resources.getStringArray(R.array.date_formats) - val now = Date().time - entries = entryValues.map { value -> - val formattedDate = settings.getDateFormat(value.toString()).format(now) - if (value == "") { - "${context.getString(R.string.system_default)} ($formattedDate)" - } else { - formattedDate - } - }.toTypedArray() - setDefaultValueCompat("") - summary = "%s" - } - findPreference(AppSettings.KEY_SUGGESTIONS)?.setSummary( - if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled - ) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - findPreference(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName() - findPreference(AppSettings.KEY_PROTECT_APP)?.isChecked = - !settings.appPassword.isNullOrEmpty() - settings.subscribe(this) - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.opt_settings, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.action_leaks -> { - val intent = Intent() - intent.component = ComponentName(requireContext(), "leakcanary.internal.activity.LeakActivity") - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - startActivity(intent) - true - } - else -> super.onOptionsItemSelected(item) - } - } - - override fun onDestroyView() { - settings.unsubscribe(this) - super.onDestroyView() - } - - override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { - when (key) { - AppSettings.KEY_THEME -> { - AppCompatDelegate.setDefaultNightMode(settings.theme) - } - AppSettings.KEY_DYNAMIC_THEME -> { - findPreference(key)?.setSummary(R.string.restart_required) - } - AppSettings.KEY_THEME_AMOLED -> { - findPreference(key)?.setSummary(R.string.restart_required) - } - AppSettings.KEY_LOCAL_STORAGE -> { - findPreference(key)?.bindStorageName() - } - AppSettings.KEY_APP_PASSWORD -> { - findPreference(AppSettings.KEY_PROTECT_APP) - ?.isChecked = !settings.appPassword.isNullOrEmpty() - } - AppSettings.KEY_SUGGESTIONS -> { - findPreference(AppSettings.KEY_SUGGESTIONS)?.setSummary( - if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled - ) - } - } - } - - override fun onResume() { - super.onResume() - findPreference(AppSettings.KEY_REMOTE_SOURCES)?.run { - val total = MangaSource.values().size - 1 - summary = getString( - R.string.enabled_d_of_d, total - settings.hiddenSources.size, total - ) - } - } - - override fun onPreferenceTreeClick(preference: Preference): Boolean { - return when (preference.key) { - AppSettings.KEY_LOCAL_STORAGE -> { - val ctx = context ?: return false - StorageSelectDialog.Builder(ctx, storageManager, this) - .setTitle(preference.title ?: "") - .setNegativeButton(android.R.string.cancel) - .create() - .show() - true - } - AppSettings.KEY_PROTECT_APP -> { - val pref = (preference as? TwoStatePreference ?: return false) - if (pref.isChecked) { - pref.isChecked = false - startActivity(Intent(preference.context, ProtectSetupActivity::class.java)) - } else { - settings.appPassword = null - } - true - } - else -> super.onPreferenceTreeClick(preference) - } - } - - override fun onStorageSelected(file: File) { - settings.mangaStorageDir = file - } - - private fun Preference.bindStorageName() { - viewLifecycleScope.launch { - val storage = storageManager.getDefaultWriteableDir() - summary = storage?.getStorageName(context) ?: getString(R.string.not_available) - } - } -} diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/RootSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/RootSettingsFragment.kt new file mode 100644 index 000000000..ad02acf64 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/RootSettingsFragment.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.settings + +import android.os.Bundle +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BasePreferenceFragment + +class RootSettingsFragment : BasePreferenceFragment(R.string.settings) { + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.pref_root) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt index 04afdd246..03e1e0195 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt @@ -1,9 +1,13 @@ package org.koitharu.kotatsu.settings +import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.Menu +import android.view.MenuItem import androidx.core.graphics.Insets +import androidx.core.view.updatePadding import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction @@ -16,7 +20,6 @@ import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.databinding.ActivitySettingsBinding import org.koitharu.kotatsu.main.ui.AppBarOwner -import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.utils.ext.isScrolledToTop class SettingsActivity : @@ -35,7 +38,7 @@ class SettingsActivity : if (supportFragmentManager.findFragmentById(R.id.container) == null) { supportFragmentManager.commit { - replace(R.id.container, MainSettingsFragment()) + replace(R.id.container, SettingsHeadersFragment()) } } } @@ -55,6 +58,22 @@ class SettingsActivity : super.onStop() } + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.opt_settings, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { + R.id.action_leaks -> { + val intent = Intent() + intent.component = ComponentName(this, "leakcanary.internal.activity.LeakActivity") + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + startActivity(intent) + true + } + else -> super.onOptionsItemSelected(item) + } + override fun onBackStackChanged() { val fragment = supportFragmentManager.findFragmentById(R.id.container) as? RecyclerViewOwner ?: return val recyclerView = fragment.recyclerView @@ -70,30 +89,31 @@ class SettingsActivity : val fm = supportFragmentManager val fragment = fm.fragmentFactory.instantiate(classLoader, pref.fragment ?: return false) fragment.arguments = pref.extras - fragment.setTargetFragment(caller, 0) + // fragment.setTargetFragment(caller, 0) openFragment(fragment) return true } - fun openMangaSourceSettings(mangaSource: MangaSource) { - openFragment(SourceSettingsFragment.newInstance(mangaSource)) + override fun onWindowInsetsChanged(insets: Insets) { + binding.appbar.updatePadding( + left = insets.left, + right = insets.right, + ) + binding.container.updatePadding( + left = insets.left, + right = insets.right, + ) } - fun openNotificationSettingsLegacy() { - openFragment(NotificationSettingsLegacyFragment()) - } - - private fun openFragment(fragment: Fragment) { + fun openFragment(fragment: Fragment) { supportFragmentManager.commit { - setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) - replace(R.id.container, fragment) setReorderingAllowed(true) + replace(R.id.container, fragment) + setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) addToBackStack(null) } } - override fun onWindowInsetsChanged(insets: Insets) = Unit - companion object { fun newIntent(context: Context) = Intent(context, SettingsActivity::class.java) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsHeadersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsHeadersFragment.kt new file mode 100644 index 000000000..bec03f017 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsHeadersFragment.kt @@ -0,0 +1,49 @@ +package org.koitharu.kotatsu.settings + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentTransaction +import androidx.fragment.app.commit +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceHeaderFragmentCompat +import androidx.slidingpanelayout.widget.SlidingPaneLayout +import org.koitharu.kotatsu.R + +class SettingsHeadersFragment : PreferenceHeaderFragmentCompat(), SlidingPaneLayout.PanelSlideListener { + + private var currentTitle: CharSequence? = null + + override fun onCreatePreferenceHeader(): PreferenceFragmentCompat = RootSettingsFragment() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + slidingPaneLayout.addPanelSlideListener(this) + } + + override fun onPanelSlide(panel: View, slideOffset: Float) = Unit + + override fun onPanelOpened(panel: View) { + activity?.title = currentTitle ?: getString(R.string.settings) + } + + override fun onPanelClosed(panel: View) { + activity?.setTitle(R.string.settings) + } + + fun setTitle(title: CharSequence?) { + currentTitle = title + if (slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen) { + activity?.title = title + } + } + + fun openFragment(fragment: Fragment) { + childFragmentManager.commit { + setReorderingAllowed(true) + replace(androidx.preference.R.id.preferences_detail, fragment) + setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) + addToBackStack(null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt index fc670874a..384905df2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SourceSettingsFragment.kt @@ -25,7 +25,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) { override fun onResume() { super.onResume() - activity?.title = source.title + setTitle(source.title) } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/TrackerSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/TrackerSettingsFragment.kt index a7191a756..1e993f845 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/TrackerSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/TrackerSettingsFragment.kt @@ -31,7 +31,6 @@ class TrackerSettingsFragment : BasePreferenceFragment(R.string.check_for_new_ch append(getString(R.string.read_more)) } } - warningPreference } } @@ -43,10 +42,10 @@ class TrackerSettingsFragment : BasePreferenceFragment(R.string.check_for_new_ch .putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) .putExtra(Settings.EXTRA_CHANNEL_ID, TrackWorker.CHANNEL_ID) startActivity(intent) + true } else { - (activity as? SettingsActivity)?.openNotificationSettingsLegacy() + super.onPreferenceTreeClick(preference) } - true } else -> super.onPreferenceTreeClick(preference) } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/about/AboutSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/about/AboutSettingsFragment.kt index a88856b15..8e149f83b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/about/AboutSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/about/AboutSettingsFragment.kt @@ -1,13 +1,13 @@ package org.koitharu.kotatsu.settings.about +import android.content.Intent import android.os.Bundle -import android.view.View +import androidx.core.net.toUri import androidx.preference.Preference import kotlinx.coroutines.launch import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment -import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.settings.AppUpdateChecker import org.koitharu.kotatsu.utils.ext.viewLifecycleScope @@ -16,20 +16,16 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_about) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + val isUpdateSupported = AppUpdateChecker.isUpdateSupported(requireContext()) findPreference(AppSettings.KEY_APP_UPDATE_AUTO)?.run { - isVisible = AppUpdateChecker.isUpdateSupported(context) + isVisible = isUpdateSupported } findPreference(AppSettings.KEY_APP_VERSION)?.run { title = getString(R.string.app_version, BuildConfig.VERSION_NAME) - isEnabled = AppUpdateChecker.isUpdateSupported(context) + isEnabled = isUpdateSupported } } - override fun onPreferenceTreeClick(preference: Preference): Boolean { return when (preference.key) { AppSettings.KEY_APP_VERSION -> { @@ -37,39 +33,19 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) { true } AppSettings.KEY_APP_TRANSLATION -> { - startActivity(context?.let { BrowserActivity.newIntent(it, - "https://hosted.weblate.org/engage/kotatsu", - resources.getString(R.string.about_app_translation)) }) + openLink(getString(R.string.url_weblate), preference.title) true } AppSettings.KEY_FEEDBACK_4PDA -> { - startActivity(context?.let { BrowserActivity.newIntent(it, - "https://4pda.to/forum/index.php?showtopic=697669", - resources.getString(R.string.about_feedback_4pda)) }) + openLink(getString(R.string.url_forpda), preference.title) true } AppSettings.KEY_FEEDBACK_DISCORD -> { - startActivity(context?.let { BrowserActivity.newIntent(it, - "https://discord.gg/NNJ5RgVBC5", - "Discord") }) + openLink(getString(R.string.url_discord), preference.title) true } AppSettings.KEY_FEEDBACK_GITHUB -> { - startActivity(context?.let { BrowserActivity.newIntent(it, - "https://github.com/nv95/Kotatsu/issues", - "GitHub") }) - true - } - AppSettings.KEY_SUPPORT_DEVELOPER -> { - startActivity(context?.let { BrowserActivity.newIntent(it, - "https://yoomoney.ru/to/410012543938752", - resources.getString(R.string.about_support_developer)) }) - true - } - AppSettings.KEY_APP_GRATITUDES -> { - startActivity(context?.let { BrowserActivity.newIntent(it, - "https://github.com/nv95/Kotatsu/graphs/contributors", - resources.getString(R.string.about_gratitudes)) }) + openLink(getString(R.string.url_github_issues), preference.title) true } else -> super.onPreferenceTreeClick(preference) @@ -95,4 +71,16 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) { } } } -} + + private fun openLink(url: String, title: CharSequence?) { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = url.toUri() + startActivity( + if (title != null) { + Intent.createChooser(intent, title) + } else { + intent + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/about/LicenseFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/about/LicenseFragment.kt deleted file mode 100644 index 8d25e3ca9..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/settings/about/LicenseFragment.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.koitharu.kotatsu.settings.about - -import android.os.Bundle -import android.text.SpannableStringBuilder -import android.text.method.LinkMovementMethod -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.graphics.Insets -import androidx.core.text.HtmlCompat -import androidx.core.text.parseAsHtml -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.ui.BaseFragment -import org.koitharu.kotatsu.databinding.FragmentCopyrightBinding - -class LicenseFragment : BaseFragment() { - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.textView.apply { - text = - SpannableStringBuilder(resources.openRawResource(R.raw.copyright).bufferedReader() - .readText() - .parseAsHtml(HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST)) - movementMethod = LinkMovementMethod.getInstance() - } - } - - override fun onInflateView( - inflater: LayoutInflater, - container: ViewGroup? - ) = FragmentCopyrightBinding.inflate(inflater, container, false) - - override fun onResume() { - super.onResume() - activity?.setTitle(R.string.about_license) - } - - override fun onWindowInsetsChanged(insets: Insets) = Unit - -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt index 200d8c31f..60a5b6ff9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt @@ -15,6 +15,8 @@ import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding import org.koitharu.kotatsu.main.ui.AppBarOwner import org.koitharu.kotatsu.settings.SettingsActivity +import org.koitharu.kotatsu.settings.SettingsHeadersFragment +import org.koitharu.kotatsu.settings.SourceSettingsFragment import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem @@ -87,7 +89,9 @@ class SourcesSettingsFragment : BaseFragment(), } override fun onItemSettingsClick(item: SourceConfigItem.SourceItem) { - (activity as? SettingsActivity)?.openMangaSourceSettings(item.source) + val fragment = SourceSettingsFragment.newInstance(item.source) + (parentFragment as? SettingsHeadersFragment)?.openFragment(fragment) + ?: (activity as? SettingsActivity)?.openFragment(fragment) } override fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/utils/SliderPreference.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/SliderPreference.kt index 472467a1d..0933fe1d0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/utils/SliderPreference.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/SliderPreference.kt @@ -30,18 +30,22 @@ class SliderPreference @JvmOverloads constructor( set(value) = setValueInternal(value, notifyChanged = true) private val sliderListener = Slider.OnChangeListener { _, value, fromUser -> - if (fromUser) { - syncValueInternal(value.toInt()) - } + if (fromUser) { + syncValueInternal(value.toInt()) } + } init { - context.withStyledAttributes(attrs, + context.withStyledAttributes( + attrs, R.styleable.SliderPreference, defStyleAttr, - defStyleRes) { - valueFrom = getFloat(R.styleable.SliderPreference_android_valueFrom, - valueFrom.toFloat()).toInt() + defStyleRes + ) { + valueFrom = getFloat( + R.styleable.SliderPreference_android_valueFrom, + valueFrom.toFloat() + ).toInt() valueTo = getFloat(R.styleable.SliderPreference_android_valueTo, valueTo.toFloat()).toInt() stepSize = diff --git a/app/src/main/res/drawable/ic_appearance.xml b/app/src/main/res/drawable/ic_appearance.xml new file mode 100644 index 000000000..b5aa6519c --- /dev/null +++ b/app/src/main/res/drawable/ic_appearance.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_copyleft.xml b/app/src/main/res/drawable/ic_copyleft.xml deleted file mode 100644 index 47ac8bbca..000000000 --- a/app/src/main/res/drawable/ic_copyleft.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - diff --git a/app/src/main/res/layout-w600dp/activity_settings.xml b/app/src/main/res/layout-w600dp/activity_settings.xml index c381476a0..642380efc 100644 --- a/app/src/main/res/layout-w600dp/activity_settings.xml +++ b/app/src/main/res/layout-w600dp/activity_settings.xml @@ -1,5 +1,5 @@ - + app:layout_constraintTop_toTopOf="parent" + app:liftOnScroll="false"> - + - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_copyright.xml b/app/src/main/res/layout/fragment_copyright.xml deleted file mode 100644 index fe9c3e4e1..000000000 --- a/app/src/main/res/layout/fragment_copyright.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/raw/copyright b/app/src/main/res/raw/copyright deleted file mode 100644 index bf4212730..000000000 --- a/app/src/main/res/raw/copyright +++ /dev/null @@ -1,24 +0,0 @@ -

Kotatsu is a free and open source manga reader for Android.

-

Copyright (C) 2020 by Koitharu

-

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 http://www.gnu.org/licenses/.

-

Open Source Licenses

- \ No newline at end of file diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 0f0e6134e..0e8ab0bad 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -217,13 +217,7 @@ Гэтая глава адсутнічае на вашай прыладзе. Спампуйце ціпрачытайце яе онлайн. На дадзены момант няма актыўных спамповак У чарзе - Ліцэнзія - Аўтарскія правы і ліцэнзіі - Гэтыя людзі робяць Kotatsu лепш - Падзякі - Калі вам падабаецца гэтая праграма, вы можаце дапамагчы фінансава з дапамогай ЮMoney (был. Яндекс.Деньги) - Падтрымаць распрацоўшчыка - Тэма на 4PDA + Тэма на 4PDA Зваротная сувязь Дапамагчы з перакладам праграмы Пераклад diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 966ee5667..d25f7be80 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -217,13 +217,7 @@ Dieses Kapitel fehlt auf deinem Gerät. Lade ihn herunter oder lese ihn online. Zurzeit sind keine aktiven Datenübertragungen vorhanden In Warteschlange - Wenn diese Anwendung dir gefällt, kannst du über Yoomoney (Yandex.Money) finanziell helfen - Lizenz - Urheberrecht und Lizenzen - Diese Leute machen Kotatsu besser - Danksagung - Den Entwickler unterstützen - Thema auf 4PDA + Thema auf 4PDA Rückmeldung Übersetzung Diese Anwendung übersetzen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 1b64f874e..44bb53494 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -205,14 +205,8 @@ Encuentra qué leer en el menú lateral. Lo que leas se mostrará aquí Está un poco vacío aquí… - Agradecimientos - Si te gusta esta aplicación, puedes ayudar económicamente a través de Yoomoney (ex. Yandex.Money) - Apoyar al desarrollador - Buscar sólo en %s - Todas estas personas hicieron que Kotatsu fuera mejor - Licencia - Derechos de autor y licencias - Falta un capítulo + Buscar sólo en %s + Falta un capítulo Traducir esta aplicación Comentarios Traducción diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 022d894ce..69ef71847 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -4,13 +4,7 @@ Tyylilajit %s -valtuutusta ei tueta Valtuutus valmis - Lisenssi - Tekijänoikeudet ja lisenssit - Nämä ihmiset tekevät Kotatsusta paremman - Kiitokset - Jos pidät tästä sovelluksesta, voit auttaa taloudellisesti Yoomoneyn (ent. Yandex.Money) kautta - Tue kehittäjää - Aihe 4PDA: ssa + Aihe 4PDA: ssa Palaute Käännös Käännä tämä sovellus diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index a20bce18d..c76fb6027 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -217,13 +217,7 @@ Téléchargez ou lisez ce chapitre manquant en ligne. Aucun téléchargement actif En file d\'attente - Si vous aimez cette application, vous pouvez envoyer de l\'argent via Yoomoney (ex. Yandex.Money) - Soutenir le concepteur - Licence - Droits d\'auteur et licences - Ces personnes ont toutes rendu Kotatsu meilleur - Remerciements - Sujet sur 4PDA + Sujet sur 4PDA Remarques Traduction Traduire cette application diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4245f4355..a1586ed71 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -217,13 +217,7 @@ Questo capitolo manca sul tuo dispositivo. Scaricalo o leggilo in linea. Attualmente non ci sono scaricamenti attivi In coda - Licenza - Diritti d\'autore e licenze - Queste persone rendono Kotatsu migliore - Ringraziamenti - Se ti piace questa applicazione, puoi aiutare finanziariamente via Yoomoney (Yandex.Money) - Sostieni lo sviluppatore - Argomento su 4PDA + Argomento su 4PDA Commenti Traduzione Traduci questa applicazione diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index dbf16817d..4e3fdfcd5 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -163,10 +163,7 @@ 右から左(←)の読書を好む フィードバック 4PDAに関する話題 - 開発者をサポートします(Yoomoneyが開きます) - このアプリが気に入ったら、Yoomoney(Yandex.Moneyなど)から開発者をサポートできます - ライセンス - 承認済み + 承認済み %sへのログインはサポートされていません 完成 進行中 @@ -212,12 +209,10 @@ このアプリを翻訳 全てのデータが復元されました 復元 - 感謝の気持ち - 準備中… + 準備中… 開始時に維持 バックアップと復元 - 著作権とライセンス - リバース + リバース 選択した構成はこの漫画のために記憶されます クッキーを削除 GitHubでissueを作成する(GitHubのアカウントが必要です) @@ -235,8 +230,7 @@ スケールモード 名前を入力する必要があります 今日 - この人達のお陰でKotatsuがより良くなりました - Kotatsuを翻訳する(Weblateのサイトに移動します) + Kotatsuを翻訳する(Weblateのサイトに移動します) 次のページ サイレント サインイン diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 04a7d2dd5..74c07b980 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -223,13 +223,7 @@ Oversettelse Tilbakemelding Emne på 4PDA - Støtt utvikleren - Hvis du liker programmet kan du kronerulle det på Yoomoney (tidligere Yandex.Money) - Takk rettes til - Folk som gjorde Kotatsu enda bedre - Opphavsrett og lisenser - Lisens - Identitetsbekreftet + Identitetsbekreftet Innlogging på %s støttes ikke Du vil bli utlogget fra alle kilder Sjangere diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index afa68d7a0..25bc9f483 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -183,12 +183,7 @@ Tradução Comentar Tópico no 4PDA - Apoie o desenvolvedor - Se você gosta deste aplicativo, você pode enviar dinheiro através do Yoomoney (ex. Yandex.Money) - Gratidão - Todas essas pessoas tornaram o Kotatsu melhor - Licença - Autorizado + Autorizado O login em %s não é suportado Você será desconectado de todas as fontes Gêneros @@ -266,8 +261,7 @@ Os dados foram restaurados, mas há erros Criar problema no GitHub Arquivo não encontrado - Direitos autorais e licenças - Localizar capítulo + Localizar capítulo Não há capítulos neste mangá %1$s%% \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 24d687be3..0f0d99fc0 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -185,13 +185,7 @@ Traduzir esta aplicação Comentar Tópico no 4PDA - Apoiar o desenvolvedor - Se você gosta deste aplicativo, você pode enviar dinheiro através do Yoomoney (ex. Yandex.Money) - Agradecimentos - Todas essas pessoas tornaram o Kotatsu melhor - Direitos de autor e licenças - Licença - Baixe ou leia este capítulo perdido online. + Baixe ou leia este capítulo perdido online. O capítulo está em falta Autorizado O login em %s não é suportado diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4801b3498..1fc097e9e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -220,13 +220,7 @@ Перевод Тема на 4PDA Обратная связь - Поддержать разработчика - Если вам нравится это приложение, вы можете помочь финансово с помощью ЮMoney (бывш. Яндекс.Деньги) - Благодарности - Все эти люди сделали Kotatsu лучше - Авторские права и лицензии - Лицензия - Авторизация выполнена + Авторизация выполнена Вход в %s не поддерживается Вы выйдете из всех источников Жанры @@ -269,4 +263,6 @@ Разные языки Найти главу В этой манге нет глав + Оформление + Контент \ No newline at end of file diff --git a/app/src/main/res/values-sw360dp/bools.xml b/app/src/main/res/values-sw360dp/bools.xml new file mode 100644 index 000000000..927afe2f8 --- /dev/null +++ b/app/src/main/res/values-sw360dp/bools.xml @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c9154f6a5..eb03a51d6 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -131,8 +131,7 @@ Yedekten geri yükle Güncelle Oturum aç - Lisans - Bitti + Bitti Hakkında Bu içeriği görüntülemek için oturum açın Onayla @@ -185,9 +184,7 @@ Çeviri Yalnızca %s içinde ara 4PDA\'daki konu - Teşekkürler - Tüm bu insanlar Kotatsu\'yu daha iyi hale getirdi - Devam ediyor + Devam ediyor Tüm kaynaklardaki oturumunuz kapatılacak Kullanılan kaynaklar Kullanılabilir kaynaklar @@ -226,16 +223,13 @@ CAPTCHA gerekli Tüm çerezler kaldırıldı Seçilen yapılandırma bu manga için hatırlanacak - Bu uygulamayı beğendiyseniz Yoomoney (eski Yandex.Money) üzerinden para gönderebilirsiniz - Tüm güncelleme geçmişi kalıcı olarak silinsin mi\? + Tüm güncelleme geçmişi kalıcı olarak silinsin mi\? …ve %1$d daha fazlası Uygulamayı başlatmak için bir parola girin Tüm son arama sorguları kalıcı olarak kaldırılsın mı\? Geri bildirim Yedek kaydedildi - Geliştiriciyi destekleyin - Telif Hakkı ve Lisanslar - Türler + Türler Tarih biçimi Öntanımlı Bir ad girmelisiniz diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml index 967cf9dce..6cb53be9e 100644 --- a/app/src/main/res/values/bools.xml +++ b/app/src/main/res/values/bools.xml @@ -1,5 +1,5 @@ - + false true false diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml index 2e676d010..5132610e6 100644 --- a/app/src/main/res/values/constants.xml +++ b/app/src/main/res/values/constants.xml @@ -1,5 +1,9 @@ + https://github.com/nv95/Kotatsu/issues + https://discord.gg/NNJ5RgVBC5 + https://4pda.to/forum/index.php?showtopic=697669 + https://hosted.weblate.org/engage/kotatsu -1 1 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2dc561b1..aeace4628 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -222,13 +222,7 @@ Translation Feedback Topic on 4PDA - Support the developer - If you like this app, you can send money through Yoomoney (ex. Yandex.Money) - Gratitudes - These people all made Kotatsu better - Copyright and Licenses - License - Authorized + Authorized Logging in on %s is not supported You will be logged out from all sources Genres @@ -270,4 +264,8 @@ Find chapter No chapters in this manga %1$s%% + Appearance + Content + GitHub + Discord \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 212be7451..f0bdca57b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/res/xml/pref_about.xml b/app/src/main/res/xml/pref_about.xml index 7be3e5c8a..bde3d1904 100644 --- a/app/src/main/res/xml/pref_about.xml +++ b/app/src/main/res/xml/pref_about.xml @@ -1,84 +1,44 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> - + + android:key="app_version" + android:persistent="false" + android:summary="@string/check_for_updates" /> - - - - - - + android:defaultValue="true" + android:key="app_update_auto" + android:summary="@string/show_notification_app_update" + android:title="@string/application_update" /> - + + android:key="about_feedback_github" + android:persistent="false" + android:summary="@string/url_github_issues" + android:title="@string/github" /> + android:key="about_feedback_4pda" + android:summary="@string/url_forpda" + android:title="@string/about_feedback_4pda" /> - - - - + android:key="about_feedback_discord" + android:summary="@string/url_discord" + android:title="@string/discord" /> + android:key="about_app_translation" + android:summary="@string/about_app_translation_summary" + android:title="@string/about_app_translation" /> diff --git a/app/src/main/res/xml/pref_appearance.xml b/app/src/main/res/xml/pref_appearance.xml new file mode 100644 index 000000000..eac08e162 --- /dev/null +++ b/app/src/main/res/xml/pref_appearance.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/pref_backup.xml b/app/src/main/res/xml/pref_backup.xml index 7a61eb336..7fc94bcba 100644 --- a/app/src/main/res/xml/pref_backup.xml +++ b/app/src/main/res/xml/pref_backup.xml @@ -6,14 +6,12 @@ + android:title="@string/create_backup" /> + android:title="@string/restore_backup" /> + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/pref_history.xml b/app/src/main/res/xml/pref_history.xml index 50061d04a..6b7d82c50 100644 --- a/app/src/main/res/xml/pref_history.xml +++ b/app/src/main/res/xml/pref_history.xml @@ -1,51 +1,42 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> + android:title="@string/clear_search_history" /> + android:title="@string/clear_updates_feed" /> + android:title="@string/exclude_nsfw_from_history" /> - + + android:title="@string/clear_thumbs_cache" /> + android:title="@string/clear_pages_cache" /> + android:title="@string/clear_cookies" /> \ No newline at end of file diff --git a/app/src/main/res/xml/pref_main.xml b/app/src/main/res/xml/pref_main.xml deleted file mode 100644 index dd72fe7c1..000000000 --- a/app/src/main/res/xml/pref_main.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/pref_notifications.xml b/app/src/main/res/xml/pref_notifications.xml index c1a70a6fd..b3c349d72 100644 --- a/app/src/main/res/xml/pref_notifications.xml +++ b/app/src/main/res/xml/pref_notifications.xml @@ -1,23 +1,19 @@ + xmlns:android="http://schemas.android.com/apk/res/android"> + android:title="@string/notification_sound" /> + android:title="@string/vibration" /> + android:title="@string/light_indicator" /> \ No newline at end of file diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml index 8fa00ef0e..e152d7054 100644 --- a/app/src/main/res/xml/pref_reader.xml +++ b/app/src/main/res/xml/pref_reader.xml @@ -7,14 +7,13 @@ android:defaultValue="false" android:key="reader_prefer_rtl" android:summary="@string/prefer_rtl_reader_summary" - android:title="@string/prefer_rtl_reader" - app:iconSpaceReserved="false" /> + android:title="@string/prefer_rtl_reader" /> + app:allowDividerAbove="true" /> + android:title="@string/pages_animation" /> + android:title="@string/show_pages_numbers" /> \ No newline at end of file diff --git a/app/src/main/res/xml/pref_root.xml b/app/src/main/res/xml/pref_root.xml new file mode 100644 index 000000000..a7ed841c0 --- /dev/null +++ b/app/src/main/res/xml/pref_root.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/pref_source.xml b/app/src/main/res/xml/pref_source.xml index d4be72273..51a669888 100644 --- a/app/src/main/res/xml/pref_source.xml +++ b/app/src/main/res/xml/pref_source.xml @@ -5,10 +5,9 @@ + app:allowDividerAbove="true" /> \ No newline at end of file diff --git a/app/src/main/res/xml/pref_suggestions.xml b/app/src/main/res/xml/pref_suggestions.xml index aa6f20132..0206aa4c2 100644 --- a/app/src/main/res/xml/pref_suggestions.xml +++ b/app/src/main/res/xml/pref_suggestions.xml @@ -7,14 +7,12 @@ android:defaultValue="false" android:key="suggestions" android:summary="@string/suggestions_summary" - android:title="@string/suggestions_enable" - app:iconSpaceReserved="false" /> + android:title="@string/suggestions_enable" /> + android:title="@string/exclude_nsfw_from_suggestions" /> + android:title="@string/track_sources" /> + android:title="@string/notifications" /> + android:title="@string/notifications_settings" /> Date: Mon, 4 Apr 2022 10:02:20 +0300 Subject: [PATCH 02/14] Remove SimpleSettingsActivity --- app/src/main/AndroidManifest.xml | 9 --- .../kotatsu/reader/ui/ReaderActivity.kt | 3 +- .../reader/ui/SimpleSettingsActivity.kt | 81 ------------------- .../remotelist/ui/RemoteListFragment.kt | 7 +- .../settings/NetworkSettingsFragment.kt | 12 --- .../kotatsu/settings/SettingsActivity.kt | 39 ++++++++- .../suggestions/ui/SuggestionsFragment.kt | 4 +- .../res/layout/activity_settings_simple.xml | 35 -------- 8 files changed, 42 insertions(+), 148 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/NetworkSettingsFragment.kt delete mode 100644 app/src/main/res/layout/activity_settings_simple.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eb198b8ea..b4eb138dc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -58,15 +58,6 @@ - - - - - - { - startActivity(SimpleSettingsActivity.newReaderSettingsIntent(this)) + startActivity(SettingsActivity.newReaderSettingsIntent(this)) } R.id.action_chapters -> { ChaptersBottomSheet.show( diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt deleted file mode 100644 index 54a5201d2..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/SimpleSettingsActivity.kt +++ /dev/null @@ -1,81 +0,0 @@ -package org.koitharu.kotatsu.reader.ui - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.view.ViewGroup -import androidx.core.graphics.Insets -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding -import androidx.fragment.app.commit -import com.google.android.material.appbar.AppBarLayout -import org.koitharu.kotatsu.BuildConfig -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.ui.BaseActivity -import org.koitharu.kotatsu.databinding.ActivitySettingsSimpleBinding -import org.koitharu.kotatsu.main.ui.AppBarOwner -import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.settings.* - -@Deprecated("") -class SimpleSettingsActivity : BaseActivity(), AppBarOwner { - - override val appBar: AppBarLayout - get() = binding.appbar - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(ActivitySettingsSimpleBinding.inflate(layoutInflater)) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - supportFragmentManager.commit { - replace( - R.id.container, - when (intent?.action) { - Intent.ACTION_MANAGE_NETWORK_USAGE -> NetworkSettingsFragment() - ACTION_READER -> ReaderSettingsFragment() - ACTION_SUGGESTIONS -> SuggestionsSettingsFragment() - ACTION_SOURCE -> SourceSettingsFragment.newInstance( - intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL - ) - else -> SettingsHeadersFragment() - } - ) - } - } - - override fun onWindowInsetsChanged(insets: Insets) { - with(binding.toolbar) { - updatePadding( - left = insets.left, - right = insets.right - ) - updateLayoutParams { - topMargin = insets.top - } - } - } - - companion object { - - private const val ACTION_READER = - "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS" - private const val ACTION_SUGGESTIONS = - "${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS" - private const val ACTION_SOURCE = - "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS" - private const val EXTRA_SOURCE = "source" - - fun newReaderSettingsIntent(context: Context) = - Intent(context, SimpleSettingsActivity::class.java) - .setAction(ACTION_READER) - - fun newSuggestionsSettingsIntent(context: Context) = - Intent(context, SimpleSettingsActivity::class.java) - .setAction(ACTION_SUGGESTIONS) - - fun newSourceSettingsIntent(context: Context, source: MangaSource) = - Intent(context, SimpleSettingsActivity::class.java) - .setAction(ACTION_SOURCE) - .putExtra(EXTRA_SOURCE, source) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt index 2df3aa5db..8c9cff562 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt @@ -9,7 +9,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.filter.FilterBottomSheet import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity +import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.utils.ext.serializableArgument import org.koitharu.kotatsu.utils.ext.withArgs @@ -34,10 +34,7 @@ class RemoteListFragment : MangaListFragment() { return when (item.itemId) { R.id.action_source_settings -> { startActivity( - SimpleSettingsActivity.newSourceSettingsIntent( - context ?: return false, - source, - ) + SettingsActivity.newSourceSettingsIntent(context ?: return false, source) ) true } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/NetworkSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/NetworkSettingsFragment.kt deleted file mode 100644 index 8bfb2c3d6..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/settings/NetworkSettingsFragment.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.koitharu.kotatsu.settings - -import android.os.Bundle -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.ui.BasePreferenceFragment - -class NetworkSettingsFragment : BasePreferenceFragment(R.string.settings) { - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - //TODO https://developer.android.com/training/basics/network-ops/managing - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt index 03e1e0195..8b82b327c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt @@ -15,11 +15,13 @@ import androidx.fragment.app.commit import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.google.android.material.appbar.AppBarLayout +import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.databinding.ActivitySettingsBinding import org.koitharu.kotatsu.main.ui.AppBarOwner +import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.utils.ext.isScrolledToTop class SettingsActivity : @@ -37,9 +39,7 @@ class SettingsActivity : supportActionBar?.setDisplayHomeAsUpEnabled(true) if (supportFragmentManager.findFragmentById(R.id.container) == null) { - supportFragmentManager.commit { - replace(R.id.container, SettingsHeadersFragment()) - } + openDefaultFragment() } } @@ -114,8 +114,41 @@ class SettingsActivity : } } + private fun openDefaultFragment() { + val fragment = when (intent?.action) { + ACTION_READER -> ReaderSettingsFragment() + ACTION_SUGGESTIONS -> SuggestionsSettingsFragment() + ACTION_SOURCE -> SourceSettingsFragment.newInstance( + intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL + ) + else -> SettingsHeadersFragment() + } + supportFragmentManager.commit { + setReorderingAllowed(true) + replace(R.id.container, fragment) + } + } + companion object { + private const val ACTION_READER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS" + private const val ACTION_SUGGESTIONS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS" + private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS" + private const val EXTRA_SOURCE = "source" + fun newIntent(context: Context) = Intent(context, SettingsActivity::class.java) + + fun newReaderSettingsIntent(context: Context) = + Intent(context, SettingsActivity::class.java) + .setAction(ACTION_READER) + + fun newSuggestionsSettingsIntent(context: Context) = + Intent(context, SettingsActivity::class.java) + .setAction(ACTION_SUGGESTIONS) + + fun newSourceSettingsIntent(context: Context, source: MangaSource) = + Intent(context, SettingsActivity::class.java) + .setAction(ACTION_SOURCE) + .putExtra(EXTRA_SOURCE, source) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt index 507c2a142..0e7eef63a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt @@ -8,7 +8,7 @@ import com.google.android.material.snackbar.Snackbar import org.koin.androidx.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.list.ui.MangaListFragment -import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity +import org.koitharu.kotatsu.settings.SettingsActivity class SuggestionsFragment : MangaListFragment() { @@ -37,7 +37,7 @@ class SuggestionsFragment : MangaListFragment() { true } R.id.action_settings -> { - startActivity(SimpleSettingsActivity.newSuggestionsSettingsIntent(requireContext())) + startActivity(SettingsActivity.newSuggestionsSettingsIntent(requireContext())) true } else -> super.onOptionsItemSelected(item) diff --git a/app/src/main/res/layout/activity_settings_simple.xml b/app/src/main/res/layout/activity_settings_simple.xml deleted file mode 100644 index fa9c5d762..000000000 --- a/app/src/main/res/layout/activity_settings_simple.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file From 36a7a3ebbc5188f6c3527b505696243165e30d47 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Apr 2022 17:24:10 +0300 Subject: [PATCH 03/14] Fix DownloadService foreground notification #50 --- .../koitharu/kotatsu/download/ui/service/DownloadService.kt | 6 +++--- .../download/ui/service/ForegroundNotificationSwitcher.kt | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt index 8dc7a738f..65e6c8b58 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt @@ -10,6 +10,8 @@ import android.os.PowerManager import android.widget.Toast import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope +import java.util.concurrent.TimeUnit +import kotlin.collections.set import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.mapLatest @@ -33,8 +35,6 @@ import org.koitharu.kotatsu.utils.ext.connectivityManager import org.koitharu.kotatsu.utils.ext.throttle import org.koitharu.kotatsu.utils.ext.toArraySet import org.koitharu.kotatsu.utils.progress.ProgressJob -import java.util.concurrent.TimeUnit -import kotlin.collections.set class DownloadService : BaseService() { @@ -70,7 +70,6 @@ class DownloadService : BaseService() { return if (manga != null) { jobs[startId] = downloadManga(startId, manga, chapters) jobCount.value = jobs.size - Toast.makeText(this, R.string.manga_downloading_, Toast.LENGTH_SHORT).show() START_REDELIVER_INTENT } else { stopSelf(startId) @@ -184,6 +183,7 @@ class DownloadService : BaseService() { intent.putExtra(EXTRA_CHAPTERS_IDS, chaptersIds.toLongArray()) } ContextCompat.startForegroundService(context, intent) + Toast.makeText(context, R.string.manga_downloading_, Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/ForegroundNotificationSwitcher.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/ForegroundNotificationSwitcher.kt index 2202ab606..c7d0b0191 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/ForegroundNotificationSwitcher.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/ForegroundNotificationSwitcher.kt @@ -24,9 +24,10 @@ class ForegroundNotificationSwitcher( @Synchronized fun notify(startId: Int, notification: Notification) { if (notifications.isEmpty()) { - handler.postDelayed(StartForegroundRunnable(startId, notification), DEFAULT_DELAY) + StartForegroundRunnable(startId, notification) + } else { + notificationManager.notify(startId, notification) } - notificationManager.notify(startId, notification) notifications[startId] = notification } From cca6d5fa040a6c11a9b9be88db6199728ebc5c1a Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Apr 2022 17:50:08 +0300 Subject: [PATCH 04/14] Migrate to expedited jobs --- .../suggestions/ui/SuggestionsWorker.kt | 44 ++++++++++++++++++- .../kotatsu/tracker/work/TrackWorker.kt | 5 ++- app/src/main/res/values/strings.xml | 1 + 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt index 4420d4481..fe6b73ffb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt @@ -1,9 +1,17 @@ package org.koitharu.kotatsu.suggestions.ui +import android.app.NotificationChannel +import android.app.NotificationManager import android.content.Context +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat import androidx.work.* +import java.util.concurrent.TimeUnit +import kotlin.math.pow import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.history.domain.HistoryRepository @@ -11,8 +19,6 @@ import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository -import java.util.concurrent.TimeUnit -import kotlin.math.pow class SuggestionsWorker(appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params), KoinComponent { @@ -28,6 +34,37 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : Result.failure() } + override suspend fun getForegroundInfo(): ForegroundInfo { + val manager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val title = applicationContext.getString(R.string.suggestions_updating) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = NotificationChannel( + WORKER_CHANNEL_ID, + title, + NotificationManager.IMPORTANCE_LOW + ) + channel.setShowBadge(false) + channel.enableVibration(false) + channel.setSound(null, null) + channel.enableLights(false) + manager.createNotificationChannel(channel) + } + + val notification = NotificationCompat.Builder(applicationContext, WORKER_CHANNEL_ID) + .setContentTitle(title) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setDefaults(0) + .setColor(ContextCompat.getColor(applicationContext, R.color.blue_primary_dark)) + .setSilent(true) + .setProgress(0, 0, true) + .setSmallIcon(android.R.drawable.stat_notify_sync) + .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED) + .setOngoing(true) + .build() + + return ForegroundInfo(WORKER_NOTIFICATION_ID, notification) + } + private suspend fun doWorkImpl(): Int { if (!appSettings.isSuggestionsEnabled) { suggestionRepository.clear() @@ -74,6 +111,8 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : private const val TAG_ONESHOT = "suggestions_oneshot" private const val LIMIT = 140 private const val DATA_COUNT = "count" + private const val WORKER_CHANNEL_ID = "suggestion_worker" + private const val WORKER_NOTIFICATION_ID = 36 fun setup(context: Context) { val constraints = Constraints.Builder() @@ -96,6 +135,7 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : val request = OneTimeWorkRequestBuilder() .setConstraints(constraints) .addTag(TAG_ONESHOT) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build() WorkManager.getInstance(context) .enqueue(request) diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt index 04ad1695b..1144bcce0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt @@ -53,7 +53,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) : if (tracks.isEmpty()) { return Result.success() } - setForeground(createForegroundInfo()) + setForeground(getForegroundInfo()) var success = 0 val workData = Data.Builder() .putInt(DATA_TOTAL, tracks.size) @@ -201,7 +201,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) : } } - private fun createForegroundInfo(): ForegroundInfo { + override suspend fun getForegroundInfo(): ForegroundInfo { val title = applicationContext.getString(R.string.check_for_new_chapters) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( @@ -281,6 +281,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) : val request = OneTimeWorkRequestBuilder() .setConstraints(constraints) .addTag(TAG_ONESHOT) + .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build() WorkManager.getInstance(context) .enqueue(request) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aeace4628..6a880bd56 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -268,4 +268,5 @@ Content GitHub Discord + Suggestions updating \ No newline at end of file From e9b2b545a42cf0e789ca8a4c7c4982babb40157d Mon Sep 17 00:00:00 2001 From: grrrrr <1025506+grrrrr@users.noreply.github.com> Date: Wed, 6 Apr 2022 19:05:33 +0000 Subject: [PATCH 05/14] Update README.md - Add additional features (password protection and localization) to list. - Add details on how to contribute to translation --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index cd5c23394..a05eb4954 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ Download APK from Github Releases: * Tablet-optimized material design UI * Standard and Webtoon-optimized reader * Notifications about new chapters with updates feed +* Localized in multiple languages +* Password protect access to the app ### Screenshots @@ -35,6 +37,14 @@ Download APK from Github Releases: | ![](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/tenInchScreenshots/1.png) | ![](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/tenInchScreenshots/2.png) | |-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| +### Localization + + +Translation status + + +Kotatsu is localized in a number of different languages, if you would like to help improve these or add new languages, please head over to the Weblate project page + ### License [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) From 3ebd074e93ecb3737ecbf54fd31274deae2629bb Mon Sep 17 00:00:00 2001 From: grrrrr <1025506+grrrrr@users.noreply.github.com> Date: Wed, 6 Apr 2022 19:10:23 +0000 Subject: [PATCH 06/14] Update README.md change feature "localized in" to "available in --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a05eb4954..b079e87af 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Download APK from Github Releases: * Tablet-optimized material design UI * Standard and Webtoon-optimized reader * Notifications about new chapters with updates feed -* Localized in multiple languages +* Available in multiple languages * Password protect access to the app ### Screenshots From 36b4e16b7c417e251a19bbd9dc1ac7bbbbce01d7 Mon Sep 17 00:00:00 2001 From: grrrrr <1025506+grrrrr@users.noreply.github.com> Date: Wed, 6 Apr 2022 19:12:15 +0000 Subject: [PATCH 07/14] Update full_description.txt - Remote HTML code so displaying on sites such as f-droid does not create a lot of wasted space - add additional features taken from updated README.md --- metadata/en-US/full_description.txt | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt index bc037798f..8c4f8945c 100644 --- a/metadata/en-US/full_description.txt +++ b/metadata/en-US/full_description.txt @@ -1,12 +1,13 @@ Kotatsu is a free and open source manga reader for Android.
-Main Features: -
    -
  • Online manga catalogues
  • -
  • Search manga by name and genre
  • -
  • Reading history
  • -
  • Favourites organized by user-defined categories
  • -
  • Downloading manga and reading it offline. Third-party CBZ archives also supported
  • -
  • Tablet-optimized material design UI
  • -
  • Standard and Webtoon-optimized reader
  • -
  • Notifications about new chapters with updates feed
  • -
+**Main Features:** + +- Online manga catalogues +- Search manga by name and genre +- Reading history +- Favourites organized by user-defined categories +- Downloading manga and reading it offline. Third-party CBZ archives also supported +- Tablet-optimized material design UI +- Standard and Webtoon-optimized reader +- Notifications about new chapters with updates feed +- Available in multiple languages +- Password protect access to the app From 3d5c2123d47a9d45c6c5d46b26c85779be5a426a Mon Sep 17 00:00:00 2001 From: grrrrr <1025506+grrrrr@users.noreply.github.com> Date: Wed, 6 Apr 2022 19:13:43 +0000 Subject: [PATCH 08/14] Update full_description.txt - Remove HTML code so displaying on sites such as f-droid does not create a lot of wasted space --- metadata/ru/full_description.txt | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/metadata/ru/full_description.txt b/metadata/ru/full_description.txt index 327bdf3b7..87944e3da 100644 --- a/metadata/ru/full_description.txt +++ b/metadata/ru/full_description.txt @@ -1,12 +1,11 @@ Kotatsu - приложения для чтения манги с открытым исходным кодом.
-Основные возможности: -
    -
  • Онлайн каталоги с мангой
  • -
  • Поиск манги по имени и жанрам
  • -
  • История чтения
  • -
  • Избранное с пользовательскими категориями
  • -
  • Возможность сохранять мангу и читать её оффлайн. Поддержка сторонних комиксов в формате CBZ
  • -
  • Интерфейс также оптимизирован для планшетов
  • -
  • Поддержка манхвы (Webtoon)
  • -
  • Уведомления о новых главах и лента обновлений
  • -
+**Основные возможности:** + +- Онлайн каталоги с мангой +- Поиск манги по имени и жанрам +- История чтения +- Избранное с пользовательскими категориями +- Возможность сохранять мангу и читать её оффлайн. Поддержка сторонних комиксов в формате CBZ +- Интерфейс также оптимизирован для планшетов +- Поддержка манхвы (Webtoon) +- Уведомления о новых главах и лента обновлений From cc6b114e4dc105a789b4681e595bf652b2a20606 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 7 Apr 2022 17:04:11 +0300 Subject: [PATCH 09/14] Improve suggestions worker --- .../base/domain/MangaDataRepository.kt | 4 +- .../kotatsu/base/domain/MangaUtils.kt | 2 +- .../kotatsu/core/db/entity/MangaWithTags.kt | 6 +- .../core/db/entity/TrackLogWithManga.kt | 5 +- .../kotatsu/details/ui/DetailsViewModel.kt | 2 +- .../details/ui/adapter/BranchesAdapter.kt | 2 +- .../kotatsu/download/ui/DownloadItemAD.kt | 1 + .../ui/service/DownloadNotification.kt | 2 +- .../favourites/domain/FavouritesRepository.kt | 2 +- .../kotatsu/history/data/HistoryDao.kt | 13 ++-- .../history/domain/HistoryRepository.kt | 6 +- .../suggestion/model/SearchSuggestionItem.kt | 2 +- .../settings/onboard/OnboardViewModel.kt | 1 + .../domain/SuggestionRepository.kt | 2 +- .../suggestions/ui/SuggestionsWorker.kt | 64 +++++++++++++------ .../kotatsu/tracker/work/TrackWorker.kt | 5 +- .../koitharu/kotatsu/utils/ext/AndroidExt.kt | 8 ++- .../kotatsu/utils/ext/CollectionExt.kt | 54 ++-------------- .../koitharu/kotatsu/utils/ext/CommonExt.kt | 5 +- .../kotatsu/utils/ext/PrimitiveExt.kt | 24 ------- 20 files changed, 92 insertions(+), 118 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt index a658a474a..21f9c9989 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt @@ -9,7 +9,7 @@ import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag -import org.koitharu.kotatsu.utils.ext.mapToSet +import org.koitharu.kotatsu.parsers.util.mapToSet class MangaDataRepository(private val db: MangaDatabase) { @@ -37,7 +37,7 @@ class MangaDataRepository(private val db: MangaDatabase) { suspend fun resolveIntent(intent: MangaIntent): Manga? = when { intent.manga != null -> intent.manga - intent.mangaId != 0L -> db.mangaDao.find(intent.mangaId)?.toManga() + intent.mangaId != 0L -> findMangaById(intent.mangaId) else -> null // TODO resolve uri } diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt index 9ed4e714b..09a970eee 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt @@ -14,7 +14,7 @@ import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.util.await -import org.koitharu.kotatsu.utils.ext.medianOrNull +import org.koitharu.kotatsu.parsers.util.medianOrNull import java.io.InputStream import java.util.zip.ZipFile diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt index ad80b0beb..524fe906d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt @@ -3,7 +3,7 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.Embedded import androidx.room.Junction import androidx.room.Relation -import org.koitharu.kotatsu.utils.ext.mapToSet +import org.koitharu.kotatsu.parsers.util.mapToSet class MangaWithTags( @Embedded val manga: MangaEntity, @@ -15,7 +15,5 @@ class MangaWithTags( val tags: List ) { - fun toManga() = manga.toManga(tags.mapToSet { - it.toMangaTag() - }) + fun toManga() = manga.toManga(tags.mapToSet { it.toMangaTag() }) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt index 263e91938..037b22ad8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt @@ -3,9 +3,10 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.Embedded import androidx.room.Junction import androidx.room.Relation -import org.koitharu.kotatsu.core.model.TrackingLogItem -import org.koitharu.kotatsu.utils.ext.mapToSet import java.util.* +import org.koitharu.kotatsu.core.model.TrackingLogItem +import org.koitharu.kotatsu.parsers.util.mapToSet +import org.koitharu.kotatsu.utils.ext.mapToSet class TrackLogWithManga( @Embedded val trackLog: TrackLogEntity, diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 53546ea6a..7c5fe8574 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -24,11 +24,11 @@ import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.iterator -import org.koitharu.kotatsu.utils.ext.mapToSet import java.io.IOException class DetailsViewModel( diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt index 5cee213a7..2ca602df9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt @@ -6,7 +6,7 @@ import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.TextView import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.utils.ext.replaceWith +import org.koitharu.kotatsu.parsers.util.replaceWith class BranchesAdapter : BaseAdapter() { diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt index c16896205..1a86d0153 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.onEach import org.koitharu.kotatsu.R import org.koitharu.kotatsu.databinding.ItemDownloadBinding import org.koitharu.kotatsu.download.domain.DownloadState +import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.progress.ProgressJob diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt index aef7a1667..dd3fa3035 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt @@ -16,8 +16,8 @@ import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.download.ui.DownloadsActivity import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.utils.PendingIntentCompat -import org.koitharu.kotatsu.utils.ext.format import org.koitharu.kotatsu.utils.ext.getDisplayMessage import com.google.android.material.R as materialR diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index 868e2495c..ad9605cc3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -13,8 +13,8 @@ import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity import org.koitharu.kotatsu.favourites.data.FavouriteEntity import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.utils.ext.mapItems -import org.koitharu.kotatsu.utils.ext.mapToSet class FavouritesRepository(private val db: MangaDatabase) { diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt index 0b973aa64..a2527f59e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.flow.Flow import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.TagEntity - @Dao abstract class HistoryDao { @@ -23,8 +22,15 @@ abstract class HistoryDao { @Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM history)") abstract suspend fun findAllManga(): List - @Query("SELECT * FROM tags WHERE tag_id IN (SELECT tag_id FROM manga_tags WHERE manga_id IN (SELECT manga_id FROM history))") - abstract suspend fun findAllTags(): List + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + INNER JOIN history ON history.manga_id = manga_tags.manga_id + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_tags.manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findPopularTags(limit: Int): List @Query("SELECT * FROM history WHERE manga_id = :id") abstract suspend fun find(id: Long): HistoryEntity? @@ -60,5 +66,4 @@ abstract class HistoryDao { true } else false } - } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt index 4235765fd..89586e805 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt @@ -11,9 +11,9 @@ import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.history.data.HistoryEntity import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.ext.mapItems -import org.koitharu.kotatsu.utils.ext.mapToSet class HistoryRepository( private val db: MangaDatabase, @@ -91,7 +91,7 @@ class HistoryRepository( } } - suspend fun getAllTags(): Set { - return db.historyDao.findAllTags().mapToSet { x -> x.toMangaTag() } + suspend fun getPopularTags(limit: Int): List { + return db.historyDao.findPopularTags(limit).map { x -> x.toMangaTag() } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt index 1b89b32d3..341c89ac8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.utils.ext.areItemsEquals +import org.koitharu.kotatsu.parsers.util.areItemsEquals sealed interface SearchSuggestionItem { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt index 4e336c181..b9f97f37d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.settings.onboard.model.SourceLocale import org.koitharu.kotatsu.utils.ext.map diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt index a7bc69ace..17dd71840 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt @@ -6,9 +6,9 @@ import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.entity.MangaEntity import org.koitharu.kotatsu.core.db.entity.TagEntity import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.suggestions.data.SuggestionEntity import org.koitharu.kotatsu.utils.ext.mapItems -import org.koitharu.kotatsu.utils.ext.mapToSet class SuggestionRepository( private val db: MangaDatabase, diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt index fe6b73ffb..46bb42b5a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt @@ -4,21 +4,28 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.os.Build +import androidx.annotation.FloatRange import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.work.* -import java.util.concurrent.TimeUnit -import kotlin.math.pow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.history.domain.HistoryRepository -import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.suggestions.domain.MangaSuggestion import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository +import org.koitharu.kotatsu.utils.ext.asArrayList +import org.koitharu.kotatsu.utils.ext.trySetForeground +import java.util.concurrent.TimeUnit +import kotlin.math.pow class SuggestionsWorker(appContext: Context, params: WorkerParameters) : CoroutineWorker(appContext, params), KoinComponent { @@ -27,11 +34,10 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : private val historyRepository by inject() private val appSettings by inject() - override suspend fun doWork(): Result = try { + override suspend fun doWork(): Result { val count = doWorkImpl() - Result.success(workDataOf(DATA_COUNT to count)) - } catch (t: Throwable) { - Result.failure() + val outputData = workDataOf(DATA_COUNT to count) + return Result.success(outputData) } override suspend fun getForegroundInfo(): ForegroundInfo { @@ -70,21 +76,28 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : suggestionRepository.clear() return 0 } - val rawResults = ArrayList() - val allTags = historyRepository.getAllTags() + val allTags = historyRepository.getPopularTags(TAGS_LIMIT) if (allTags.isEmpty()) { return 0 } + if (TAG in tags) { // not expedited + trySetForeground() + } val tagsBySources = allTags.groupBy { x -> x.source } - for ((source, tags) in tagsBySources) { - val repo = MangaRepository(source) - tags.flatMapTo(rawResults) { tag -> - repo.getList( - offset = 0, - sortOrder = SortOrder.UPDATED, - tags = setOf(tag), - ) - } + val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM) + val rawResults = coroutineScope { + tagsBySources.flatMap { (source, tags) -> + val repo = MangaRepository(source) + tags.map { tag -> + async(dispatcher) { + repo.getList( + offset = 0, + sortOrder = SortOrder.UPDATED, + tags = setOf(tag), + ) + } + } + }.awaitAll().flatten().asArrayList() } if (appSettings.isSuggestionsExcludeNsfw) { rawResults.removeAll { it.isNsfw } @@ -95,21 +108,32 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : val suggestions = rawResults.distinctBy { manga -> manga.id }.map { manga -> - val jointTags = manga.tags intersect allTags MangaSuggestion( manga = manga, - relevance = (jointTags.size / manga.tags.size.toDouble()).pow(2.0).toFloat(), + relevance = computeRelevance(manga.tags, allTags) ) }.sortedBy { it.relevance }.take(LIMIT) suggestionRepository.replace(suggestions) return suggestions.size } + @FloatRange(from = 0.0, to = 1.0) + private fun computeRelevance(mangaTags: Set, allTags: List): Float { + val maxWeight = (allTags.size + allTags.size + 1 - mangaTags.size) * mangaTags.size / 2.0 + val weight = mangaTags.sumOf { tag -> + val index = allTags.indexOf(tag) + if (index < 0) 0 else allTags.size - index + } + return (weight / maxWeight).pow(2.0).toFloat() + } + companion object { private const val TAG = "suggestions" private const val TAG_ONESHOT = "suggestions_oneshot" private const val LIMIT = 140 + private const val TAGS_LIMIT = 20 + private const val MAX_PARALLELISM = 4 private const val DATA_COUNT = "count" private const val WORKER_CHANNEL_ID = "suggestion_worker" private const val WORKER_NOTIFICATION_ID = 36 diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt index 1144bcce0..a61b25c47 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt @@ -27,6 +27,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.PendingIntentCompat import org.koitharu.kotatsu.utils.ext.referer import org.koitharu.kotatsu.utils.ext.toBitmapOrNull +import org.koitharu.kotatsu.utils.ext.trySetForeground import org.koitharu.kotatsu.utils.progress.Progress import java.util.concurrent.TimeUnit @@ -53,7 +54,9 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) : if (tracks.isEmpty()) { return Result.success() } - setForeground(getForegroundInfo()) + if (TAG in tags) { // not expedited + trySetForeground() + } var success = 0 val workData = Data.Builder() .putInt(DATA_TOTAL, tracks.size) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt index 2fc663c9b..21140be00 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt @@ -5,6 +5,7 @@ import android.net.ConnectivityManager import android.net.Network import android.net.NetworkRequest import android.net.Uri +import androidx.work.CoroutineWorker import kotlin.coroutines.resume import kotlinx.coroutines.suspendCancellableCoroutine @@ -28,4 +29,9 @@ suspend fun ConnectivityManager.waitForNetwork(): Network { } } -fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this) \ No newline at end of file +fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this) + +suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatching { + val info = getForegroundInfo() + setForeground(info) +}.isSuccess \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt index 5ba3d0aa2..916d83491 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt @@ -3,46 +3,12 @@ package org.koitharu.kotatsu.utils.ext import androidx.collection.ArraySet import java.util.* -fun MutableCollection.replaceWith(subject: Iterable) { - clear() - addAll(subject) -} - -fun List.medianOrNull(): T? = when { - isEmpty() -> null - else -> get((size / 2).coerceIn(indices)) -} - -inline fun Collection.mapToSet(transform: (T) -> R): Set { - return mapTo(ArraySet(size), transform) -} - fun LongArray.toArraySet(): Set = createSet(size) { i -> this[i] } fun > Array.names() = Array(size) { i -> this[i].name } -fun Collection.isDistinct(): Boolean { - val set = HashSet(size) - for (item in this) { - if (!set.add(item)) { - return false - } - } - return set.size == size -} - -fun Collection.isDistinctBy(selector: (T) -> K): Boolean { - val set = HashSet(size) - for (item in this) { - if (!set.add(selector(item))) { - return false - } - } - return set.size == size -} - fun MutableList.move(sourceIndex: Int, targetIndex: Int) { if (sourceIndex <= targetIndex) { Collections.rotate(subList(sourceIndex, targetIndex + 1), -1) @@ -51,20 +17,6 @@ fun MutableList.move(sourceIndex: Int, targetIndex: Int) { } } -inline fun List.areItemsEquals(other: List, equals: (T, T) -> Boolean): Boolean { - if (size != other.size) { - return false - } - for (i in indices) { - val a = this[i] - val b = other[i] - if (!equals(a, b)) { - return false - } - } - return true -} - @Suppress("FunctionName") inline fun MutableSet(size: Int, init: (index: Int) -> T): MutableSet { val set = ArraySet(size) @@ -82,4 +34,10 @@ inline fun createList(size: Int, init: (index: Int) -> T): List = when (s 0 -> emptyList() 1 -> Collections.singletonList(init(0)) else -> MutableList(size, init) +} + +fun List.asArrayList(): ArrayList = if (this is ArrayList<*>) { + this as ArrayList +} else { + ArrayList(this) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt index 7e104e0f4..67ca43114 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt @@ -2,14 +2,15 @@ package org.koitharu.kotatsu.utils.ext import android.content.res.Resources import android.util.Log +import java.io.FileNotFoundException +import java.net.SocketTimeoutException import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException import org.koitharu.kotatsu.core.exceptions.WrongPasswordException import org.koitharu.kotatsu.parsers.exception.AuthRequiredException -import java.io.FileNotFoundException -import java.net.SocketTimeoutException +import org.koitharu.kotatsu.parsers.util.format fun Throwable.getDisplayMessage(resources: Resources) = when (this) { is AuthRequiredException -> resources.getString(R.string.auth_required) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/PrimitiveExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/PrimitiveExt.kt index 14e7cbb9d..a8359f5a1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/PrimitiveExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/PrimitiveExt.kt @@ -1,27 +1,3 @@ package org.koitharu.kotatsu.utils.ext -import java.text.DecimalFormat -import java.text.NumberFormat -import java.util.* - -fun Number.format(decimals: Int = 0, decPoint: Char = '.', thousandsSep: Char? = ' '): String { - val formatter = NumberFormat.getInstance(Locale.US) as DecimalFormat - val symbols = formatter.decimalFormatSymbols - if (thousandsSep != null) { - symbols.groupingSeparator = thousandsSep - formatter.isGroupingUsed = true - } else { - formatter.isGroupingUsed = false - } - symbols.decimalSeparator = decPoint - formatter.decimalFormatSymbols = symbols - formatter.minimumFractionDigits = decimals - formatter.maximumFractionDigits = decimals - return when (this) { - is Float, - is Double -> formatter.format(this.toDouble()) - else -> formatter.format(this.toLong()) - } -} - inline fun Int.ifZero(defaultValue: () -> Int): Int = if (this == 0) defaultValue() else this \ No newline at end of file From 8f9c0cbff17a5ed449514d7be582dd7d2bc30e59 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 7 Apr 2022 17:20:02 +0300 Subject: [PATCH 10/14] Fix tags suggestion --- .../main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt | 8 ++++---- .../kotatsu/search/domain/MangaSearchRepository.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt index c31307a24..8b3498e3c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt @@ -12,7 +12,7 @@ abstract class TagsDao { @Query( """SELECT tags.* FROM tags LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id - GROUP BY manga_tags.tag_id + GROUP BY tags.title ORDER BY COUNT(manga_id) DESC LIMIT :limit""" ) @@ -22,7 +22,7 @@ abstract class TagsDao { """SELECT tags.* FROM tags LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id WHERE tags.source = :source - GROUP BY manga_tags.tag_id + GROUP BY tags.title ORDER BY COUNT(manga_id) DESC LIMIT :limit""" ) @@ -32,7 +32,7 @@ abstract class TagsDao { """SELECT tags.* FROM tags LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id WHERE tags.source = :source AND title LIKE :query - GROUP BY manga_tags.tag_id + GROUP BY tags.title ORDER BY COUNT(manga_id) DESC LIMIT :limit""" ) @@ -42,7 +42,7 @@ abstract class TagsDao { """SELECT tags.* FROM tags LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id WHERE title LIKE :query - GROUP BY manga_tags.tag_id + GROUP BY tags.title ORDER BY COUNT(manga_id) DESC LIMIT :limit""" ) diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index a60ee96ac..c8ed656e7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -84,7 +84,7 @@ class MangaSearchRepository( return when { query.isNotEmpty() && source != null -> db.tagsDao.findTags(source.name, "%$query%", limit) query.isNotEmpty() -> db.tagsDao.findTags("%$query%", limit) - source != null -> db.tagsDao.findTags(source.name, limit) + source != null -> db.tagsDao.findPopularTags(source.name, limit) else -> db.tagsDao.findPopularTags(limit) }.map { it.toMangaTag() From 6ca9608a80efabf234b7126d685ce9e3adbb8eb8 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 7 Apr 2022 17:23:01 +0300 Subject: [PATCH 11/14] Remove CurlLoggingInterceptor --- .../core/network/CurlLoggingInterceptor.kt | 56 ------------------- .../kotatsu/core/network/NetworkModule.kt | 4 -- 2 files changed, 60 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/core/network/CurlLoggingInterceptor.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/CurlLoggingInterceptor.kt b/app/src/main/java/org/koitharu/kotatsu/core/network/CurlLoggingInterceptor.kt deleted file mode 100644 index e5bf2deca..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/core/network/CurlLoggingInterceptor.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.koitharu.kotatsu.core.network - -import android.util.Log -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response -import okio.Buffer -import java.io.IOException -import java.nio.charset.StandardCharsets - -private const val TAG = "CURL" - -class CurlLoggingInterceptor( - private val extraCurlOptions: String? = null, -) : Interceptor { - - @Throws(IOException::class) - override fun intercept(chain: Interceptor.Chain): Response { - val request: Request = chain.request() - var compressed = false - val curlCmd = StringBuilder("curl") - if (extraCurlOptions != null) { - curlCmd.append(" ").append(extraCurlOptions) - } - curlCmd.append(" -X ").append(request.method) - val headers = request.headers - var i = 0 - val count = headers.size - while (i < count) { - val name = headers.name(i) - val value = headers.value(i) - if ("Accept-Encoding".equals(name, ignoreCase = true) && "gzip".equals(value, - ignoreCase = true) - ) { - compressed = true - } - curlCmd.append(" -H " + "\"").append(name).append(": ").append(value).append("\"") - i++ - } - val requestBody = request.body - if (requestBody != null) { - val buffer = Buffer() - requestBody.writeTo(buffer) - val contentType = requestBody.contentType() - val charset = contentType?.charset(StandardCharsets.UTF_8) ?: StandardCharsets.UTF_8 - curlCmd.append(" --data $'") - .append(buffer.readString(charset).replace("\n", "\\n")) - .append("'") - } - curlCmd.append(if (compressed) " --compressed " else " ").append(request.url) - Log.d(TAG, "╭--- cURL (" + request.url + ")") - Log.d(TAG, curlCmd.toString()) - Log.d(TAG, "╰--- (copy and paste the above line to a terminal)") - return chain.proceed(request) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt index 4e7f0eb1c..48b009a33 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt @@ -5,7 +5,6 @@ import okhttp3.CookieJar import okhttp3.OkHttpClient import org.koin.dsl.bind import org.koin.dsl.module -import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl import org.koitharu.kotatsu.local.data.LocalStorageManager import org.koitharu.kotatsu.parsers.MangaLoaderContext @@ -22,9 +21,6 @@ val networkModule cache(get().createHttpCache()) addInterceptor(UserAgentInterceptor()) addInterceptor(CloudFlareInterceptor()) - if (BuildConfig.DEBUG) { - addNetworkInterceptor(CurlLoggingInterceptor()) - } }.build() } single { MangaLoaderContextImpl(get(), get(), get()) } From c92bdae842b5a68a1f3490830c61b511e1b51914 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 8 Apr 2022 14:56:45 +0300 Subject: [PATCH 12/14] Add tags blacklist option for suggestions --- .../kotatsu/core/prefs/AppSettings.kt | 13 ++ .../settings/SuggestionsSettingsFragment.kt | 8 ++ .../MultiAutoCompleteTextViewPreference.kt | 118 ++++++++++++++++++ .../utils/TagsAutoCompleteProvider.kt | 23 ++++ .../suggestions/ui/SuggestionsWorker.kt | 10 +- ...rence_dialog_multiautocompletetextview.xml | 42 +++++++ ...{pref_slider.xml => preference_slider.xml} | 0 app/src/main/res/values-ru/strings.xml | 3 + app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/styles.xml | 6 +- app/src/main/res/xml/pref_suggestions.xml | 6 + 12 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/utils/MultiAutoCompleteTextViewPreference.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/settings/utils/TagsAutoCompleteProvider.kt create mode 100644 app/src/main/res/layout/preference_dialog_multiautocompletetextview.xml rename app/src/main/res/layout/{pref_slider.xml => preference_slider.xml} (100%) 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 ac199619b..de191e304 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 @@ -165,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) @@ -247,6 +259,7 @@ 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" // About diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SuggestionsSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SuggestionsSettingsFragment.kt index 02467f1d6..46aced8a0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SuggestionsSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SuggestionsSettingsFragment.kt @@ -4,10 +4,13 @@ import android.content.SharedPreferences import android.os.Bundle import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.launch +import org.koin.android.ext.android.get import org.koin.android.ext.android.inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.prefs.AppSettings +import org.koitharu.kotatsu.settings.utils.MultiAutoCompleteTextViewPreference +import org.koitharu.kotatsu.settings.utils.TagsAutoCompleteProvider import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker @@ -23,6 +26,11 @@ class SuggestionsSettingsFragment : BasePreferenceFragment(R.string.suggestions) override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_suggestions) + + findPreference(AppSettings.KEY_SUGGESTIONS_EXCLUDE_TAGS)?.run { + autoCompleteProvider = TagsAutoCompleteProvider(get()) + summaryProvider = MultiAutoCompleteTextViewPreference.SimpleSummaryProvider(summary) + } } override fun onDestroy() { diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/utils/MultiAutoCompleteTextViewPreference.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/MultiAutoCompleteTextViewPreference.kt new file mode 100644 index 000000000..bccaaa942 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/MultiAutoCompleteTextViewPreference.kt @@ -0,0 +1,118 @@ +package org.koitharu.kotatsu.settings.utils + +import android.content.Context +import android.util.AttributeSet +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.Filter +import android.widget.MultiAutoCompleteTextView +import androidx.annotation.AttrRes +import androidx.annotation.MainThread +import androidx.annotation.StyleRes +import androidx.annotation.WorkerThread +import androidx.preference.EditTextPreference +import kotlinx.coroutines.runBlocking +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.parsers.util.replaceWith + +class MultiAutoCompleteTextViewPreference @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @AttrRes defStyleAttr: Int = R.attr.multiAutoCompleteTextViewPreferenceStyle, + @StyleRes defStyleRes: Int = R.style.Preference_MultiAutoCompleteTextView, +) : EditTextPreference(context, attrs, defStyleAttr, defStyleRes) { + + private val autoCompleteBindListener = AutoCompleteBindListener() + + var autoCompleteProvider: AutoCompleteProvider? = null + + init { + super.setOnBindEditTextListener(autoCompleteBindListener) + } + + override fun setOnBindEditTextListener(onBindEditTextListener: OnBindEditTextListener?) { + autoCompleteBindListener.delegate = onBindEditTextListener + } + + private inner class AutoCompleteBindListener : OnBindEditTextListener { + + var delegate: OnBindEditTextListener? = null + + override fun onBindEditText(editText: EditText) { + delegate?.onBindEditText(editText) + if (editText !is MultiAutoCompleteTextView) { + return + } + editText.setTokenizer(MultiAutoCompleteTextView.CommaTokenizer()) + editText.setAdapter( + autoCompleteProvider?.let { + CompletionAdapter(editText.context, it, ArrayList()) + } + ) + editText.threshold = 1 + } + } + + interface AutoCompleteProvider { + + suspend fun getSuggestions(query: String): List + } + + class SimpleSummaryProvider( + private val emptySummary: CharSequence?, + ) : SummaryProvider { + + override fun provideSummary(preference: MultiAutoCompleteTextViewPreference): CharSequence? { + return if (preference.text.isNullOrEmpty()) { + emptySummary + } else { + preference.text?.trimEnd(' ', ',') + } + } + } + + private class CompletionAdapter( + context: Context, + private val completionProvider: AutoCompleteProvider, + private val dataset: MutableList, + ) : ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, dataset) { + + override fun getFilter(): Filter { + return CompletionFilter(this, completionProvider) + } + + fun publishResults(results: List) { + dataset.replaceWith(results) + notifyDataSetChanged() + } + } + + private class CompletionFilter( + private val adapter: CompletionAdapter, + private val provider: AutoCompleteProvider, + ) : Filter() { + + @WorkerThread + override fun performFiltering(constraint: CharSequence?): FilterResults { + val query = constraint?.toString().orEmpty() + val suggestions = runBlocking { provider.getSuggestions(query) } + return CompletionResults(suggestions) + } + + @MainThread + override fun publishResults(constraint: CharSequence?, results: FilterResults) { + val completions = (results as CompletionResults).completions + adapter.publishResults(completions) + } + + private class CompletionResults( + val completions: List, + ) : FilterResults() { + + init { + values = completions + count = completions.size + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/utils/TagsAutoCompleteProvider.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/TagsAutoCompleteProvider.kt new file mode 100644 index 000000000..53998e48b --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/TagsAutoCompleteProvider.kt @@ -0,0 +1,23 @@ +package org.koitharu.kotatsu.settings.utils + +import org.koitharu.kotatsu.core.db.MangaDatabase + +class TagsAutoCompleteProvider( + private val db: MangaDatabase, +) : MultiAutoCompleteTextViewPreference.AutoCompleteProvider { + + override suspend fun getSuggestions(query: String): List { + if (query.isEmpty()) { + return emptyList() + } + val tags = db.tagsDao.findTags(query = "$query%", limit = 6) + val set = HashSet() + val result = ArrayList(tags.size) + for (tag in tags) { + if (set.add(tag.title)) { + result.add(tag.title) + } + } + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt index 46bb42b5a..7433e689b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt @@ -76,7 +76,10 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : suggestionRepository.clear() return 0 } - val allTags = historyRepository.getPopularTags(TAGS_LIMIT) + val blacklistTagRegex = appSettings.getSuggestionsTagsBlacklistRegex() + val allTags = historyRepository.getPopularTags(TAGS_LIMIT).filterNot { + blacklistTagRegex?.containsMatchIn(it.title) ?: false + } if (allTags.isEmpty()) { return 0 } @@ -102,6 +105,11 @@ class SuggestionsWorker(appContext: Context, params: WorkerParameters) : if (appSettings.isSuggestionsExcludeNsfw) { rawResults.removeAll { it.isNsfw } } + if (blacklistTagRegex != null) { + rawResults.removeAll { + it.tags.any { x -> blacklistTagRegex.containsMatchIn(x.title) } + } + } if (rawResults.isEmpty()) { return 0 } diff --git a/app/src/main/res/layout/preference_dialog_multiautocompletetextview.xml b/app/src/main/res/layout/preference_dialog_multiautocompletetextview.xml new file mode 100644 index 000000000..bcf067dc3 --- /dev/null +++ b/app/src/main/res/layout/preference_dialog_multiautocompletetextview.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/pref_slider.xml b/app/src/main/res/layout/preference_slider.xml similarity index 100% rename from app/src/main/res/layout/pref_slider.xml rename to app/src/main/res/layout/preference_slider.xml diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 1fc097e9e..6a432f53a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -265,4 +265,7 @@ В этой манге нет глав Оформление Контент + Обновление рекомендаций + Исключить жанры + Укажите жанры, которые Вы не хотите видеть в рекомендациях
\ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 42d3174b2..449fc3a29 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -2,6 +2,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a880bd56..9a4552a1a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -269,4 +269,6 @@ GitHub Discord Suggestions updating + Exclude genres + Specify genres that you do not want to see in the suggestions \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f0bdca57b..37da1ddcb 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -129,7 +129,11 @@ + +
diff --git a/app/src/main/res/xml/pref_suggestions.xml b/app/src/main/res/xml/pref_suggestions.xml index 0206aa4c2..224185a67 100644 --- a/app/src/main/res/xml/pref_suggestions.xml +++ b/app/src/main/res/xml/pref_suggestions.xml @@ -9,6 +9,12 @@ android:summary="@string/suggestions_summary" android:title="@string/suggestions_enable" /> + + Date: Fri, 8 Apr 2022 18:15:04 +0300 Subject: [PATCH 13/14] Add check to avoid TransactionTooLargeException --- .../core/model/parcelable/Parcelable.kt | 8 ++++-- .../core/model/parcelable/ParcelableManga.kt | 28 +++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt index 7b74c329c..67bb80044 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt @@ -4,7 +4,7 @@ import android.os.Parcel import androidx.core.os.ParcelCompat import org.koitharu.kotatsu.parsers.model.* -fun Manga.writeToParcel(out: Parcel, flags: Int) { +fun Manga.writeToParcel(out: Parcel, flags: Int, withChapters: Boolean) { out.writeLong(id) out.writeString(title) out.writeString(altTitle) @@ -18,7 +18,11 @@ fun Manga.writeToParcel(out: Parcel, flags: Int) { out.writeParcelable(ParcelableMangaTags(tags), flags) out.writeSerializable(state) out.writeString(author) - out.writeParcelable(chapters?.let(::ParcelableMangaChapters), flags) + if (withChapters) { + out.writeParcelable(chapters?.let(::ParcelableMangaChapters), flags) + } else { + out.writeString(null) + } out.writeSerializable(source) } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt index 44c0ae8f8..fd9b6cfa1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt @@ -2,24 +2,34 @@ package org.koitharu.kotatsu.core.model.parcelable import android.os.Parcel import android.os.Parcelable -import android.util.Log -import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.parsers.model.Manga +// Limits to avoid TransactionTooLargeException +private const val MAX_SAFE_SIZE = 1024 * 512 // Assume that 512 kb is safe parcel size +private const val MAX_SAFE_CHAPTERS_COUNT = 40 // this is 100% safe + class ParcelableManga( val manga: Manga, ) : Parcelable { constructor(parcel: Parcel) : this(parcel.readManga()) - init { - if (BuildConfig.DEBUG && manga.chapters != null) { - Log.w("ParcelableManga", "Passing manga with chapters as Parcelable is dangerous!") - } - } - override fun writeToParcel(parcel: Parcel, flags: Int) { - manga.writeToParcel(parcel, flags) + val chapters = manga.chapters + if (chapters == null || chapters.size <= MAX_SAFE_CHAPTERS_COUNT) { + // fast path + manga.writeToParcel(parcel, flags, withChapters = true) + return + } + val tempParcel = Parcel.obtain() + manga.writeToParcel(tempParcel, flags, withChapters = true) + val size = tempParcel.dataSize() + if (size < MAX_SAFE_SIZE) { + parcel.appendFrom(tempParcel, 0, size) + } else { + manga.writeToParcel(parcel, flags, withChapters = false) + } + tempParcel.recycle() } override fun describeContents(): Int { From 2cd9ea19fdcc9f0f70ada50fc60a5e01e203ba04 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 9 Apr 2022 08:28:01 +0300 Subject: [PATCH 14/14] Update dependencies --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6515eeac4..b90b1bfdc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,12 +65,12 @@ android { } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) - implementation('com.github.nv95:kotatsu-parsers:3ea7e92e64') { + implementation('com.github.nv95:kotatsu-parsers:0ee689cd2f') { exclude group: 'org.json', module: 'json' } - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1' implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.activity:activity-ktx:1.4.0' @@ -108,7 +108,7 @@ dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1' testImplementation 'io.insert-koin:koin-test-junit4:3.1.5' androidTestImplementation 'androidx.test:runner:1.4.0'