diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 410a50bd9..705055c2d 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1 @@ -github: inorichi ko_fi: inorichi diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f8fcf7d87..4c9ff5552 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,7 +3,7 @@ I acknowledge that: - I have updated: - - To the latest version of the app (stable is v0.10.10) + - To the latest version of the app (stable is v0.10.11) - All extensions - I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/ - If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e61ec24af..084d65863 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -10,7 +10,7 @@ labels: "bug" I acknowledge that: - I have updated: - - To the latest version of the app (stable is v0.10.10) + - To the latest version of the app (stable is v0.10.11) - All extensions - I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/ - If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9b0b28ffa..27f3d04cc 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -10,7 +10,7 @@ labels: "feature" I acknowledge that: - I have updated: - - To the latest version of the app (stable is v0.10.10) + - To the latest version of the app (stable is v0.10.11) - All extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions - I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue diff --git a/PREVIEW_RELEASE_NOTES.md b/PREVIEW_RELEASE_NOTES.md deleted file mode 100644 index dec33fa83..000000000 --- a/PREVIEW_RELEASE_NOTES.md +++ /dev/null @@ -1,31 +0,0 @@ -### r2903 -- The MyAnimeList tracker was rewritten. You will need to log out and log in again. - -### r1810 -- Background jobs were migrated to a new system. You may need to toggle the settings to ensure they -run properly. This includes app updates, library updates, and automatic backups. - -### r1340 -- A new screen for managing extensions was added. If you previously installed extensions from FDroid, -you will have to uninstall all of them first (tap on the extension then uninstall), otherwise you won't be able -to update them due to signature mismatch. You won't lose anything in this process as the extensions themselves -don't store anything. - -### r959 -- The download manager has been rewritten and it's possible some of your downloads -aren't recognized anymore. You may have to check your downloads folder and manually delete those. -- You can now download to any folder in your SD card. -- The download directory setting has been reset. - -### r857 -- **Important!** Delete after read has been updated. -This means the value has been reset set to disabled. -This can be changed in Settings > Downloads - -### r736 -- **Important!** Now chapters follow the order of the sources. **It's required that you update your entire library -before reading in order for them to be synced.** Old behavior can be restored for a manga in the overflow menu of the chapters tab. - -### r724 -- Kissmanga covers may not load anymore. The only workaround is to update the details of the manga -from the info tab, or clearing the database (the latter won't fix covers from library manga). diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ad6d185e3..577980b27 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,8 +29,8 @@ android { minSdkVersion(AndroidConfig.minSdk) targetSdkVersion(AndroidConfig.targetSdk) testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - versionCode = 57 - versionName = "0.10.10" + versionCode = 58 + versionName = "0.10.11" buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") @@ -265,7 +265,7 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion") // For detecting memory leaks; see https://square.github.io/leakcanary/ - // debugImplementation("com.squareup.leakcanary:leakcanary-android:2.6") + // debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7") } tasks { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8a5c0bc92..bae99b775 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -32,7 +32,7 @@ android:largeHeap="true" android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" - android:theme="@style/Theme.Tachiyomi.Light" + android:theme="@style/Theme.Base" android:networkSecurityConfig="@xml/network_security_config"> + android:theme="@style/Theme.Base" /> diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index fab8a2b31..a6227bf7d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -2,17 +2,17 @@ package eu.kanade.tachiyomi.data.cache import android.content.Context import android.text.format.Formatter -import com.github.salomonbrys.kotson.fromJson -import com.google.gson.Gson import com.jakewharton.disklrucache.DiskLruCache import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.saveTo +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import okhttp3.Response import okio.buffer import okio.sink -import rx.Observable import uy.kohesive.injekt.injectLazy import java.io.File import java.io.IOException @@ -42,8 +42,7 @@ class ChapterCache(private val context: Context) { const val PARAMETER_CACHE_SIZE = 100L * 1024 * 1024 } - /** Google Json class used for parsing JSON files. */ - private val gson: Gson by injectLazy() + private val json: Json by injectLazy() /** Cache class used for cache management. */ private val diskCache = DiskLruCache.open( @@ -56,7 +55,7 @@ class ChapterCache(private val context: Context) { /** * Returns directory of cache. */ - val cacheDir: File + private val cacheDir: File get() = diskCache.directory /** @@ -71,43 +70,19 @@ class ChapterCache(private val context: Context) { val readableSize: String get() = Formatter.formatFileSize(context, realSize) - /** - * Remove file from cache. - * - * @param file name of file "md5.0". - * @return status of deletion for the file. - */ - fun removeFileFromCache(file: String): Boolean { - // Make sure we don't delete the journal file (keeps track of cache). - if (file == "journal" || file.startsWith("journal.")) { - return false - } - - return try { - // Remove the extension from the file to get the key of the cache - val key = file.substringBeforeLast(".") - // Remove file from cache. - diskCache.remove(key) - } catch (e: Exception) { - false - } - } - /** * Get page list from cache. * * @param chapter the chapter. - * @return an observable of the list of pages. + * @return the list of pages. */ - fun getPageListFromCache(chapter: Chapter): Observable> { - return Observable.fromCallable { - // Get the key for the chapter. - val key = DiskUtil.hashKeyForDisk(getKey(chapter)) + fun getPageListFromCache(chapter: Chapter): List { + // Get the key for the chapter. + val key = DiskUtil.hashKeyForDisk(getKey(chapter)) - // Convert JSON string to list of objects. Throws an exception if snapshot is null - diskCache.get(key).use { - gson.fromJson>(it.getString(0)) - } + // Convert JSON string to list of objects. Throws an exception if snapshot is null + return diskCache.get(key).use { + json.decodeFromString(it.getString(0)) } } @@ -119,7 +94,7 @@ class ChapterCache(private val context: Context) { */ fun putPageListToCache(chapter: Chapter, pages: List) { // Convert list of pages to json string. - val cachedValue = gson.toJson(pages) + val cachedValue = json.encodeToString(pages) // Initialize the editor (edits the values for an entry). var editor: DiskLruCache.Editor? = null @@ -199,6 +174,38 @@ class ChapterCache(private val context: Context) { } } + fun clear(): Int { + var deletedFiles = 0 + cacheDir.listFiles()?.forEach { + if (removeFileFromCache(it.name)) { + deletedFiles++ + } + } + return deletedFiles + } + + /** + * Remove file from cache. + * + * @param file name of file "md5.0". + * @return status of deletion for the file. + */ + private fun removeFileFromCache(file: String): Boolean { + // Make sure we don't delete the journal file (keeps track of cache). + if (file == "journal" || file.startsWith("journal.")) { + return false + } + + return try { + // Remove the extension from the file to get the key of the cache + val key = file.substringBeforeLast(".") + // Remove file from cache. + diskCache.remove(key) + } catch (e: Exception) { + false + } + } + private fun getKey(chapter: Chapter): String { return "${chapter.manga_id}${chapter.url}" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index 362b20703..5ca8189c4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -163,7 +163,7 @@ internal object ExtensionLoader { else -> throw Exception("Unknown source class type! ${obj.javaClass}") } } catch (e: Throwable) { - Timber.w(e, "Extension load error: $extName ($it)") + Timber.e(e, "Extension load error: $extName ($it)") return LoadResult.Error(e) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt index afd4d1028..11ddee568 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt @@ -1,65 +1,39 @@ package eu.kanade.tachiyomi.ui.base.activity -import android.content.res.Configuration -import android.os.Build +import android.content.res.Configuration.UI_MODE_NIGHT_MASK +import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferenceValues.DarkThemeVariant +import eu.kanade.tachiyomi.data.preference.PreferenceValues.LightThemeVariant +import eu.kanade.tachiyomi.data.preference.PreferenceValues.ThemeMode import eu.kanade.tachiyomi.data.preference.PreferencesHelper import uy.kohesive.injekt.injectLazy -import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values abstract class BaseThemedActivity : AppCompatActivity() { val preferences: PreferencesHelper by injectLazy() - private val isDarkMode: Boolean by lazy { - val themeMode = preferences.themeMode().get() - (themeMode == Values.ThemeMode.dark) || - ( - themeMode == Values.ThemeMode.system && - (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES) - ) - } - - private val lightTheme: Int by lazy { - when (preferences.themeLight().get()) { - Values.LightThemeVariant.blue -> R.style.Theme_Tachiyomi_LightBlue - else -> { - when { - // Light status + navigation bar - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> { - R.style.Theme_Tachiyomi_Light_Api27 - } - // Light status bar + fallback gray navigation bar - Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> { - R.style.Theme_Tachiyomi_Light_Api23 - } - // Fallback gray status + navigation bar - else -> { - R.style.Theme_Tachiyomi_Light - } - } - } - } - } - - private val darkTheme: Int by lazy { - when (preferences.themeDark().get()) { - Values.DarkThemeVariant.blue -> R.style.Theme_Tachiyomi_DarkBlue - Values.DarkThemeVariant.amoled -> R.style.Theme_Tachiyomi_Amoled - else -> R.style.Theme_Tachiyomi_Dark - } - } - override fun onCreate(savedInstanceState: Bundle?) { - setTheme( - when { - isDarkMode -> darkTheme - else -> lightTheme + val isDarkMode = when (preferences.themeMode().get()) { + ThemeMode.light -> false + ThemeMode.dark -> true + ThemeMode.system -> resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES + } + val themeId = if (isDarkMode) { + when (preferences.themeDark().get()) { + DarkThemeVariant.default -> R.style.Theme_Tachiyomi_Dark + DarkThemeVariant.blue -> R.style.Theme_Tachiyomi_Dark_Blue + DarkThemeVariant.amoled -> R.style.Theme_Tachiyomi_Dark_Amoled } - ) - + } else { + when (preferences.themeLight().get()) { + LightThemeVariant.default -> R.style.Theme_Tachiyomi_Light + LightThemeVariant.blue -> R.style.Theme_Tachiyomi_Light_Blue + } + } + setTheme(themeId) super.onCreate(savedInstanceState) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt index c868af234..44e55b0fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt @@ -19,7 +19,8 @@ import timber.log.Timber abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateController(bundle) { - lateinit var binding: VB + protected lateinit var binding: VB + private set lateinit var viewScope: CoroutineScope @@ -51,11 +52,12 @@ abstract class BaseController(bundle: Bundle? = null) : ) } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { - return inflateView(inflater, container) - } + abstract fun createBinding(inflater: LayoutInflater): VB - abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { + binding = createBinding(inflater) + return binding.root + } open fun onViewCreated(view: View) {} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt index 98ac3e765..3107fe5a3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.browse import android.os.Bundle import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import com.bluelinelabs.conductor.Controller import com.bluelinelabs.conductor.ControllerChangeHandler @@ -50,10 +49,7 @@ class BrowseController : return resources!!.getString(R.string.browse) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = PagerControllerBinding.inflate(inflater) - return binding.root - } + override fun createBinding(inflater: LayoutInflater) = PagerControllerBinding.inflate(inflater) override fun onViewCreated(view: View) { super.onViewCreated(view) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt index 8adc75cdc..5b3f43bcf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionController.kt @@ -5,7 +5,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.LinearLayoutManager import com.bluelinelabs.conductor.ControllerChangeHandler @@ -57,18 +56,16 @@ open class ExtensionController : return ExtensionPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = ExtensionControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater) = ExtensionControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + binding.recycler.applyInsetter { type(navigationBars = true) { padding() } } - return binding.root - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) binding.swipeRefresh.isRefreshing = true binding.swipeRefresh.refreshes() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt index 52882285b..69762a591 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsController.kt @@ -12,7 +12,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.appcompat.view.ContextThemeWrapper import androidx.core.os.bundleOf import androidx.preference.Preference @@ -65,15 +64,9 @@ class ExtensionDetailsController(bundle: Bundle? = null) : setHasOptionsMenu(true) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun createBinding(inflater: LayoutInflater): ExtensionDetailControllerBinding { val themedInflater = inflater.cloneInContext(getPreferenceThemeContext()) - binding = ExtensionDetailControllerBinding.inflate(themedInflater) - binding.extensionPrefsRecycler.applyInsetter { - type(navigationBars = true) { - padding() - } - } - return binding.root + return ExtensionDetailControllerBinding.inflate(themedInflater) } override fun createPresenter(): ExtensionDetailsPresenter { @@ -88,6 +81,12 @@ class ExtensionDetailsController(bundle: Bundle? = null) : override fun onViewCreated(view: View) { super.onViewCreated(view) + binding.extensionPrefsRecycler.applyInsetter { + type(navigationBars = true) { + padding() + } + } + val extension = presenter.extension ?: return val context = view.context diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt index b98f21161..bb85a9721 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt @@ -6,7 +6,6 @@ import android.os.Bundle import android.util.TypedValue import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.appcompat.view.ContextThemeWrapper import androidx.core.os.bundleOf import androidx.preference.DialogPreference @@ -45,10 +44,9 @@ class SourcePreferencesController(bundle: Bundle? = null) : bundleOf(SOURCE_ID to sourceId) ) - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + override fun createBinding(inflater: LayoutInflater): SourcePreferencesControllerBinding { val themedInflater = inflater.cloneInContext(getPreferenceThemeContext()) - binding = SourcePreferencesControllerBinding.inflate(themedInflater) - return binding.root + return SourcePreferencesControllerBinding.inflate(themedInflater) } override fun createPresenter(): SourcePreferencesPresenter { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaController.kt index d4cfd4811..5fe586e11 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaController.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga import android.os.Bundle import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.recyclerview.widget.LinearLayoutManager import dev.chrisbanes.insetter.applyInsetter @@ -45,18 +44,16 @@ class MigrationMangaController : return MigrationMangaPresenter(sourceId) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = MigrationMangaControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater) = MigrationMangaControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + binding.recycler.applyInsetter { type(navigationBars = true) { padding() } } - return binding.root - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) adapter = MigrationMangaAdapter(this) binding.recycler.layoutManager = LinearLayoutManager(view.context) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt index 204f6cbec..c1a254e09 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt @@ -104,23 +104,22 @@ class SearchPresenter( val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking() val maxChapterRead = prevMangaChapters .filter { it.read } - .maxOfOrNull { it.chapter_number } - if (maxChapterRead != null) { - val dbChapters = db.getChapters(manga).executeAsBlocking() - for (chapter in dbChapters) { - if (chapter.isRecognizedNumber) { - val prevChapter = prevMangaChapters - .find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number } - if (prevChapter != null) { - chapter.date_fetch = prevChapter.date_fetch - chapter.bookmark = prevChapter.bookmark - } else if (chapter.chapter_number <= maxChapterRead) { - chapter.read = true - } + .maxOfOrNull { it.chapter_number } ?: 0f + val dbChapters = db.getChapters(manga).executeAsBlocking() + for (chapter in dbChapters) { + if (chapter.isRecognizedNumber) { + val prevChapter = prevMangaChapters + .find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number } + if (prevChapter != null) { + chapter.date_fetch = prevChapter.date_fetch + chapter.bookmark = prevChapter.bookmark + } + if (chapter.chapter_number <= maxChapterRead) { + chapter.read = true } } - db.insertChapters(dbChapters).executeAsBlocking() } + db.insertChapters(dbChapters).executeAsBlocking() } // Update categories diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt index 557c7db71..d70a370fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt @@ -5,7 +5,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import dev.chrisbanes.insetter.applyInsetter import eu.davidea.flexibleadapter.FlexibleAdapter @@ -30,18 +29,16 @@ class MigrationSourcesController : return MigrationSourcesPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = MigrationSourcesControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater) = MigrationSourcesControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + binding.recycler.applyInsetter { type(navigationBars = true) { padding() } } - return binding.root - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) adapter = SourceAdapter(this) binding.recycler.layoutManager = LinearLayoutManager(view.context) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt index f0a2dc17d..58043ef7c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt @@ -8,7 +8,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.list.listItems @@ -67,25 +66,16 @@ class SourceController : return SourcePresenter() } - /** - * Initiate the view with [R.layout.source_main_controller]. - * - * @param inflater used to load the layout xml. - * @param container containing parent views. - * @return inflated view. - */ - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = SourceMainControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater) = SourceMainControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + binding.recycler.applyInsetter { type(navigationBars = true) { padding() } } - return binding.root - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) adapter = SourceAdapter(this) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/AnimePager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/AnimePager.kt new file mode 100644 index 000000000..96f49c0fa --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/AnimePager.kt @@ -0,0 +1,30 @@ +package eu.kanade.tachiyomi.ui.browse.source.browse + +import com.jakewharton.rxrelay.PublishRelay +import eu.kanade.tachiyomi.source.model.AnimesPage +import eu.kanade.tachiyomi.source.model.SAnime +import rx.Observable + +/** + * A general pager for source requests (latest updates, popular, search) + */ +abstract class AnimePager(var currentPage: Int = 1) { + + var hasNextPage = true + private set + + protected val results: PublishRelay>> = PublishRelay.create() + + fun results(): Observable>> { + return results.asObservable() + } + + abstract fun requestNext(): Observable + + fun onPageReceived(animesPage: AnimesPage) { + val page = currentPage + currentPage++ + hasNextPage = animesPage.hasNextPage && animesPage.animes.isNotEmpty() + results.call(Pair(page, animesPage.animes)) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/AnimeSourcePager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/AnimeSourcePager.kt new file mode 100644 index 000000000..9848a938c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/AnimeSourcePager.kt @@ -0,0 +1,32 @@ +package eu.kanade.tachiyomi.ui.browse.source.browse + +import eu.kanade.tachiyomi.source.AnimeCatalogueSource +import eu.kanade.tachiyomi.source.model.AnimesPage +import eu.kanade.tachiyomi.source.model.FilterList +import rx.Observable +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers + +open class AnimeSourcePager(val source: AnimeCatalogueSource, val query: String, val filters: FilterList) : AnimePager() { + + override fun requestNext(): Observable { + val page = currentPage + + val observable = if (query.isBlank() && filters.isEmpty()) { + source.fetchPopularAnime(page) + } else { + source.fetchSearchAnime(page, query, filters) + } + + return observable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { + if (it.animes.isNotEmpty()) { + onPageReceived(it) + } else { + throw NoResultsException() + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseAnimeSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseAnimeSourceController.kt new file mode 100644 index 000000000..39501ff2a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseAnimeSourceController.kt @@ -0,0 +1,620 @@ +package eu.kanade.tachiyomi.ui.browse.source.browse + +import android.content.res.Configuration +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.core.view.updatePadding +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.list.listItems +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton +import com.google.android.material.snackbar.Snackbar +import com.tfcporciuncula.flow.Preference +import dev.chrisbanes.insetter.applyInsetter +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.Anime +import eu.kanade.tachiyomi.data.database.models.Category +import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.asImmediateFlow +import eu.kanade.tachiyomi.databinding.AnimeSourceControllerBinding +import eu.kanade.tachiyomi.source.AnimeCatalogueSource +import eu.kanade.tachiyomi.source.LocalAnimeSource +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.online.AnimeHttpSource +import eu.kanade.tachiyomi.ui.anime.AnimeController +import eu.kanade.tachiyomi.ui.animelib.ChangeAnimeCategoriesDialog +import eu.kanade.tachiyomi.ui.base.controller.FabController +import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController +import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.more.MoreController +import eu.kanade.tachiyomi.ui.webview.WebViewActivity +import eu.kanade.tachiyomi.util.system.connectivityManager +import eu.kanade.tachiyomi.util.system.openInBrowser +import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.inflate +import eu.kanade.tachiyomi.util.view.shrinkOnScroll +import eu.kanade.tachiyomi.util.view.snack +import eu.kanade.tachiyomi.widget.AutofitRecyclerView +import eu.kanade.tachiyomi.widget.EmptyView +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import uy.kohesive.injekt.injectLazy + +/** + * Controller to manage the catalogues available in the app. + */ +open class BrowseAnimeSourceController(bundle: Bundle) : + SearchableNucleusController(bundle), + FabController, + FlexibleAdapter.OnItemClickListener, + FlexibleAdapter.OnItemLongClickListener, + FlexibleAdapter.EndlessScrollListener, + ChangeAnimeCategoriesDialog.Listener { + + constructor(source: AnimeCatalogueSource, searchQuery: String? = null) : this( + Bundle().apply { + putLong(SOURCE_ID_KEY, source.id) + + if (searchQuery != null) { + putString(SEARCH_QUERY_KEY, searchQuery) + } + } + ) + + private val preferences: PreferencesHelper by injectLazy() + + /** + * Adapter containing the list of anime from the catalogue. + */ + protected var adapter: FlexibleAdapter>? = null + + private var actionFab: ExtendedFloatingActionButton? = null + private var actionFabScrollListener: RecyclerView.OnScrollListener? = null + + /** + * Snackbar containing an error message when a request fails. + */ + private var snack: Snackbar? = null + + /** + * Sheet containing filter items. + */ + private var filterSheet: SourceFilterSheet? = null + + /** + * Recycler view with the list of results. + */ + private var recycler: RecyclerView? = null + + /** + * Subscription for the number of anime per row. + */ + private var numColumnsJob: Job? = null + + /** + * Endless loading item. + */ + private var progressItem: ProgressItem? = null + + init { + setHasOptionsMenu(true) + } + + override fun getTitle(): String? { + return presenter.source.name + } + + override fun createPresenter(): BrowseAnimeSourcePresenter { + return BrowseAnimeSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY)) + } + + override fun createBinding(inflater: LayoutInflater) = AnimeSourceControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + + // Prepare filter sheet + initFilterSheet() + + // Initialize adapter, scroll listener and recycler views + adapter = FlexibleAdapter(null, this) + setupRecycler(view) + + binding.progress.isVisible = true + } + + open fun initFilterSheet() { + if (presenter.sourceFilters.isEmpty()) { + return + } + + filterSheet = SourceFilterSheet( + activity!!, + onFilterClicked = { + val allDefault = presenter.sourceFilters == presenter.source.getFilterList() + showProgressBar() + adapter?.clear() + presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters) + }, + onResetClicked = { + presenter.appliedFilters = FilterList() + val newFilters = presenter.source.getFilterList() + presenter.sourceFilters = newFilters + filterSheet?.setFilters(presenter.filterItems) + } + ) + filterSheet?.setFilters(presenter.filterItems) + + // TODO: [ExtendedFloatingActionButton] hide/show methods don't work properly + filterSheet?.setOnShowListener { actionFab?.isVisible = false } + filterSheet?.setOnDismissListener { actionFab?.isVisible = true } + + actionFab?.setOnClickListener { filterSheet?.show() } + + actionFab?.isVisible = true + } + + override fun configureFab(fab: ExtendedFloatingActionButton) { + actionFab = fab + + // Controlled by initFilterSheet() + fab.isVisible = false + + fab.setText(R.string.action_filter) + fab.setIconResource(R.drawable.ic_filter_list_24dp) + } + + override fun cleanupFab(fab: ExtendedFloatingActionButton) { + fab.setOnClickListener(null) + actionFabScrollListener?.let { recycler?.removeOnScrollListener(it) } + actionFab = null + } + + override fun onDestroyView(view: View) { + numColumnsJob?.cancel() + numColumnsJob = null + adapter = null + snack = null + recycler = null + super.onDestroyView(view) + } + + private fun setupRecycler(view: View) { + numColumnsJob?.cancel() + + var oldPosition = RecyclerView.NO_POSITION + val oldRecycler = binding.catalogueView.getChildAt(1) + if (oldRecycler is RecyclerView) { + oldPosition = (oldRecycler.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() + oldRecycler.adapter = null + + binding.catalogueView.removeView(oldRecycler) + } + + val recycler = if (preferences.sourceDisplayMode().get() == DisplayMode.LIST) { + RecyclerView(view.context).apply { + id = R.id.recycler + layoutManager = LinearLayoutManager(context) + layoutParams = RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + } + } else { + (binding.catalogueView.inflate(R.layout.source_recycler_autofit) as AutofitRecyclerView).apply { + numColumnsJob = getColumnsPreferenceForCurrentOrientation().asImmediateFlow { spanCount = it } + .drop(1) + // Set again the adapter to recalculate the covers height + .onEach { adapter = this@BrowseAnimeSourceController.adapter } + .launchIn(viewScope) + + (layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return when (adapter?.getItemViewType(position)) { + R.layout.source_compact_grid_item, R.layout.source_comfortable_grid_item, null -> 1 + else -> spanCount + } + } + } + } + } + + if (filterSheet != null) { + // Add bottom padding if filter FAB is visible + recycler.updatePadding(bottom = view.resources.getDimensionPixelOffset(R.dimen.fab_list_padding)) + recycler.clipToPadding = false + + actionFab?.shrinkOnScroll(recycler) + } + + recycler.applyInsetter { + type(navigationBars = true) { + padding() + } + } + recycler.setHasFixedSize(true) + recycler.adapter = adapter + + binding.catalogueView.addView(recycler, 1) + + if (oldPosition != RecyclerView.NO_POSITION) { + recycler.layoutManager?.scrollToPosition(oldPosition) + } + this.recycler = recycler + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + createOptionsMenu(menu, inflater, R.menu.source_browse, R.id.action_search) + val searchItem = menu.findItem(R.id.action_search) + + searchItem.fixExpand( + onExpand = { invalidateMenuOnExpand() }, + onCollapse = { + if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller() is GlobalSearchController) { + router.popController(this) + } else { + nonSubmittedQuery = "" + searchWithQuery("") + } + + true + } + ) + + val displayItem = when (preferences.sourceDisplayMode().get()) { + DisplayMode.COMPACT_GRID -> R.id.action_compact_grid + DisplayMode.COMFORTABLE_GRID -> R.id.action_comfortable_grid + DisplayMode.LIST -> R.id.action_list + } + menu.findItem(displayItem).isChecked = true + } + + override fun onSearchViewQueryTextSubmit(query: String?) { + searchWithQuery(query ?: "") + } + + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + + val isHttpSource = presenter.source is AnimeHttpSource + menu.findItem(R.id.action_open_in_web_view).isVisible = isHttpSource + + val isLocalSource = presenter.source is LocalAnimeSource + menu.findItem(R.id.action_local_source_help).isVisible = isLocalSource + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_search -> expandActionViewFromInteraction = true + R.id.action_compact_grid -> setDisplayMode(DisplayMode.COMPACT_GRID) + R.id.action_comfortable_grid -> setDisplayMode(DisplayMode.COMFORTABLE_GRID) + R.id.action_list -> setDisplayMode(DisplayMode.LIST) + R.id.action_open_in_web_view -> openInWebView() + R.id.action_local_source_help -> openLocalSourceHelpGuide() + } + return super.onOptionsItemSelected(item) + } + + private fun openInWebView() { + val source = presenter.source as? AnimeHttpSource ?: return + + val activity = activity ?: return + val intent = WebViewActivity.newIntent(activity, source.baseUrl, source.id, presenter.source.name) + startActivity(intent) + } + + private fun openLocalSourceHelpGuide() { + activity?.openInBrowser(LocalAnimeSource.HELP_URL) + } + + /** + * Restarts the request with a new query. + * + * @param newQuery the new query. + */ + fun searchWithQuery(newQuery: String) { + // If text didn't change, do nothing + if (presenter.query == newQuery) { + return + } + + showProgressBar() + adapter?.clear() + + presenter.restartPager(newQuery) + } + + /** + * Called from the presenter when the network request is received. + * + * @param page the current page. + * @param animes the list of anime of the page. + */ + fun onAddPage(page: Int, animes: List) { + val adapter = adapter ?: return + hideProgressBar() + if (page == 1) { + adapter.clear() + resetProgressItem() + } + adapter.onLoadMoreComplete(animes) + } + + /** + * Called from the presenter when the network request fails. + * + * @param error the error received. + */ + fun onAddPageError(error: Throwable) { + Timber.e(error) + val adapter = adapter ?: return + adapter.onLoadMoreComplete(null) + hideProgressBar() + + snack?.dismiss() + + val message = getErrorMessage(error) + val retryAction = View.OnClickListener { + // If not the first page, show bottom progress bar. + if (adapter.mainItemCount > 0 && progressItem != null) { + adapter.addScrollableFooterWithDelay(progressItem!!, 0, true) + } else { + showProgressBar() + } + presenter.requestNext() + } + + if (adapter.isEmpty) { + val actions = if (presenter.source is LocalAnimeSource) { + listOf( + EmptyView.Action(R.string.local_source_help_guide, R.drawable.ic_help_24dp) { openLocalSourceHelpGuide() } + ) + } else { + listOf( + EmptyView.Action(R.string.action_retry, R.drawable.ic_refresh_24dp, retryAction), + EmptyView.Action(R.string.action_open_in_web_view, R.drawable.ic_public_24dp) { openInWebView() }, + EmptyView.Action(R.string.label_help, R.drawable.ic_help_24dp) { activity?.openInBrowser(MoreController.URL_HELP) } + ) + } + + binding.emptyView.show(message, actions) + } else { + snack = (activity as? MainActivity)?.binding?.rootCoordinator?.snack(message, Snackbar.LENGTH_INDEFINITE) { + setAction(R.string.action_retry, retryAction) + } + } + } + + private fun getErrorMessage(error: Throwable): String { + if (error is NoResultsException) { + return binding.catalogueView.context.getString(R.string.no_results_found) + } + + return when { + error.message == null -> "" + error.message!!.startsWith("HTTP error") -> "${error.message}: ${binding.catalogueView.context.getString(R.string.http_error_hint)}" + else -> error.message!! + } + } + + /** + * Sets a new progress item and reenables the scroll listener. + */ + private fun resetProgressItem() { + progressItem = ProgressItem() + adapter?.endlessTargetCount = 0 + adapter?.setEndlessScrollListener(this, progressItem!!) + } + + /** + * Called by the adapter when scrolled near the bottom. + */ + override fun onLoadMore(lastPosition: Int, currentPage: Int) { + if (presenter.hasNextPage()) { + presenter.requestNext() + } else { + adapter?.onLoadMoreComplete(null) + adapter?.endlessTargetCount = 1 + } + } + + override fun noMoreLoad(newItemsSize: Int) { + } + + /** + * Called from the presenter when a anime is initialized. + * + * @param anime the anime initialized + */ + fun onAnimeInitialized(anime: Anime) { + getHolder(anime)?.setImage(anime) + } + + /** + * Sets the current display mode. + * + * @param mode the mode to change to + */ + private fun setDisplayMode(mode: DisplayMode) { + val view = view ?: return + val adapter = adapter ?: return + + preferences.sourceDisplayMode().set(mode) + activity?.invalidateOptionsMenu() + setupRecycler(view) + + // Initialize animes if not on a metered connection + if (!view.context.connectivityManager.isActiveNetworkMetered) { + val animes = (0 until adapter.itemCount).mapNotNull { + (adapter.getItem(it) as? AnimeSourceItem)?.anime + } + presenter.initializeAnimes(animes) + } + } + + /** + * Returns a preference for the number of anime per row based on the current orientation. + * + * @return the preference. + */ + private fun getColumnsPreferenceForCurrentOrientation(): Preference { + return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) { + preferences.portraitColumns() + } else { + preferences.landscapeColumns() + } + } + + /** + * Returns the view holder for the given anime. + * + * @param anime the anime to find. + * @return the holder of the anime or null if it's not bound. + */ + private fun getHolder(anime: Anime): AnimeSourceHolder<*>? { + val adapter = adapter ?: return null + + adapter.allBoundViewHolders.forEach { holder -> + val item = adapter.getItem(holder.bindingAdapterPosition) as? AnimeSourceItem + if (item != null && item.anime.id!! == anime.id!!) { + return holder as AnimeSourceHolder<*> + } + } + + return null + } + + /** + * Shows the progress bar. + */ + private fun showProgressBar() { + binding.emptyView.hide() + binding.progress.isVisible = true + snack?.dismiss() + snack = null + } + + /** + * Hides active progress bars. + */ + private fun hideProgressBar() { + binding.emptyView.hide() + binding.progress.isVisible = false + } + + /** + * Called when a anime is clicked. + * + * @param position the position of the element clicked. + * @return true if the item should be selected, false otherwise. + */ + override fun onItemClick(view: View, position: Int): Boolean { + val item = adapter?.getItem(position) as? AnimeSourceItem ?: return false + router.pushController(AnimeController(item.anime, true).withFadeTransaction()) + + return false + } + + /** + * Called when a anime is long clicked. + * + * Adds the anime to the default category if none is set it shows a list of categories for the user to put the anime + * in, the list consists of the default category plus the user's categories. The default category is preselected on + * new anime, and on already favorited anime the anime's categories are preselected. + * + * @param position the position of the element clicked. + */ + override fun onItemLongClick(position: Int) { + val activity = activity ?: return + val anime = (adapter?.getItem(position) as? AnimeSourceItem?)?.anime ?: return + + if (anime.favorite) { + MaterialDialog(activity) + .listItems( + items = listOf(activity.getString(R.string.remove_from_library)), + waitForPositiveButton = false + ) { _, which, _ -> + when (which) { + 0 -> { + presenter.changeAnimeFavorite(anime) + adapter?.notifyItemChanged(position) + activity.toast(activity.getString(R.string.manga_removed_library)) + } + } + } + .show() + } else { + val categories = presenter.getCategories() + val defaultCategoryId = preferences.defaultCategory() + val defaultCategory = categories.find { it.id == defaultCategoryId } + + when { + // Default category set + defaultCategory != null -> { + presenter.moveAnimeToCategory(anime, defaultCategory) + + presenter.changeAnimeFavorite(anime) + adapter?.notifyItemChanged(position) + activity.toast(activity.getString(R.string.manga_added_library)) + } + + // Automatic 'Default' or no categories + defaultCategoryId == 0 || categories.isEmpty() -> { + presenter.moveAnimeToCategory(anime, null) + + presenter.changeAnimeFavorite(anime) + adapter?.notifyItemChanged(position) + activity.toast(activity.getString(R.string.manga_added_library)) + } + + // Choose a category + else -> { + val ids = presenter.getAnimeCategoryIds(anime) + val preselected = ids.mapNotNull { id -> + categories.indexOfFirst { it.id == id }.takeIf { it != -1 } + }.toTypedArray() + + ChangeAnimeCategoriesDialog(this, listOf(anime), categories, preselected) + .showDialog(router) + } + } + } + } + + /** + * Update anime to use selected categories. + * + * @param animes The list of anime to move to categories. + * @param categories The list of categories where anime will be placed. + */ + override fun updateCategoriesForAnimes(animes: List, categories: List) { + val anime = animes.firstOrNull() ?: return + + presenter.changeAnimeFavorite(anime) + presenter.updateAnimeCategories(anime, categories) + + val position = adapter?.currentItems?.indexOfFirst { it -> (it as AnimeSourceItem).anime.id == anime.id } + if (position != null) { + adapter?.notifyItemChanged(position) + } + activity?.toast(activity?.getString(R.string.manga_added_library)) + } + + protected companion object { + const val SOURCE_ID_KEY = "sourceId" + const val SEARCH_QUERY_KEY = "searchQuery" + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseAnimeSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseAnimeSourcePresenter.kt new file mode 100644 index 000000000..b5f4a9a33 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseAnimeSourcePresenter.kt @@ -0,0 +1,371 @@ +package eu.kanade.tachiyomi.ui.browse.source.browse + +import android.os.Bundle +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.data.cache.AnimeCoverCache +import eu.kanade.tachiyomi.data.database.AnimeDatabaseHelper +import eu.kanade.tachiyomi.data.database.models.Anime +import eu.kanade.tachiyomi.data.database.models.AnimeCategory +import eu.kanade.tachiyomi.data.database.models.Category +import eu.kanade.tachiyomi.data.database.models.toAnimeInfo +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.source.AnimeCatalogueSource +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.SAnime +import eu.kanade.tachiyomi.source.model.toSAnime +import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem +import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxSectionItem +import eu.kanade.tachiyomi.ui.browse.source.filter.GroupItem +import eu.kanade.tachiyomi.ui.browse.source.filter.HeaderItem +import eu.kanade.tachiyomi.ui.browse.source.filter.SelectItem +import eu.kanade.tachiyomi.ui.browse.source.filter.SelectSectionItem +import eu.kanade.tachiyomi.ui.browse.source.filter.SeparatorItem +import eu.kanade.tachiyomi.ui.browse.source.filter.SortGroup +import eu.kanade.tachiyomi.ui.browse.source.filter.SortItem +import eu.kanade.tachiyomi.ui.browse.source.filter.TextItem +import eu.kanade.tachiyomi.ui.browse.source.filter.TextSectionItem +import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateItem +import eu.kanade.tachiyomi.ui.browse.source.filter.TriStateSectionItem +import eu.kanade.tachiyomi.util.episode.EpisodeSettingsHelper +import eu.kanade.tachiyomi.util.lang.launchIO +import eu.kanade.tachiyomi.util.lang.withUIContext +import eu.kanade.tachiyomi.util.removeCovers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import rx.Observable +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import timber.log.Timber +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.util.Date + +/** + * Presenter of [BrowseSourceController]. + */ +open class BrowseAnimeSourcePresenter( + private val sourceId: Long, + searchQuery: String? = null, + private val sourceManager: SourceManager = Injekt.get(), + private val db: AnimeDatabaseHelper = Injekt.get(), + private val prefs: PreferencesHelper = Injekt.get(), + private val coverCache: AnimeCoverCache = Injekt.get() +) : BasePresenter() { + + /** + * Selected source. + */ + lateinit var source: AnimeCatalogueSource + + /** + * Modifiable list of filters. + */ + var sourceFilters = FilterList() + set(value) { + field = value + filterItems = value.toItems() + } + + var filterItems: List> = emptyList() + + /** + * List of filters used by the [Pager]. If empty alongside [query], the popular query is used. + */ + var appliedFilters = FilterList() + + /** + * Pager containing a list of anime results. + */ + private lateinit var pager: AnimePager + + /** + * Flow of anime list to initialize. + */ + private val animeDetailsFlow = MutableStateFlow>(emptyList()) + + /** + * Subscription for the pager. + */ + private var pagerSubscription: Subscription? = null + + /** + * Subscription for one request from the pager. + */ + private var pageSubscription: Subscription? = null + + init { + query = searchQuery ?: "" + } + + override fun onCreate(savedState: Bundle?) { + super.onCreate(savedState) + + source = sourceManager.get(sourceId) as? AnimeCatalogueSource ?: return + + sourceFilters = source.getFilterList() + + if (savedState != null) { + query = savedState.getString(::query.name, "") + } + + restartPager() + } + + override fun onSave(state: Bundle) { + state.putString(::query.name, query) + super.onSave(state) + } + + /** + * Restarts the pager for the active source with the provided query and filters. + * + * @param query the query. + * @param filters the current state of the filters (for search mode). + */ + fun restartPager(query: String = this.query, filters: FilterList = this.appliedFilters) { + this.query = query + this.appliedFilters = filters + + // Create a new pager. + pager = createPager(query, filters) + + val sourceId = source.id + + val sourceDisplayMode = prefs.sourceDisplayMode() + + // Prepare the pager. + pagerSubscription?.let { remove(it) } + pagerSubscription = pager.results() + .observeOn(Schedulers.io()) + .map { (first, second) -> first to second.map { networkToLocalAnime(it, sourceId) } } + .doOnNext { initializeAnimes(it.second) } + .map { (first, second) -> first to second.map { AnimeSourceItem(it, sourceDisplayMode) } } + .observeOn(AndroidSchedulers.mainThread()) + .subscribeReplay( + { view, (page, animes) -> + view.onAddPage(page, animes) + }, + { _, error -> + Timber.e(error) + } + ) + + // Request first page. + requestNext() + } + + /** + * Requests the next page for the active pager. + */ + fun requestNext() { + if (!hasNextPage()) return + + pageSubscription?.let { remove(it) } + pageSubscription = Observable.defer { pager.requestNext() } + .subscribeFirst( + { _, _ -> + // Nothing to do when onNext is emitted. + }, + BrowseAnimeSourceController::onAddPageError + ) + } + + /** + * Returns true if the last fetched page has a next page. + */ + fun hasNextPage(): Boolean { + return pager.hasNextPage + } + + /** + * Returns a anime from the database for the given anime from network. It creates a new entry + * if the anime is not yet in the database. + * + * @param sAnime the anime from the source. + * @return a anime from the database. + */ + private fun networkToLocalAnime(sAnime: SAnime, sourceId: Long): Anime { + var localAnime = db.getAnime(sAnime.url, sourceId).executeAsBlocking() + if (localAnime == null) { + val newAnime = Anime.create(sAnime.url, sAnime.title, sourceId) + newAnime.copyFrom(sAnime) + val result = db.insertAnime(newAnime).executeAsBlocking() + newAnime.id = result.insertedId() + localAnime = newAnime + } + return localAnime + } + + /** + * Initialize a list of anime. + * + * @param animes the list of anime to initialize. + */ + fun initializeAnimes(animes: List) { + presenterScope.launchIO { + animes.asFlow() + .filter { it.thumbnail_url == null && !it.initialized } + .map { getAnimeDetails(it) } + .onEach { + withUIContext { + @Suppress("DEPRECATION") + view?.onAnimeInitialized(it) + } + } + .catch { e -> Timber.e(e) } + .collect() + } + } + + /** + * Returns the initialized anime. + * + * @param anime the anime to initialize. + * @return the initialized anime + */ + private suspend fun getAnimeDetails(anime: Anime): Anime { + try { + val networkAnime = source.getAnimeDetails(anime.toAnimeInfo()) + anime.copyFrom(networkAnime.toSAnime()) + anime.initialized = true + db.insertAnime(anime).executeAsBlocking() + } catch (e: Exception) { + Timber.e(e) + } + return anime + } + + /** + * Adds or removes a anime from the library. + * + * @param anime the anime to update. + */ + fun changeAnimeFavorite(anime: Anime) { + anime.favorite = !anime.favorite + anime.date_added = when (anime.favorite) { + true -> Date().time + false -> 0 + } + + if (!anime.favorite) { + anime.removeCovers(coverCache) + } else { + EpisodeSettingsHelper.applySettingDefaults(anime) + } + + db.insertAnime(anime).executeAsBlocking() + } + + /** + * Set the filter states for the current source. + * + * @param filters a list of active filters. + */ + fun setSourceFilter(filters: FilterList) { + restartPager(filters = filters) + } + + open fun createPager(query: String, filters: FilterList): AnimePager { + return AnimeSourcePager(source, query, filters) + } + + private fun FilterList.toItems(): List> { + return mapNotNull { filter -> + when (filter) { + is Filter.Header -> HeaderItem(filter) + is Filter.Separator -> SeparatorItem(filter) + is Filter.CheckBox -> CheckboxItem(filter) + is Filter.TriState -> TriStateItem(filter) + is Filter.Text -> TextItem(filter) + is Filter.Select<*> -> SelectItem(filter) + is Filter.Group<*> -> { + val group = GroupItem(filter) + val subItems = filter.state.mapNotNull { + when (it) { + is Filter.CheckBox -> CheckboxSectionItem(it) + is Filter.TriState -> TriStateSectionItem(it) + is Filter.Text -> TextSectionItem(it) + is Filter.Select<*> -> SelectSectionItem(it) + else -> null + } + } + subItems.forEach { it.header = group } + group.subItems = subItems + group + } + is Filter.Sort -> { + val group = SortGroup(filter) + val subItems = filter.values.map { + SortItem(it, group) + } + group.subItems = subItems + group + } + } + } + } + + /** + * Get user categories. + * + * @return List of categories, not including the default category + */ + fun getCategories(): List { + return db.getCategories().executeAsBlocking() + } + + /** + * Gets the category id's the anime is in, if the anime is not in a category, returns the default id. + * + * @param anime the anime to get categories from. + * @return Array of category ids the anime is in, if none returns default id + */ + fun getAnimeCategoryIds(anime: Anime): Array { + val categories = db.getCategoriesForAnime(anime).executeAsBlocking() + return categories.mapNotNull { it.id }.toTypedArray() + } + + /** + * Move the given anime to categories. + * + * @param categories the selected categories. + * @param anime the anime to move. + */ + private fun moveAnimeToCategories(anime: Anime, categories: List) { + val mc = categories.filter { it.id != 0 }.map { AnimeCategory.create(anime, it) } + db.setAnimeCategories(mc, listOf(anime)) + } + + /** + * Move the given anime to the category. + * + * @param category the selected category. + * @param anime the anime to move. + */ + fun moveAnimeToCategory(anime: Anime, category: Category?) { + moveAnimeToCategories(anime, listOfNotNull(category)) + } + + /** + * Update anime to use selected categories. + * + * @param anime needed to change + * @param selectedCategories selected categories + */ + fun updateAnimeCategories(anime: Anime, selectedCategories: List) { + if (!anime.favorite) { + changeAnimeFavorite(anime) + } + + moveAnimeToCategories(anime, selectedCategories) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt index 890feba74..1d6269415 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt @@ -124,10 +124,7 @@ open class BrowseSourceController(bundle: Bundle) : return BrowseSourcePresenter(args.getLong(SOURCE_ID_KEY), args.getString(SEARCH_QUERY_KEY)) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = SourceControllerBinding.inflate(inflater) - return binding.root - } + override fun createBinding(inflater: LayoutInflater) = SourceControllerBinding.inflate(inflater) override fun onViewCreated(view: View) { super.onViewCreated(view) @@ -269,6 +266,7 @@ open class BrowseSourceController(bundle: Bundle) : if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller() is GlobalSearchController) { router.popController(this) } else { + nonSubmittedQuery = "" searchWithQuery("") } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt index b6dc40a14..ad5b1a729 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt @@ -6,7 +6,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager @@ -50,22 +49,7 @@ open class GlobalSearchController( setHasOptionsMenu(true) } - /** - * Initiate the view with [R.layout.global_search_controller]. - * - * @param inflater used to load the layout xml. - * @param container containing parent views. - * @return inflated view - */ - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = GlobalSearchControllerBinding.inflate(inflater) - binding.recycler.applyInsetter { - type(navigationBars = true) { - padding() - } - } - return binding.root - } + override fun createBinding(inflater: LayoutInflater) = GlobalSearchControllerBinding.inflate(inflater) override fun getTitle(): String? { return presenter.query @@ -142,6 +126,12 @@ open class GlobalSearchController( override fun onViewCreated(view: View) { super.onViewCreated(view) + binding.recycler.applyInsetter { + type(navigationBars = true) { + padding() + } + } + adapter = GlobalSearchAdapter(this) // Create recycler and set adapter. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt index af189e574..4b2a17745 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryController.kt @@ -4,7 +4,6 @@ import android.view.LayoutInflater import android.view.Menu import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.recyclerview.widget.LinearLayoutManager @@ -68,21 +67,7 @@ class CategoryController : return resources?.getString(R.string.action_edit_categories) } - /** - * Returns the view of this controller. - * - * @param inflater The layout inflater to create the view from XML. - * @param container The parent view for this one. - */ - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = CategoriesControllerBinding.inflate(inflater) - binding.recycler.applyInsetter { - type(navigationBars = true) { - padding() - } - } - return binding.root - } + override fun createBinding(inflater: LayoutInflater) = CategoriesControllerBinding.inflate(inflater) /** * Called after view inflation. Used to initialize the view. @@ -92,6 +77,12 @@ class CategoryController : override fun onViewCreated(view: View) { super.onViewCreated(view) + binding.recycler.applyInsetter { + type(navigationBars = true) { + padding() + } + } + adapter = CategoryAdapter(this@CategoryController) binding.recycler.layoutManager = LinearLayoutManager(view.context) binding.recycler.setHasFixedSize(true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt index 72cacd907..e2699ccf6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadController.kt @@ -5,7 +5,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -55,15 +54,7 @@ class DownloadController : setHasOptionsMenu(true) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = DownloadControllerBinding.inflate(inflater) - binding.recycler.applyInsetter { - type(navigationBars = true) { - padding() - } - } - return binding.root - } + override fun createBinding(inflater: LayoutInflater) = DownloadControllerBinding.inflate(inflater) override fun createPresenter(): DownloadPresenter { return DownloadPresenter() @@ -76,6 +67,12 @@ class DownloadController : override fun onViewCreated(view: View) { super.onViewCreated(view) + binding.recycler.applyInsetter { + type(navigationBars = true) { + padding() + } + } + // Check if download queue is empty and update information accordingly. setInformationView() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index fed43212a..2b0795053 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -131,6 +131,15 @@ class MainActivity : BaseViewBindingActivity() { tabAnimator = ViewHeightAnimator(binding.tabs, 0L) bottomNavAnimator = ViewHeightAnimator(binding.bottomNav) + // If bottom nav is hidden, make it visible again when the app bar is expanded + binding.appbar.addOnOffsetChangedListener( + AppBarLayout.OnOffsetChangedListener { _, verticalOffset -> + if (verticalOffset == 0) { + showBottomNav(true) + } + } + ) + // Set behavior of bottom nav preferences.hideBottomBar() .asImmediateFlow { setBottomNavBehaviorOnScroll() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index d4512d7c6..c4025a6a0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -11,7 +11,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.graphics.blue @@ -199,8 +198,11 @@ class MangaController : ) } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = MangaControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater) = MangaControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + binding.recycler.applyInsetter { type(navigationBars = true) { padding() @@ -211,11 +213,6 @@ class MangaController : margin(bottom = true) } } - return binding.root - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) if (manga == null || source == null) return @@ -1007,11 +1004,17 @@ class MangaController : // OVERFLOW MENU DIALOGS - private fun getUnreadChaptersSorted() = presenter.chapters - .sortedWith(presenter.getChapterSort()) - .filter { !it.read && it.status == Download.State.NOT_DOWNLOADED } - .distinctBy { it.name } - .reversed() + private fun getUnreadChaptersSorted(): List { + val chapters = presenter.chapters + .sortedWith(presenter.getChapterSort()) + .filter { !it.read && it.status == Download.State.NOT_DOWNLOADED } + .distinctBy { it.name } + return if (presenter.sortDescending()) { + chapters.reversed() + } else { + chapters + } + } private fun downloadChapters(choice: Int) { val chaptersToDownload = when (choice) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 109732e57..7dd1f653f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -474,7 +474,12 @@ class MangaPresenter( * Returns the next unread chapter or null if everything is read. */ fun getNextUnreadChapter(): ChapterItem? { - return chapters.sortedWith(getChapterSort()).findLast { !it.read } + val chapters = chapters.sortedWith(getChapterSort()) + return if (sortDescending()) { + return chapters.findLast { !it.read } + } else { + chapters.find { !it.read } + } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt index 6a8eb21b2..5a762b6d3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt @@ -78,15 +78,6 @@ class AboutController : SettingsController() { openInBrowser(url) } } - if (BuildConfig.DEBUG) { - preference { - key = "pref_about_notices" - titleRes = R.string.notices - onClick { - openInBrowser("https://github.com/tachiyomiorg/tachiyomi/blob/master/PREVIEW_RELEASE_NOTES.md") - } - } - } preferenceCategory { preference { @@ -97,6 +88,14 @@ class AboutController : SettingsController() { onClick { openInBrowser(it) } } } + preference { + key = "pref_about_facebook" + title = "Facebook" + "https://facebook.com/tachiyomiorg".also { + summary = it + onClick { openInBrowser(it) } + } + } preference { key = "pref_about_twitter" title = "Twitter" @@ -116,15 +115,7 @@ class AboutController : SettingsController() { preference { key = "pref_about_github" title = "GitHub" - "https://github.com/tachiyomiorg/tachiyomi".also { - summary = it - onClick { openInBrowser(it) } - } - } - preference { - key = "pref_about_label_extensions" - titleRes = R.string.label_extensions - "https://github.com/tachiyomiorg/tachiyomi-extensions".also { + "https://github.com/tachiyomiorg".also { summary = it onClick { openInBrowser(it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index 81481a266..cdbe67d7a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -85,8 +85,7 @@ class HttpPageLoader( * the local cache, otherwise fallbacks to network. */ override fun getPages(): Observable> { - return chapterCache - .getPageListFromCache(chapter.chapter) + return Observable.fromCallable { chapterCache.getPageListFromCache(chapter.chapter) } .onErrorResumeNext { source.fetchPageList(chapter.chapter) } .map { pages -> pages.mapIndexed { index, page -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt index 769b82fac..f6e1fba96 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt @@ -7,7 +7,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog @@ -72,18 +71,16 @@ class HistoryController : return HistoryPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = HistoryControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater) = HistoryControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + binding.recycler.applyInsetter { type(navigationBars = true) { padding() } } - return binding.root - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) // Initialize adapter binding.recycler.layoutManager = LinearLayoutManager(view.context) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt index a8a611642..905c05038 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt @@ -5,7 +5,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.recyclerview.widget.LinearLayoutManager @@ -69,8 +68,10 @@ class UpdatesController : return UpdatesPresenter() } - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = UpdatesControllerBinding.inflate(inflater) + override fun createBinding(inflater: LayoutInflater) = UpdatesControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) binding.recycler.applyInsetter { type(navigationBars = true) { padding() @@ -81,11 +82,7 @@ class UpdatesController : margin(bottom = true) } } - return binding.root - } - override fun onViewCreated(view: View) { - super.onViewCreated(view) view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS) // Init RecyclerView and adapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/security/BiometricUnlockActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/security/BiometricUnlockActivity.kt index bb7ed1338..af094c1bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/security/BiometricUnlockActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/security/BiometricUnlockActivity.kt @@ -1,22 +1,19 @@ package eu.kanade.tachiyomi.ui.security import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricPrompt import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity import eu.kanade.tachiyomi.util.system.BiometricUtil import timber.log.Timber -import uy.kohesive.injekt.injectLazy import java.util.Date import java.util.concurrent.Executors /** * Blank activity with a BiometricPrompt. */ -class BiometricUnlockActivity : AppCompatActivity() { +class BiometricUnlockActivity : BaseThemedActivity() { - private val preferences: PreferencesHelper by injectLazy() private val executor = Executors.newSingleThreadExecutor() override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index a3f51db11..f306a93f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -20,6 +20,8 @@ import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.CrashLogUtil +import eu.kanade.tachiyomi.util.lang.launchIO +import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.preference.defaultValue import eu.kanade.tachiyomi.util.preference.intListPreference import eu.kanade.tachiyomi.util.preference.onChange @@ -31,9 +33,6 @@ import eu.kanade.tachiyomi.util.preference.switchPreference import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.powerManager import eu.kanade.tachiyomi.util.system.toast -import rx.Observable -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers import uy.kohesive.injekt.injectLazy import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys @@ -171,27 +170,18 @@ class SettingsAdvancedController : SettingsController() { private fun clearChapterCache() { if (activity == null) return - val files = chapterCache.cacheDir.listFiles() ?: return - - var deletedFiles = 0 - - Observable.defer { Observable.from(files) } - .doOnNext { file -> - if (chapterCache.removeFileFromCache(file.name)) { - deletedFiles++ + launchIO { + try { + val deletedFiles = chapterCache.clear() + withUIContext { + activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles)) + findPreference(CLEAR_CACHE_KEY)?.summary = + resources?.getString(R.string.used_cache, chapterCache.readableSize) } + } catch (e: Throwable) { + withUIContext { activity?.toast(R.string.cache_delete_error) } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError { - activity?.toast(R.string.cache_delete_error) - } - .doOnCompleted { - activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles)) - findPreference(CLEAR_CACHE_KEY)?.summary = - resources?.getString(R.string.used_cache, chapterCache.readableSize) - } - .subscribe() + } } class ClearDatabaseDialogController : DialogController() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt index 042661eb7..025457652 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt @@ -32,6 +32,7 @@ abstract class SettingsController : PreferenceController() { var preferenceKey: String? = null val preferences: PreferencesHelper = Injekt.get() val viewScope = MainScope() + private var themedContext: Context? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View { val view = super.onCreateView(inflater, container, savedInstanceState) @@ -76,20 +77,23 @@ abstract class SettingsController : PreferenceController() { super.onChangeStarted(handler, type) } + override fun onDestroyView(view: View) { + super.onDestroyView(view) + themedContext = null + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - val screen = preferenceManager.createPreferenceScreen(getThemedContext()) + val tv = TypedValue() + activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true) + themedContext = ContextThemeWrapper(activity, tv.resourceId) + + val screen = preferenceManager.createPreferenceScreen(themedContext) preferenceScreen = screen setupPreferenceScreen(screen) } abstract fun setupPreferenceScreen(screen: PreferenceScreen): PreferenceScreen - private fun getThemedContext(): Context { - val tv = TypedValue() - activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true) - return ContextThemeWrapper(activity, tv.resourceId) - } - private fun animatePreferenceHighlight(view: View) { ValueAnimator .ofObject(ArgbEvaluator(), Color.TRANSPARENT, view.context.getResourceColor(R.attr.rippleColor)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt index 2f8517fc5..5dc4e1d05 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchController.kt @@ -6,7 +6,6 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.LinearLayoutManager import eu.kanade.tachiyomi.R @@ -33,17 +32,7 @@ class SettingsSearchController : setHasOptionsMenu(true) } - /** - * Initiate the view with [R.layout.settings_search_controller]. - * - * @param inflater used to load the layout xml. - * @param container containing parent views. - * @return inflated view - */ - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - binding = SettingsSearchControllerBinding.inflate(inflater) - return binding.root - } + override fun createBinding(inflater: LayoutInflater) = SettingsSearchControllerBinding.inflate(inflater) override fun getTitle(): String? { return presenter.query diff --git a/app/src/main/res/layout/anime_source_controller.xml b/app/src/main/res/layout/anime_source_controller.xml new file mode 100644 index 000000000..1338a925e --- /dev/null +++ b/app/src/main/res/layout/anime_source_controller.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/animelib_grid_recycler.xml b/app/src/main/res/layout/animelib_grid_recycler.xml new file mode 100644 index 000000000..7eaa14346 --- /dev/null +++ b/app/src/main/res/layout/animelib_grid_recycler.xml @@ -0,0 +1,14 @@ + + diff --git a/app/src/main/res/layout/animelib_list_recycler.xml b/app/src/main/res/layout/animelib_list_recycler.xml new file mode 100644 index 000000000..7b5ad9153 --- /dev/null +++ b/app/src/main/res/layout/animelib_list_recycler.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/watcher_color_filter_settings.xml b/app/src/main/res/layout/watcher_color_filter_settings.xml new file mode 100644 index 000000000..80899cf9b --- /dev/null +++ b/app/src/main/res/layout/watcher_color_filter_settings.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/watcher_general_settings.xml b/app/src/main/res/layout/watcher_general_settings.xml new file mode 100644 index 000000000..e266d49b7 --- /dev/null +++ b/app/src/main/res/layout/watcher_general_settings.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/watcher_reading_mode_settings.xml b/app/src/main/res/layout/watcher_reading_mode_settings.xml new file mode 100644 index 000000000..15968285b --- /dev/null +++ b/app/src/main/res/layout/watcher_reading_mode_settings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/watcher_transition_view.xml b/app/src/main/res/layout/watcher_transition_view.xml new file mode 100644 index 000000000..d40d40e7e --- /dev/null +++ b/app/src/main/res/layout/watcher_transition_view.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/animelib_selection.xml b/app/src/main/res/menu/animelib_selection.xml new file mode 100644 index 000000000..481509214 --- /dev/null +++ b/app/src/main/res/menu/animelib_selection.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 8e78ae13a..cb9002f0a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -630,7 +630,7 @@ Zakázat vše Povolit vše Nastavení vyhledávání - Data přidání + Datum přidání Naposledy zkontrolováno Sledováno Opětovným stisknutím tlačítka aplikaci opustíte @@ -668,4 +668,15 @@ Zobrazení Zobrazovat karty kategorií Odznáček u nepřečtených + Tato verze systému Android již není podporována + Kopírování do schránky se nezdařilo + Nejsi přihlášen/a: %1$s + Průběžné vertikální + Okraj + Převrátit klepnutí + Rozdělení na dvě stránky + Ukázat oblast klepnutí když je čtečka otevřená + Systém sledování + Zobrazit chyby + Datum načtení \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3e4b6c202..b5797f732 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -672,4 +672,7 @@ Manga in ausgeschlossenen Kategorien werden nicht heruntergeladen, auch wenn sie in eingeschlossenen Kategorien vorhanden sind. Automatisches Herunterladen Manga in ausgeschlossenen Kategorien werden nicht aktualisiert, auch wenn sie in eingeschlossenen Kategorien vorhanden sind. + Fehler anzeigen + Diese Android-Version wird nicht mehr unterstützt + Kopieren in die Zwischenablage fehlgeschlagen \ No newline at end of file diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 067367c89..47b7ae4a3 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -672,4 +672,7 @@ Τα Manga σε εξαιρούμενες κατηγορίες δεν θα ληφθούν ακόμα κι αν ανήκουν και σε κατηγορίες που περιλαμβάνονται. Αυτόματη λήψη Τα manga στις αποκλεισμένες κατηγορίες δεν θα ενημερώνονται ακόμη και αν βρίσκονται επίσης στις συμπεριλαμβανόμενες κατηγορίες. + Εμφάνιση σφαλμάτων + Αυτή η έκδοση Android δεν υποστηρίζεται πλέον + Απέτυχε η αντιγραφή στο πρόχειρο \ No newline at end of file diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index b33072b7c..4a265d0f9 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -4,15 +4,15 @@ Historio Ĝisdatigoj Biblioteko - Elŝutos + Elŝutoj Agordoj - Plu + Pli Nomo Neniuj ĝisdatigoj Neniu elŝuto Asistado Konektprogramaro Informaĵo - Konektoprogramaro + Aldonaĵoj Migri Arĥivo Retejo @@ -162,4 +162,114 @@ 18+ Lingvo: %1$s Versio: %1$s + Sekv\' + Antaŭ\' + Foliumi + Ĉapitroj + + 1 ĉapitro + %1$s ĉapitroj + + Elŝutitaj ĉapitroj + En biblioteko + Titolo aŭ aŭtoro… + Aldonita al biblioteko + Aldoni al biblioteko + Propra dosierujo + Post legi ilin + Elŝuti nur per Vifio + 25% + 20% + 15% + 10% + Neniu + Legada + Legada reĝimo + Vid + Blu + Verd + Ruĝ + Ŝlosita + Libera + Orientiĝo + Rapida + Normala + Aŭtomate + Ŝaĝa adapto + Adapti al alto + Adapti al larĝo + Seninterrompe vertikale + Vertikale + Dekstra + Maldekstra + Dekstra kaj Maldekstra + En formo kiel \"L\" + Defaŭlte + Defaŭlte + Ambaŭ + Vertikala + Horizontala + Nenio + Navigo + Preterpasi ĉapitrojn markitajn kiel legitaj + Ne malŝalti ekranon + Ekranado + Obligado + Plustavolo + Defaŭlta + Adaptita lumeco + Stuci borderojn + 32-bitaj koloroj + Legada reĝimo + Montri numero de paĝo + Ŝlosi orientiĝon + Plenekrano + Ĉi tiu aldonaĵo ne estas plu disponebla. + Ĉiuj + Ĉio + Ĝisdatiga ordigo + Po unu fojon 2 tage + Po unu fojon 12 hore + Po unu fojon 8 hore + Po unu fojon 6 hore + Po unu fojon 4 hore + Po unu fojon 3 hore + Po unu fojon 2 hore + Po unu fojon hore + Mana + Ofteco de ĝisdatigoj + Ĝisdatigoj + Defaŭlte + Horizontale + Vertikale + Montrado + Montri en aldonaĵlisto + Montri en fontlisto + MPL/NSFW (18+) fontoj + Konfirmi eliron + AMOLED-a nigra + Lingvo + Ĝenerala + Apo maldisponebla + Restaŭro + Montri erarojn + Restartigi + Malkreskante + Montri nombro de elementoj + Montri kategoriajn langetojn + Nelegitajsignoj + Elŝutosignoj + Kompakta krado + Montrado + Montrada reĝimo + Malfermi per WebView + Movi + Daŭrigi + Paŭzigi + Sekva nelegita + Ordigi malkreskante + Ordigi kreskante + Redakti kovrilon + Aldoni al kategorioj + Serĉi ĉie \ No newline at end of file diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 9f3e04c2b..8b79fc9e5 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -672,4 +672,7 @@ Poissuljettuihin kategorioihin kuuluvia mangoja ei ladata, vaikka ne olisivat myös sisällytetyissä kategorioissa. Automaattinen lataus Poissuljettuihin kategorioihin sisältyvää mangaa ei päivitetä, vaikka ne olisivat myös sisällytetyissä kategorioissa. + Näytä virheet + Tätä Android-versiota ei enää tueta + Kopiointi leikepöydälle epäonnistui \ No newline at end of file diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index 5712a5c6b..09eb77c61 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -254,7 +254,7 @@ Lahat Lahat - 1 kategorya + %d kategorya %d (na) kategorya Palaging tanungin @@ -526,7 +526,7 @@ Sagisag (username) Mag-login sa %1$s - Isa na lang + 1 na lang %1$s na lang Isalâ ang Aklatan @@ -672,4 +672,5 @@ Wala Di ia-update ang mga manga na nasa di kasamang kategorya kahit na nasa kasamang kategorya ang mga ito. Petsa kinuha + Ipakita ang mga error \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index d17e5d1fd..3d4616e33 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -706,4 +706,7 @@ Les mangas dans les catégories exclus ne seront pas mises a jour meme s\'ils sont aussi dans les catégories inlcus. Les mangas dans les catégories exclus ne seront pas mise a jour meme s\'ils sont aussi dans les catégories inclus. Téléchargement Automatique + Afficher les erreurs + Cette version d\'Android n\'est plus supportée + Échec de la copie dans le presse-papiers \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 4a69d773f..2374e52b4 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -684,4 +684,5 @@ Uključi: %s Manga u isključenim kategorijama neće se ažurirati čak niti ako se također nalaze u uključenim kategorijama. Datum preuzimanja + Prikaži greške \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 663a47913..1c39ee3cb 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -80,7 +80,7 @@ Tegak Menyamping Asali - Frekuensi pembaruan perpustakaan + Frekuensi pembaruan Manual Tiap jam Tiap 2 jam @@ -91,7 +91,7 @@ Tiap 2 hari Tiap minggu Semua - Pembatasan pembaruan perpustakaan + Pembatasan pembaruan Perbaharui hanya ketika kondisi terpenuhi Sedang mengisi daya Perbarui manga yang masih belum tamat saja @@ -376,7 +376,7 @@ Dodge / Cerahkan Burn / Gelapkan Bantuan - Urutan perbarui perpustakaan + Urutan pembaruan Hasil tidak ditemukan Pilih sumber untuk migrasi dari Kembali @@ -639,4 +639,7 @@ Tampilkan jumlah item Setiap 8 jam Setiap 4 jam + Kosong + Tampilkan error + Tanggal diambil \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6640db820..398b79f47 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -594,7 +594,7 @@ Stato sconosciuto Autore sconosciuto - Aggiornato verso v%1$s + Aggiornato a v%1$s Le novità Richiesto riavvio dell\'app per applicare le modifiche Rete @@ -702,4 +702,8 @@ Destra Sinistra Manga che si trovano in categorie escluse non saranno aggiornati anche se si trovano in categorie incluse. + DNS via HTTPS + Mostra schema di navigazione + Mostra zone di tocco quando il lettore viene aperto + Mostra errori \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index a0a724019..85efe4002 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -70,7 +70,7 @@ 縦向き 横向き デフォルト - ライブラリ更新頻度 + 更新頻度 マニュアル 毎時間 2時間ごと @@ -81,7 +81,7 @@ 2日ごと 毎週 すべて - ライブラリ更新制限 + 更新制限 条件が満たされた場合にのみ更新する 充電中 連載中のマンガのみ更新 @@ -369,7 +369,7 @@ 既定 オーバーレイ スクリーン - ライブラリ更新の順 + 更新の順 結果が見つかりませんでした 移行元を選択 前へ @@ -380,7 +380,7 @@ 廃止済み この拡張機能は利用不可になりました。 日付形式 - 更新 + グローバルアップデート %1$sからログアウトしますか? ログアウト ログアウトしました @@ -402,7 +402,7 @@ AMOLEDブラック 通知設定 セキュリティ - 生体認証 + アンロックを必要とする タイムアウトロック 常時 しない @@ -650,6 +650,15 @@ 次へ 前へ - ビューアが立ち上がるとタップゾーンをしばらく表示します + ビューアが立ち上がるとタップゾーンを表示します ナビゲーションレイアウトオーバーレイを表示 + DNS over HTTPS + 含まれているカテゴリーに入っていても、除外対象カテゴリーにあるマンガは更新されません。 + 自動ダウンロード + 下記を除外:%s + 下記を含む:%s + なし + 含まれているカテゴリーに入っていても、除外対象カテゴリーにあるマンガは更新されません。 + エラーを表示 + 日付を取得しました \ No newline at end of file diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 6968e277b..47292f1b2 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -14,7 +14,7 @@ ಎಂದಿಗೂ ಇಲ್ಲ ಯಾವಾಗಲೂ ನಿಷ್ಕ್ರಿಯವಾಗಿದ್ದಾಗ ಲಾಕ್ ಮಾಡಿ - "ಬೆರಳಚ್ಚ ಬದ್ರತೆ" + ಅನ್ ಲಾಕ್ ಅಗತ್ಯವಿದೆ ಭದ್ರತೆ ಸೂಚನೆಗಳನ್ನು ನಿರ್ವಹಿಸಿ ನಿರ್ಗಮನವನ್ನು ಖಚಿತಪಡಿಸಿ @@ -281,8 +281,8 @@ ಚಾಲ್ತಿಯಿರುವ ಮಾಂಗಾವನ್ನು ಮಾತ್ರ ನವೀಕರಿಸಿ ಚಾರ್ಜಿಂಗ್ ಷರತ್ತುಗಳನ್ನು ಪೂರೈಸಿದಾಗ ಮಾತ್ರ ನವೀಕರಿಸಿ - ಗ್ರಂಥಾಲಯ ನವೀಕರಣ ನಿರ್ಬಂಧಗಳು - ಗ್ರಂಥಾಲಯ ನವೀಕರಣ ಪಾಳಿ + ನವೀಕರಣ ನಿರ್ಬಂಧಗಳು + ನವೀಕರಣ ಪಾಳಿ ವಾರಕ್ಕೊಮ್ಮೆ ಪ್ರತಿ 2 ದಿನಗಳಿಗೊಮ್ಮೆ ಪ್ರತಿದಿನ @@ -292,7 +292,7 @@ ಪ್ರತಿ 2 ಗಂಟೆಗಳಿಗೊಮ್ಮೆ ಗಂಟೆ ಸ್ವಂತ ಮಾಡು - ಗ್ರಂಥಾಲಯ ನವೀಕರಣ ಆವರ್ತನ + ನವೀಕರಣ ಆವರ್ತನ ನವೀಕರಣಗಳು ಡೀಫಾಲ್ಟ್ ಈ ಮೂಲದ ಬಳಕೆಗೆ ಲಾಗಿನ್ ಆಗುವ ಅಗತ್ಯವಿದೆ @@ -607,7 +607,7 @@ ಟ್ರ್ಯಾಕರ್ ಗಳು ಲಾಗಿನ್ ಆಗಿಲ್ಲ: ಬುಕ್ ಮಾರ್ಕ್ ಮಾಡಿದ ಅಧ್ಯಾಯಗಳನ್ನು ಅಳಿಸಿ ಅಧ್ಯಾಯಗಳನ್ನು ಅಳಿಸಿ - 18+ ವಿಷಯವನ್ನು ಹೊಂದಿರಬಹುದು + NSFW (18+) ವಿಷಯವನ್ನು ಹೊಂದಿರಬಹುದು 18+ ಸ್ಕ್ರಾಲ್‌ ಮಾಡಿದಾಗ ಕೆಳಗಿನ ಪಟ್ಟಿಯನ್ನು ಮರೆಮಾಡಿ ಸಂಯೋಜನೆಗಳಲ್ಲಿ ಹುಡುಕಿ @@ -622,7 +622,8 @@ ಇತಿಹಾಸವನ್ನು ತೆರವುಗೊಳಿಸಿ ನೀವು ಖಚಿತವಾಗಿರುವಿರಾ\? ಎಲ್ಲಾ ಇತಿಹಾಸವೂ ಕಳೆದುಹೋಗುತ್ತದೆ. ಇತಿಹಾಸವನ್ನು ಅಳಿಸಲಾಗಿದೆ - ಅಮಾನ್ಯ ಬ್ಯಾಕಪ್ ಫೈಲ್:%1$s + ಅಮಾನ್ಯ ಬ್ಯಾಕಪ್ ಫೈಲ್ ಪ್ರಕಾರ:%1$s +\nಫೈಲ್ ಪ್ರಕಾರ .proto.gz ಅಥವಾ .json ನೊಂದಿಗೆ ಕೊನೆಗೊಳ್ಳಬೇಕು. ಹಳೆಯ ಪ್ರಕಾರದ ಬ್ಯಾಕಪ್ ಅನ್ನು ಸಹ ರಚಿಸಿ ತಚಿಯೋಮಿಯ ಹಳೆಯ ಆವೃತ್ತಿಗಳಲ್ಲಿ ಬಳಸಬಹುದು ಹಳೆಯ ಪ್ರಕಾರದ ಬ್ಯಾಕಪ್ ರಚಿಸಿ @@ -635,8 +636,41 @@ ಪ್ರತಿ ೪ ಗಂಟೆಗೆ ಮೊದಲು ಚಿಕ್ಕದು ಮೊದಲ ಸಾಣ್ಣದ್ದು - ಅಧ್ಯಾಯ ಸಾಂಖ್ಯಯಂತೆ - ದಿನಾಂಕ ದಂತೆ - "ವಸ್ತುವಿನ ಸಾಂಖ್ಯ ತೋರಿಸಿ" - ಪಡೆಯುಲಾದ ಮಾಹಿತಿ + ಅಧ್ಯಾಯ ಸಂಖ್ಯೆಯಿಂದ + ಅಪ್ಲೋಡ್ ದಿನಾಂಕ ದಂತೆ + ವಸ್ತುಗಳ ಸಂಖ್ಯೆಯನ್ನು ತೋರಿಸಿ + ಸಿಕ್ಕ ಮಾಹಿತಿ + ಕ್ರ್ಯಾಶ್ ಲಾಗ್ ಗಳು + ಓದಿ ಮುಗಿಸಿದ ದಿನಾಂಕ + ಓದಲು ಪ್ರಾರಂಭಿಸಿದ ದಿನಾಂಕ + ಕ್ರ್ಯಾಶ್ ಲಾಗ್‌ಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ + ಡೆವಲಪರ್‌ಗಳೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಲು ದೋಷದೆ ಲಾಗ್‌ಗಳನ್ನು ಫೈಲ್‌ಗೆ ಸೇರಿಸಿ + ಕ್ರ್ಯಾಶ್ ಲಾಗ್‌ಗಳನ್ನು ಡಂಪ್ ಮಾಡಿ + HTTPS ಮೇಲೆ DNS ಬಳಸಿ + ಬ್ಯಾಕಪ್ ಫೈಲ್‌ನಿಂದ ಡೇಟಾವನ್ನು ಮರುಸ್ಥಾಪಿಸಲಾಗುತ್ತದೆ. +\n +\nಕಾಣೆಯಾದ ಯಾವುದೇ ವಿಸ್ತರಣೆಗಳನ್ನು ನೀವು ಪುನಃ ಸ್ಥಾಪಿಸಬೇಕಾಗುತ್ತದೆ ಮತ್ತು ಅವುಗಳನ್ನು ಬಳಸಲು ಟ್ರ್ಯಾಕಿಂಗ್ ಸೇವೆಗಳಿಗೆ ಲಾಗ್ ಇನ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ. + ಹೊರಗಿಡಲಾದ ವಿಭಾಗಗಳಲ್ಲಿ ಮಾಂಗಾವನ್ನು ಸೇರಿಸಿದ ವಿಭಾಗಗಳಲ್ಲಿದ್ದರೂ ಅವುಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುವುದಿಲ್ಲ. + ಸ್ವಯಂ ಡೌನ್‌ಲೋಡ್ + ನ್ಯಾವಿಗೇಶನ್ ಲೇಔಟ್ + ಬಲಕ್ಕೆ + ಎಡಕ್ಕೆ + ಮುಂದಿನ + ಹಿಂದಿನ + ಬಲ ಮತ್ತು ಎಡ + ಅಂಚು + ಕಿಂಡಲ್-ಇಶ್ + L ಆಕಾರದ + ಪೂರ್ವನಿಯೋಜಿತ + ಡ್ಯುಯಲ್ ಪೇಜ್ ಸ್ಪ್ಲಿಟ್ ನ ಪ್ಲೇಸ್ ಮೆಂಟ್ ಓದುವ ದಿಕ್ಕಿಗೆ ಹೊಂದಿಕೆಯಾಗದಿದ್ದರೆ + ಡ್ಯುಯಲ್ ಪೇಜ್ ಸ್ಪ್ಲಿಟ್ ಪ್ಲೇಸ್‌ಮೆಂಟ್ ಅನ್ನು ತಿರುಗಿಸಿ + ಡ್ಯುಯಲ್ ಪುಟ ವಿಭಜನೆ + ರೀಡರ್ ತೆರೆದಾಗ ಟ್ಯಾಪ್ ವಲಯಗಳನ್ನು ತೋರಿಸಿ + ನ್ಯಾವಿಗೇಶನ್ ಲೇಔಟ್ ಓವರ್ ಲೇ ತೋರಿಸಿ + ಹೊರಗಿಡಿ: %s + ಸೇರಿಸಿ: %s + ಯಾವುದು ಅಲ್ಲ + ಹೊರಗಿಡಲಾದ ವರ್ಗಗಳಲ್ಲಿನ ಮಾಂಗಾ ಸೇರಿಸಿದ ವಿಭಾಗಗಳಲ್ಲಿದ್ದರೂ ನವೀಕರಿಸಲಾಗುವುದಿಲ್ಲ. + ಅಳತೆಯಿಲ್ಲದ ನೆಟ್‌ವರ್ಕ್ + ದೋಷಗಳನ್ನು ತೋರಿಸು \ No newline at end of file diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 977965403..dc26f8e4f 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -101,7 +101,7 @@ Kemas kini bab selepas dibaca Skrin permulaan Bahasa - Lalai + Sistem asal Kategori lalai Sentiasa tanya Skrin penuh @@ -152,7 +152,7 @@ Selepas ditandakan sebagai dibaca secara manual Setelah membaca Lokasi tersuai - Dinyahaktifkan + Di nyahkan Bab terakhir dibaca Bab kedua terakhir Bab ketiga terakhir @@ -212,7 +212,7 @@ Sumber lokal Lain Lalai tidak boleh dipilih bersama kategori lain - Manga ini telah ditambahkan ke pustaka anda + Ditambah ke pustaka Carian keseluruhan… Terkini Semak imbas @@ -232,7 +232,7 @@ Muat turun dalam progres Memuat turun (%1$d/%2$d) Ralat - Dihenti sebentar + Ditangguh Tidak berhasil mendapatkan bab Tajuk sumber Nombor bab @@ -244,7 +244,7 @@ 5 bab seterusnya 10 bab seterusnya Semua - Belum dibaca + Muat turun yang belum di baca Adakah anda pasti ingin memadamkan bab yang dipilih\? Penjejakan Sedang baca @@ -274,7 +274,7 @@ Bab %1$s Bab seterusnya tidak dijumpai Bab sebelumnya tidak dijumpai - Imej tidak dapat dimuatkan + Imej tidak dapat di muatkan Guna imej ini sebagai muka hadapan\? Memuat turun bab tidak berjaya. Anda boleh mencuba lagi di bahagian muat turun Progres kemas kini: %1$d/%2$d @@ -319,7 +319,7 @@ Kemaskini Pasang Masih menunggu - Muat turun dalam progres + Menuat turun Memasang Dipasang Dipercayai @@ -391,9 +391,9 @@ Log keluar daripada %1$s\? Log keluar Anda telah log keluar - Sedang baca + Sedang di baca Ditangguh - Ingin baca + Hendak di baca Lain-lain Bab terkini Buka bab @@ -408,7 +408,7 @@ AMOLED Uruskan pemberitahuan Keselamatan - Memerlukan buka kunci + Kunci dengan biometrik Kunci apabila terbiar Selalu Tidak @@ -474,8 +474,8 @@ Tambah penjejakan Tutup Buka - Dalam pustaka - Tambah ke pustaka + Dalam Pustaka + Tambah ke Pustaka Disematkan Lesen perisian sumber terbuka Laman web @@ -660,4 +660,7 @@ Manga di dalam kategori berkecuali tidak akan dikemaskini walaupun ianya ada di dalam kategori hanya. Manga di dalam kategori berkecuali tidak akan dimuat turun walaupun ianya ada di dalam kategori hanya. Muat turun automatik + Tunjuk ralat + Versi Android ini tidak lagi disokong + Gagal menyalin ke papan keratan \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 799e65cdb..2ead141cb 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -122,8 +122,8 @@ Sporing Stående Liggende - Frekvens for oppdatering av bibliotek - Restriksjoner for oppdatering av bibliotek + Oppdateringsfrekvens + Oppdateringsrestriksjoner Kun oppdater når disse vilkårene oppfylles Kun oppdater pågående manga Oppdater kapittelfremdrift etter lesing @@ -376,7 +376,7 @@ Multiplisere Unngå / lysne Brenn / mørkere - Rekkefølge for biblioteksoppdatering + Oppdateringsrekkefølge Resultatløst Velg en kilde å migrere fra Tilbake @@ -387,7 +387,7 @@ Foreldet Denne utvidelsen er ikke lenger tilgjengelig. Datoformat - Oppdateringer + Global oppdatering Logg ut fra %1$s\? Logg ut Du er utlogget @@ -669,4 +669,8 @@ Hver 8 time Hver 4 time Dato hentet + Manga i utelukkede kategorier vil ikke bli nedlastet selv om de også er i inkluderte kategorier. + Last ned automatisk + Manga i utelukkede kategorier vil ikke bli oppdatert selv om de også er i inkluderte kategorier. + Vis feil \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 95045e903..a1ff18f65 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -672,4 +672,5 @@ Manga in uitgesloten categorieën worden niet bijgewerkt, zelfs niet als ze onder opgenomen categorieën vallen. Datum opgehaald Geen + Fouten weergeven \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 5240fae57..0d3fb8845 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -641,7 +641,7 @@ Data de início da leitura Registros de travamento salvos Salva os registros de erro em um arquivo para o compartilhamento com os desenvolvedores - Limpar os registros de travamentos + Exportar os registros de travamentos Rede ilimitada Decrescente Crescente @@ -672,4 +672,7 @@ Os mangás nas categorias excluídas não serão atualizados mesmo que eles também estejam nas categorias incluídas. Os mangás nas categorias excluídas não serão disponibilizados offline mesmo que eles também estejam nas categorias incluídas. Disponibilizar offline automaticamente + Mostrar erros + Esta versão do Android não é mais suportada + Erro ao copiar para a área de transferência \ 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 8fa6189de..7a6645971 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -59,7 +59,7 @@ Retrato Paisagem Padrão - Frequência de atualização da biblioteca + Frequência de atualização Manual Hora à hora A cada 2 horas @@ -69,7 +69,7 @@ Diariamente A cada 2 dias Tudo - Restrições sobre a atualização da biblioteca + Restrições sobre a atualização Atualizar apenas quando se cumprem as condições A carregar Atualizar apenas mangás a decorrer @@ -181,7 +181,7 @@ Por fonte Por número de capítulo Transferir - Próximo capítulo + Capítulo seguinte Próximos 5 capítulos Próximos 10 capítulos Tudo @@ -406,7 +406,7 @@ Modo de mistura do filtro de cores Sub-exposição / Clarear Ajuda - Ordem de atualização da biblioteca + Ordem de atualização Nenhum resultado encontrado Selecione uma fonte da qual migrar Voltar @@ -417,7 +417,7 @@ Obsoleto Esta extensão já não está disponível. Formato da data - Atualizações + Atualização global Terminar sessão em %1$s\? Terminar sessão Sua sessão está agora encerrada @@ -438,7 +438,7 @@ Preto AMOLED Gerir notificações Segurança - Bloqueio com biometria + Requerer desbloqueio Bloquear automaticamente Sempre Nunca @@ -690,8 +690,19 @@ Esquerda Seguinte Anterior - Brevemente mostrar zonas de toque quando o leitor é aberto + Mostrar zonas de toque quando o leitor é aberto Mostrar sobreposição da disposição de navegação A cada 8 horas A cada 4 horas + Nenhum + DNS por HTTPS + Transferir automaticamente + Esta versão do Android não é mais suportada + Falha ao copiar para a área de transferência + Mangá nas categorias excluídas não será transferida mesmo que também esteja em categorias incluídas. + Excluir: %s + Incluir: %s + Mangá em categorias excluídas não será atualizada mesmo que também estejam nas categorias incluídas. + Mostrar erros + Data de procura \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 5e8a63ca1..f3b7f3a82 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -83,7 +83,7 @@ Elemente pe rând Portret Prestabilit - Frecvența actualizării bibliotecii + Frecvență de actualizare Manual La fiecare oră La fiecare 2 ore @@ -94,7 +94,7 @@ La fiecare 2 zile Săptămânal Toate - Restricții actualizare bibliotecă + Restricții de actualizare Actualizează doar când condițiile sunt împlinite Se încarcă Actualizați doar manga în desfășurare @@ -108,7 +108,7 @@ Actualizează Instalează În așteptare - În curs de descărcare + Se descarcă În curs de instalare Instalată Ai încredere @@ -275,7 +275,7 @@ După numărul capitolului Descarcă Descarcă cantitate personalizată - Următorul capitol + Capitolul următor Următoarele 5 capitole Următoarele 10 capitole Personalizat @@ -286,7 +286,7 @@ Citind Terminată Abandonată - In așteptare + În așteptare Planificat pentru citit Recitind Scor @@ -305,10 +305,10 @@ Imaginea se salvează Opțiuni Filtru personalizat - Stabilește că și copertă + Stabilește ca și copertă Copertă actualizată Pagină copiată în %1$s - Se descărcă… + Se descarcă… Descărcat %1$d%% Pagina: %1$d Capitolul %1$s @@ -376,7 +376,7 @@ Treci peste capitolele marcate ca Citit Dialog prin apăsare lunga Ajutor - Ordinea actualizării bibliotecii + Ordinea actualizărilor Nici un rezultat găsit Selectați o sursă din care să migrați Înapoi @@ -387,7 +387,7 @@ Învechit Această extensie nu mai este disponibilă. Formatul datei - Actualizări + Actualizare globală Deconectează-te de la %1$s\? Deconectează-te Acum ești deconectat @@ -407,7 +407,7 @@ Negru AMOLED Setări notificări Securitate - Blocare cu amprentă + Necesită deblocare Deblochează Tachiyomi Ultimul capitol Blocați când este inactiv @@ -525,7 +525,7 @@ Imposibil de deschis setările dispozitivului - Gata în %1$s cu %2$s eroare + Gata în %1$s cu eroarea %2$s Gata în %1$s cu %2$s erori Gata în %1$s cu %2$s erori @@ -631,7 +631,8 @@ Pagina următoare Pagina anterioară Ghid de migrare a sursei - Fișierul copiei de rezervă este nevalid: %1$s + Tip de fișier de restaurare nevalid: %1$s +\nAr trebui să se termine cu .proto.gz sau .json. De asemenea, creați copii de rezervă de moștenire Poate fi utilizat în versiuni mai vechi de Tachiyomi Crează copie de rezervă de moștenire @@ -644,7 +645,7 @@ Datele de conectare la site-ul MAL nu au fost găsite Inversează plasamentul paginilor duble despărțite împărțirea paginilor duble - Afișați scurt zonele de atingere când cititorul este deschis + Afișați zonele de atingere atunci când cititorul este deschis Afișați suprapunerea aspectului de navigare Rețea nemăsurată O dată la 8 ore @@ -653,4 +654,37 @@ Ascendent După numărul capitolului După data postării + Excludeți: %s + Includeți: %s + Manga din categoriile excluse nu vor fi actualizate, chiar dacă se află și în categoriile incluse. + Afișați erorile + Afișare număr de elemente + Nici unul + DNS peste HTTPS + Jurnale de erori fatale + Data încheierii citirii + Data începerii citirii + Jurnalele de erori fatale salvate + Aruncați jurnalele de erori fatale + Salvează jurnalele de erori într-un fișier pentru partajarea cu dezvoltatorii + Aspect de navigare + Margine + Similar unui Kindle + În formă de L + Implicită + Data preluată + Urmărit + Datele din fișierul de rezervă vor fi restaurate. +\n +\nVa trebui să instalați toate extensiile lipsă și să vă conectați ulterior la serviciile de urmărire pentru a le utiliza. + Manga din categoriile excluse nu vor fi descărcate, chiar dacă se află și în categoriile incluse. + Descărcare automată + Dreapta + Stânga + Următorul + Anterior + Dreapta și stânga + Dacă amplasarea divizării duble a paginii nu corespunde cu direcția de citire + Această versiune Android nu mai este suportată + Nu s-a reușit copierea în clipboard \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 5f2def926..1f2a07cba 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -99,13 +99,13 @@ Не могу скачать главу из-за непревиденной ошибки Загружен(о) %1$d%% Невозможно скачать главы. Можете попробовать еще раз в разделе загрузок - Не прочитано - Загрузка… + Непрочитано + Загружается… Заброшено Категория с таким именем уже существует! Не могу получить главы Пятая от прочитанной главы - Выбрать файл бэкапа + Выбрать файл резервной копии Выбрать обложку Четвёртая от прочитанной главы Ваша библиотека пуста. Добавьте тайтлы в библиотеку из Поиска. @@ -262,7 +262,7 @@ Восстановить Отменить Манга была добавлена в библиотеку - Что вы хотите бэкапить\? + Что вы хотите резервировать\? Резевная копия создана При восстановлении используются данные из источников, что может вызвать большой расход трафика. \n @@ -279,17 +279,17 @@ Локальная манга Манга Больше нет результатов - Каталог бэкапа - Частота бэкапов - Автоматические бэкапы - Максимальное количество бэкапов - Создать бэкап + Каталог резервной копии + Частота резервных копий + Автоматические резервные копии + Максимальное количество резервных копий + Создать резервную копию Можно использовать для восстановления текущей библиотеки Обрезать поля Обновить отслеживание Обновляет статус, оценку и последнюю прочитанную главу из сервисов отслеживания - Восстановить из бэкапа - Восстановить библиотеку из бэкапа + Восстановить из резервной копии + Восстановить библиотеку из резервной копии Восстановление завершено Восстановление из резервной копии Обновления @@ -449,7 +449,7 @@ Обновление библиотеки Проверить страницу в WebView Оптимизация батареи уже выключена - Помогает с фоновым обновлением библиотеки и бэкапом + Помогает с фоновым обновлением библиотеки и резевной копии Выключить оптимизацию батареи По умолчанию @@ -506,10 +506,10 @@ Фильтрует всю мангу в вашей библиотеке Проверить обновления Восстановление отменено - Ошибка восстановления из бэкапа + Ошибка восстановления из резервной копии Восстановление уже выполняется - Ошибка бэкапа - Бэкап уже выполняется + Не удалось создать резервную копию + Резервная копия уже выполняется %02d мин, %02d сек Включать только закрепленные источники @@ -605,7 +605,7 @@ У вас нет закрепленных источников Загрузка завершена - Бэкап и восстановление + Резервирование и восстановление Загрузки Завершение Прогресс @@ -673,8 +673,8 @@ Показать число объектов Справа и слева Двухстраничное разделение - Инвертировать двухстраничное размещение разделения - Если двухстраничное размещение разделения не соответствует направлению чтения + Инвертировать двухстраничное разделение + Если двухстраничное разделение не соответствует направлению чтения Данные из файла резевной копии будут восстановлены. \n \nВам будет нужно установить все недостающие расширения, и после этого войти в сервисы отслеживания для их использования. @@ -685,7 +685,7 @@ Следующая Предыдущая Показывать зоны касания когда читалка открыта - Показывать наложение схемы навигации + Показывать наложение макета навигации DNS по HTTPS Нет Исключать: %s @@ -694,4 +694,7 @@ Манга в исключенных категориях не будет загруженна, даже если она также находится во включенных категориях. Манга в исключенных категориях не будет обновляться, даже если она также находится во включенных категориях. Автозагрузка + Показать ошибки + Эта версия Андроида больше не поддерживается + Не удалось скопировать в буфер обмена \ No newline at end of file diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 73cdc4c4e..3f90ffc18 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -87,7 +87,7 @@ Orientamentu verticale Orientamentu orizontale Predefinidu - Frecuèntzia de agiornamentu de sa biblioteca + Frecuèntzia de agiornamentu Manuale Cada ora Cada 2 oras @@ -98,7 +98,7 @@ Cada 2 dies Cada chida Totus - Restritziones a s\'agiornamentu de sa biblioteca + Restritziones a s\'agiornamentu Agiorna petzi cando sas cunditziones benint rispetadas Carrighende Agiorna sos manga in cursu ebbia @@ -376,7 +376,7 @@ Istransi / Acrari Brùsia / Iscuri Agiudu - Òrdine de agiornamentu de sa biblioteca + Òrdine de agiornamentu Perunu resultadu agadadu Ischerta una mitza dae sa cale tramudare In segus @@ -387,7 +387,7 @@ Obsoleta Cust\'estensione no est prus a disponimentu. Formadu de sa data - Agiornamentos + Agiornamentu globale Essire dae %1$s\? Essi Ses essidu @@ -669,4 +669,10 @@ Include: %s Perunu Data recuperada - + Sos manga in sas categorias esclùdidas non s\'ant a iscarrigare nemmancu si sunt fintzas in categorias inclùdidas. + Iscarrigamentu automàticu + Sos manga in sas categorias esclùdidas non s\'ant a agiornare nemmancu si sunt fintzas in categorias inclùdidas. + Ammustra sos errores + Còpia in punta de billete fallida + Custa versione de Android no est prus suportada + \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 22c074fe7..cbe3fa278 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -672,4 +672,7 @@ Manga i uteslutna kategorier laddas inte ner även om de också ingår i inkluderade kategorier. Ladda ned automatiskt Manga i uteslutna kategorier uppdateras inte även om de också ingår i inkluderade kategorier. + Visa fel + Denna Android-version stöds inte längre + Kunde inte kopiera till urklipp \ 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 b66c3ab02..ce0310ccd 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -318,7 +318,7 @@ Bu görsel kapak resmi olarak kullanılsın mı\? Taşınılacak kaynağı seçmek için dokunun Eklenecek veriyi seç - Taşın + Geçiş yap Kopyala Bölümler indirilemedi. İndirmeler bölümünden yeniden deneyebilirsiniz Güncelleme ilerlemesi: %1$d/%2$d @@ -672,4 +672,7 @@ Hariç tutulan kategorilerdeki manga, dahil edilen kategorilerde olsa bile indirilmeyecektir. Otomatik indir Hariç tutulan kategorilerdeki manga, dahil edilen kategorilerde olsa bile güncellenmeyecektir. + Hataları göster + Bu Android sürümü artık desteklenmiyor + Panoya kopyalanamadı \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 35c3accdc..2c25465ef 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -692,4 +692,5 @@ Ніхто Мангу в виключених категоріях не буде оновлено, навіть якщо вона також знаходиться у включених категоріях. Дата отримання + Показати помилки \ No newline at end of file diff --git a/app/src/main/res/values-v23/themes.xml b/app/src/main/res/values-v23/themes.xml index 73607f42f..49f3e5ea6 100644 --- a/app/src/main/res/values-v23/themes.xml +++ b/app/src/main/res/values-v23/themes.xml @@ -1,9 +1,9 @@ - - + + - - - @@ -145,6 +142,7 @@ ?attr/colorPrimary ?attr/colorPrimary + @null false true false @@ -184,16 +182,15 @@ @color/filterColorDark - + -