Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae57561591 | ||
|
|
2379efc191 | ||
|
|
4252ebd24d | ||
|
|
cd0575a524 | ||
|
|
6eb2608f88 | ||
|
|
39e21ff93c | ||
|
|
5ec2eab6b8 | ||
|
|
850f6c2f3e | ||
|
|
ec53eb9c70 | ||
|
|
cdd76f723f | ||
|
|
a37e8825b0 | ||
|
|
2450544454 | ||
|
|
f6a510653e | ||
|
|
5990da587c | ||
|
|
91e3d2f5db | ||
|
|
971c683746 | ||
|
|
15e9aaab26 | ||
|
|
12047a85c7 | ||
|
|
48808f8a7d | ||
|
|
18e573b6b8 | ||
|
|
c80dc08d6c | ||
|
|
61b5b8aa73 | ||
|
|
5e79809326 | ||
|
|
dcbd7c2117 | ||
|
|
1e134b109a | ||
|
|
7f9b6a67af | ||
|
|
79448bb01d | ||
|
|
b94f9e4b01 | ||
|
|
ae8b48d733 | ||
|
|
313013dccd | ||
|
|
c36d23ec06 | ||
|
|
ebe71476d1 | ||
|
|
ca2ae9bc83 | ||
|
|
f2898aba85 | ||
|
|
1b73f19ae1 | ||
|
|
4b4aea0410 | ||
|
|
b9f8e3978a | ||
|
|
89eed10508 | ||
|
|
799c0910ea | ||
|
|
ed6802344a | ||
|
|
2d94688742 | ||
|
|
d0b44050f5 | ||
|
|
486ae12d41 | ||
|
|
cb36f085d7 | ||
|
|
5af42e5b6b | ||
|
|
160fa2c001 | ||
|
|
f9ba87b8cf | ||
|
|
ee3cf08545 | ||
|
|
e0497b357b | ||
|
|
6de08cd4fe | ||
|
|
4760f1ea35 | ||
|
|
c64c4643bf | ||
|
|
7bfcdb387c | ||
|
|
503fcf65fb | ||
|
|
54fb79dc98 | ||
|
|
ea4c048029 | ||
|
|
badc826cd3 | ||
|
|
f5ece8124e | ||
|
|
accdc41d6c | ||
|
|
819730984e | ||
|
|
01c404f9e5 | ||
|
|
1fad686733 | ||
|
|
396be6008d | ||
|
|
42f7846167 | ||
|
|
dca56a43ee | ||
|
|
cc91e56e1b | ||
|
|
627cf73d72 | ||
|
|
514870f71c | ||
|
|
adffa800e8 |
@@ -1,11 +1,12 @@
|
||||
## Kotatsu contribution guidelines
|
||||
|
||||
- If you want to fix bug or implement a new feature, that already mention in the [issues](https://github.com/KotatsuApp/Kotatsu/issues), please, assign this issue to you and/or comment about it.
|
||||
- Whether you have to implement new feature, please, open an issue or discussion regarding it to ensure it will be accepted.
|
||||
- Translations have to be managed using the [Weblate](https://hosted.weblate.org/engage/kotatsu/) platform.
|
||||
- In case you want to add a new manga source, refer to the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers).
|
||||
+ If you want to **fix bugs** or **implement new features** that **already have an [issue card](https://github.com/KotatsuApp/Kotatsu/issues):** please assign this issue to you and/or comment about it.
|
||||
+ If you want to **implement a new feature:** open an issue or discussion regarding it to ensure it will be accepted.
|
||||
+ **Translations** have to be managed using the [Weblate](https://hosted.weblate.org/engage/kotatsu/) platform.
|
||||
+ In case you want to **add a new manga source,** refer to the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers).
|
||||
|
||||
Refactoring or some dev-faces improvements are also might be accepted, however please stick to the following principles:
|
||||
- Performance matters. In the case of choosing between source code beauty and performance, performance should be a priority.
|
||||
- Please, do not modify readme and other information files (except for typos).
|
||||
- Avoid adding new dependencies unless required. APK size is important.
|
||||
**Refactoring** or some **dev-faces improvements** might also be accepted. However, please stick to the following principles:
|
||||
|
||||
+ **Performance matters.** In the case of choosing between source code beauty and performance, performance should be a priority.
|
||||
+ Please, **do not modify readme and other information files** (except for typos).
|
||||
+ **Avoid adding new dependencies** unless required. APK size is important.
|
||||
|
||||
@@ -16,8 +16,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 611
|
||||
versionName = '6.6'
|
||||
versionCode = 617
|
||||
versionName = '6.6.7'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||
ksp {
|
||||
@@ -82,7 +82,7 @@ afterEvaluate {
|
||||
}
|
||||
dependencies {
|
||||
//noinspection GradleDependency
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:e03d0efe71') {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:57c9d26916') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
@@ -94,9 +94,9 @@ dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.activity:activity-ktx:1.8.2'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.6.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-service:2.6.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.6.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
@@ -104,7 +104,7 @@ dependencies {
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.6.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.7.0'
|
||||
|
||||
implementation 'androidx.work:work-runtime:2.9.0'
|
||||
//noinspection GradleDependency
|
||||
@@ -138,8 +138,12 @@ dependencies {
|
||||
|
||||
implementation 'ch.acra:acra-http:5.11.3'
|
||||
implementation 'ch.acra:acra-dialog:5.11.3'
|
||||
compileOnly 'com.google.auto.service:auto-service-annotations:1.1.1'
|
||||
ksp 'dev.zacsweers.autoservice:auto-service-ksp:1.1.0'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
|
||||
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.json:json:20231013'
|
||||
|
||||
@@ -36,7 +36,7 @@ class KotatsuApp : BaseApp() {
|
||||
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
|
||||
.penaltyDeath()
|
||||
.detectFragmentReuse()
|
||||
// .detectWrongFragmentContainer() FIXME: migrate to ViewPager2
|
||||
.detectWrongFragmentContainer()
|
||||
.detectRetainInstanceUsage()
|
||||
.detectSetUserVisibleHint()
|
||||
.detectFragmentTagUsage()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
@@ -19,6 +20,7 @@ import org.acra.config.httpSender
|
||||
import org.acra.data.StringFormat
|
||||
import org.acra.ktx.initAcra
|
||||
import org.acra.sender.HttpSender
|
||||
import org.conscrypt.Conscrypt
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
@@ -27,6 +29,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.WorkServiceStopHelper
|
||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.settings.work.WorkScheduleManager
|
||||
import java.security.Security
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
@@ -66,6 +69,10 @@ open class BaseApp : Application(), Configuration.Provider {
|
||||
super.onCreate()
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
AppCompatDelegate.setApplicationLocales(settings.appLocales)
|
||||
// TLS 1.3 support for Android < 10
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||
}
|
||||
setupActivityLifecycleCallbacks()
|
||||
processLifecycleScope.launch {
|
||||
val isOriginalApp = withContext(Dispatchers.Default) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.koitharu.kotatsu.core
|
||||
|
||||
import android.content.Context
|
||||
import com.google.auto.service.AutoService
|
||||
import org.acra.builder.ReportBuilder
|
||||
import org.acra.config.CoreConfiguration
|
||||
import org.acra.config.ReportingAdministrator
|
||||
|
||||
@AutoService(ReportingAdministrator::class)
|
||||
class ErrorReportingAdmin : ReportingAdministrator {
|
||||
|
||||
override fun shouldStartCollecting(
|
||||
context: Context,
|
||||
config: CoreConfiguration,
|
||||
reportBuilder: ReportBuilder
|
||||
): Boolean {
|
||||
return reportBuilder.exception?.isDeadOs() != true
|
||||
}
|
||||
|
||||
private fun Throwable.isDeadOs(): Boolean {
|
||||
val className = javaClass.simpleName
|
||||
return className == "DeadSystemException" || className == "DeadSystemRuntimeException" || cause?.isDeadOs() == true
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,8 @@ data class ParcelableChapter(
|
||||
MangaChapter(
|
||||
id = parcel.readLong(),
|
||||
name = parcel.readString().orEmpty(),
|
||||
number = parcel.readInt(),
|
||||
number = parcel.readFloat(),
|
||||
volume = parcel.readInt(),
|
||||
url = parcel.readString().orEmpty(),
|
||||
scanlator = parcel.readString(),
|
||||
uploadDate = parcel.readLong(),
|
||||
@@ -31,7 +32,8 @@ data class ParcelableChapter(
|
||||
override fun ParcelableChapter.write(parcel: Parcel, flags: Int) = with(chapter) {
|
||||
parcel.writeLong(id)
|
||||
parcel.writeString(name)
|
||||
parcel.writeInt(number)
|
||||
parcel.writeFloat(number)
|
||||
parcel.writeInt(volume)
|
||||
parcel.writeString(url)
|
||||
parcel.writeString(scanlator)
|
||||
parcel.writeLong(uploadDate)
|
||||
|
||||
@@ -347,9 +347,13 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
|
||||
|
||||
var historySortOrder: ListSortOrder
|
||||
get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.UPDATED)
|
||||
get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.LAST_READ)
|
||||
set(value) = prefs.edit { putEnumValue(KEY_HISTORY_ORDER, value) }
|
||||
|
||||
var allFavoritesSortOrder: ListSortOrder
|
||||
get() = prefs.getEnumValue(KEY_FAVORITES_ORDER, ListSortOrder.NEWEST)
|
||||
set(value) = prefs.edit { putEnumValue(KEY_FAVORITES_ORDER, value) }
|
||||
|
||||
val isRelatedMangaEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_RELATED_MANGA, true)
|
||||
|
||||
@@ -530,6 +534,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_READER_OPTIMIZE = "reader_optimize"
|
||||
const val KEY_LOCAL_LIST_ORDER = "local_order"
|
||||
const val KEY_HISTORY_ORDER = "history_order"
|
||||
const val KEY_FAVORITES_ORDER = "fav_order"
|
||||
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
|
||||
const val KEY_PREFETCH_CONTENT = "prefetch_content"
|
||||
const val KEY_APP_LOCALE = "app_locale"
|
||||
|
||||
@@ -4,8 +4,10 @@ import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.core.view.ancestors
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class FastScrollRecyclerView @JvmOverloads constructor(
|
||||
@@ -15,6 +17,16 @@ class FastScrollRecyclerView @JvmOverloads constructor(
|
||||
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||
|
||||
val fastScroller = FastScroller(context, attrs)
|
||||
var isVP2BugWorkaroundEnabled = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (value && isAttachedToWindow) {
|
||||
checkIfInVP2()
|
||||
} else if (!value) {
|
||||
applyVP2Workaround = false
|
||||
}
|
||||
}
|
||||
private var applyVP2Workaround = false
|
||||
|
||||
var isFastScrollerEnabled: Boolean = true
|
||||
set(value) {
|
||||
@@ -43,10 +55,29 @@ class FastScrollRecyclerView @JvmOverloads constructor(
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
fastScroller.attachRecyclerView(this)
|
||||
if (isVP2BugWorkaroundEnabled) {
|
||||
checkIfInVP2()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
fastScroller.detachRecyclerView()
|
||||
super.onDetachedFromWindow()
|
||||
applyVP2Workaround = false
|
||||
}
|
||||
|
||||
override fun isLayoutRequested(): Boolean {
|
||||
return if (applyVP2Workaround) false else super.isLayoutRequested()
|
||||
}
|
||||
|
||||
override fun requestLayout() {
|
||||
super.requestLayout()
|
||||
if (applyVP2Workaround && parent?.isLayoutRequested == true) {
|
||||
parent?.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfInVP2() {
|
||||
applyVP2Workaround = ancestors.any { it is ViewPager2 } == true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.koitharu.kotatsu.core.ui.util
|
||||
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.view.MenuProvider
|
||||
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
|
||||
|
||||
class PopupMenuMediator(
|
||||
private val provider: MenuProvider,
|
||||
) : View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
|
||||
|
||||
override fun onLongClick(v: View): Boolean {
|
||||
val menu = PopupMenu(v.context, v)
|
||||
provider.onCreateMenu(menu.menu, menu.menuInflater)
|
||||
provider.onPrepareMenu(menu.menu)
|
||||
if (!menu.menu.hasVisibleItems()) {
|
||||
return false
|
||||
}
|
||||
menu.setOnMenuItemClickListener(this)
|
||||
menu.setOnDismissListener(this)
|
||||
menu.show()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
return provider.onMenuItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onDismiss(menu: PopupMenu) {
|
||||
provider.onMenuClosed(menu.menu)
|
||||
}
|
||||
|
||||
fun attach(view: View) {
|
||||
view.setOnLongClickListener(this)
|
||||
view.setOnContextClickListenerCompat(this)
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,11 @@ import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.annotation.Px
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.use
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
fun Context.getThemeDrawable(
|
||||
@AttrRes resId: Int,
|
||||
@@ -75,3 +77,7 @@ fun TypedArray.getDrawableCompat(context: Context, index: Int): Drawable? {
|
||||
val resId = getResourceId(index, 0)
|
||||
return if (resId != 0) ContextCompat.getDrawable(context, resId) else null
|
||||
}
|
||||
|
||||
@get:StyleRes
|
||||
val DIALOG_THEME_CENTERED: Int
|
||||
inline get() = materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered
|
||||
|
||||
@@ -15,7 +15,9 @@ import androidx.core.view.descendants
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
@@ -161,3 +163,12 @@ val Toolbar.menuView: ActionMenuView?
|
||||
menu // to call ensureMenu()
|
||||
return children.firstNotNullOfOrNull { it as? ActionMenuView }
|
||||
}
|
||||
|
||||
fun MaterialButton.setProgressIcon() {
|
||||
val progressDrawable = CircularProgressDrawable(context)
|
||||
progressDrawable.strokeWidth = resources.resolveDp(2f)
|
||||
progressDrawable.setColorSchemeColors(currentTextColor)
|
||||
progressDrawable.setTintList(textColors)
|
||||
icon = progressDrawable
|
||||
progressDrawable.start()
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@ import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import org.koitharu.kotatsu.core.ui.util.ActionModeListener
|
||||
import org.koitharu.kotatsu.core.util.ext.doOnExpansionsChanged
|
||||
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
|
||||
|
||||
class ChaptersBottomSheetMediator(
|
||||
private val behavior: BottomSheetBehavior<*>,
|
||||
private val pager: ViewPager2,
|
||||
private val tabLayout: TabLayout,
|
||||
) : OnBackPressedCallback(false),
|
||||
ActionModeListener,
|
||||
OnLayoutChangeListener, View.OnGenericMotionListener {
|
||||
@@ -75,8 +78,7 @@ class ChaptersBottomSheetMediator(
|
||||
|
||||
fun lock() {
|
||||
lockCounter++
|
||||
behavior.isDraggable = lockCounter <= 0
|
||||
pager.isUserInputEnabled = lockCounter <= 0
|
||||
updateLock()
|
||||
}
|
||||
|
||||
fun unlock() {
|
||||
@@ -84,7 +86,12 @@ class ChaptersBottomSheetMediator(
|
||||
if (lockCounter < 0) {
|
||||
lockCounter = 0
|
||||
}
|
||||
updateLock()
|
||||
}
|
||||
|
||||
private fun updateLock() {
|
||||
behavior.isDraggable = lockCounter <= 0
|
||||
pager.isUserInputEnabled = lockCounter <= 0
|
||||
tabLayout.setTabsEnabled(lockCounter <= 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ class DetailsActivity :
|
||||
|
||||
if (viewBinding.layoutBottom != null) {
|
||||
val behavior = BottomSheetBehavior.from(checkNotNull(viewBinding.layoutBottom))
|
||||
val bsMediator = ChaptersBottomSheetMediator(behavior, viewBinding.pager)
|
||||
val bsMediator = ChaptersBottomSheetMediator(behavior, viewBinding.pager, viewBinding.tabs)
|
||||
actionModeDelegate.addListener(bsMediator)
|
||||
checkNotNull(viewBinding.layoutBsHeader).addOnLayoutChangeListener(bsMediator)
|
||||
onBackPressedDispatcher.addCallback(bsMediator)
|
||||
@@ -363,8 +363,9 @@ class DetailsActivity :
|
||||
}
|
||||
|
||||
private fun initPager() {
|
||||
viewBinding.pager.recyclerView?.isNestedScrollingEnabled = false
|
||||
val adapter = DetailsPagerAdapter(this)
|
||||
viewBinding.pager.recyclerView?.isNestedScrollingEnabled = false
|
||||
viewBinding.pager.offscreenPageLimit = 1
|
||||
viewBinding.pager.adapter = adapter
|
||||
TabLayoutMediator(viewBinding.tabs, viewBinding.pager, adapter).attach()
|
||||
viewBinding.pager.setCurrentItem(settings.defaultDetailsTab, false)
|
||||
|
||||
@@ -59,6 +59,7 @@ class ChaptersFragment :
|
||||
with(binding.recyclerViewChapters) {
|
||||
checkNotNull(selectionController).attachToRecyclerView(this)
|
||||
setHasFixedSize(true)
|
||||
isNestedScrollingEnabled = false
|
||||
adapter = chaptersAdapter
|
||||
}
|
||||
viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged)
|
||||
@@ -83,6 +84,17 @@ class ChaptersFragment :
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
// required for BottomSheetBehavior
|
||||
requireViewBinding().recyclerViewChapters.isNestedScrollingEnabled = false
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
requireViewBinding().recyclerViewChapters.isNestedScrollingEnabled = true
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onItemClick(item: ChapterListItem, view: View) {
|
||||
if (selectionController?.onItemClick(item.chapter.id) == true) {
|
||||
return
|
||||
|
||||
@@ -88,6 +88,7 @@ class PagesFragment :
|
||||
addItemDecoration(TypedListSpacingDecoration(context, false))
|
||||
adapter = thumbnailsAdapter
|
||||
setHasFixedSize(true)
|
||||
isNestedScrollingEnabled = false
|
||||
addOnLayoutChangeListener(spanResolver)
|
||||
spanResolver?.setGridSize(settings.gridSize / 100f, this)
|
||||
addOnScrollListener(ScrollListener().also { scrollListener = it })
|
||||
@@ -112,6 +113,17 @@ class PagesFragment :
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
// required for BottomSheetBehavior
|
||||
requireViewBinding().recyclerView.isNestedScrollingEnabled = false
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
requireViewBinding().recyclerView.isNestedScrollingEnabled = true
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) = Unit
|
||||
|
||||
override fun onItemClick(item: PageThumbnail, view: View) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
|
||||
class DownloadsMenuProvider(
|
||||
@@ -41,10 +42,8 @@ class DownloadsMenuProvider(
|
||||
}
|
||||
|
||||
private fun confirmCancelAll() {
|
||||
MaterialAlertDialogBuilder(
|
||||
context,
|
||||
com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered,
|
||||
).setTitle(R.string.cancel_all)
|
||||
MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED)
|
||||
.setTitle(R.string.cancel_all)
|
||||
.setMessage(R.string.cancel_all_downloads_confirm)
|
||||
.setIcon(R.drawable.ic_cancel_multiple)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
@@ -54,10 +53,8 @@ class DownloadsMenuProvider(
|
||||
}
|
||||
|
||||
private fun confirmRemoveCompleted() {
|
||||
MaterialAlertDialogBuilder(
|
||||
context,
|
||||
com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered,
|
||||
).setTitle(R.string.remove_completed)
|
||||
MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED)
|
||||
.setTitle(R.string.remove_completed)
|
||||
.setMessage(R.string.remove_completed_downloads_confirm)
|
||||
.setIcon(R.drawable.ic_clear_all)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
|
||||
@@ -306,7 +306,7 @@ class DownloadsViewModel @Inject constructor(
|
||||
return chapters.mapNotNullTo(ArrayList(size)) {
|
||||
if (chapterIds == null || it.id in chapterIds) {
|
||||
DownloadChapter(
|
||||
number = it.number,
|
||||
number = it.number.toInt(),
|
||||
name = it.name,
|
||||
isDownloaded = it.id in localChapters,
|
||||
)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package org.koitharu.kotatsu.explore.ui.adapter
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.View
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -15,10 +13,9 @@ import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.setProgressIcon
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
|
||||
@@ -30,7 +27,6 @@ import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
|
||||
import org.koitharu.kotatsu.explore.ui.model.RecommendationsItem
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
fun exploreButtonsAD(
|
||||
clickListener: View.OnClickListener,
|
||||
@@ -45,16 +41,7 @@ fun exploreButtonsAD(
|
||||
|
||||
bind {
|
||||
if (item.isRandomLoading) {
|
||||
val icon = CircularProgressDrawable(context)
|
||||
icon.strokeWidth = context.resources.resolveDp(2f)
|
||||
icon.setColorSchemeColors(
|
||||
context.getThemeColor(
|
||||
materialR.attr.colorPrimary,
|
||||
Color.DKGRAY,
|
||||
),
|
||||
)
|
||||
binding.buttonRandom.icon = icon
|
||||
icon.start()
|
||||
binding.buttonRandom.setProgressIcon()
|
||||
} else {
|
||||
binding.buttonRandom.setIconResource(R.drawable.ic_dice)
|
||||
}
|
||||
|
||||
@@ -95,6 +95,22 @@ abstract class FavouritesDao {
|
||||
return findCoversImpl(query)
|
||||
}
|
||||
|
||||
suspend fun findCovers(order: ListSortOrder, limit: Int): List<Cover> {
|
||||
val orderBy = getOrderBy(order)
|
||||
|
||||
@Language("RoomSql")
|
||||
val query = SimpleSQLiteQuery(
|
||||
"SELECT manga.cover_url AS url, manga.source AS source FROM favourites " +
|
||||
"LEFT JOIN manga ON favourites.manga_id = manga.manga_id " +
|
||||
"WHERE deleted_at = 0 GROUP BY manga.manga_id ORDER BY $orderBy LIMIT ?",
|
||||
arrayOf<Any>(limit),
|
||||
)
|
||||
return findCoversImpl(query)
|
||||
}
|
||||
|
||||
@Query("SELECT COUNT(DISTINCT manga_id) FROM favourites WHERE deleted_at = 0")
|
||||
abstract fun observeMangaCount(): Flow<Int>
|
||||
|
||||
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM favourites WHERE deleted_at = 0)")
|
||||
abstract suspend fun findAllManga(): List<MangaEntity>
|
||||
|
||||
@@ -175,9 +191,10 @@ abstract class FavouritesDao {
|
||||
ListSortOrder.RATING -> "manga.rating DESC"
|
||||
ListSortOrder.NEWEST -> "favourites.created_at DESC"
|
||||
ListSortOrder.ALPHABETIC -> "manga.title ASC"
|
||||
ListSortOrder.NEW_CHAPTERS -> "(SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id) DESC"
|
||||
ListSortOrder.UPDATED, // for legacy support
|
||||
ListSortOrder.PROGRESS -> "(SELECT percent FROM history WHERE history.manga_id = manga.manga_id) DESC"
|
||||
ListSortOrder.ALPHABETIC_REVERSE -> "manga.title DESC"
|
||||
ListSortOrder.NEW_CHAPTERS -> "IFNULL((SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC"
|
||||
ListSortOrder.PROGRESS -> "IFNULL((SELECT percent FROM history WHERE history.manga_id = manga.manga_id), 0) DESC"
|
||||
ListSortOrder.LAST_READ -> "IFNULL((SELECT updated_at FROM history WHERE history.manga_id = manga.manga_id), 0) DESC"
|
||||
|
||||
else -> throw IllegalArgumentException("Sort order $sortOrder is not supported")
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ class FavouritesRepository @Inject constructor(
|
||||
.flatMapLatest { order -> observeAll(categoryId, order) }
|
||||
}
|
||||
|
||||
fun observeMangaCount(): Flow<Int> {
|
||||
return db.getFavouritesDao().observeMangaCount()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
fun observeCategories(): Flow<List<FavouriteCategory>> {
|
||||
return db.getFavouriteCategoriesDao().observeAll().mapItems {
|
||||
it.toFavouriteCategory()
|
||||
@@ -89,6 +94,10 @@ class FavouritesRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getAllFavoritesCovers(order: ListSortOrder, limit: Int): List<Cover> {
|
||||
return db.getFavouritesDao().findCovers(order, limit)
|
||||
}
|
||||
|
||||
fun observeCategory(id: Long): Flow<FavouriteCategory?> {
|
||||
return db.getFavouriteCategoriesDao().observe(id)
|
||||
.map { it?.toFavouriteCategory() }
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||
import com.google.android.material.R as materialR
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
|
||||
class CategoriesSelectionCallback(
|
||||
private val recyclerView: RecyclerView,
|
||||
@@ -75,7 +75,7 @@ class CategoriesSelectionCallback(
|
||||
|
||||
private fun confirmDeleteCategories(ids: Set<Long>, mode: ActionMode) {
|
||||
val context = recyclerView.context
|
||||
MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
|
||||
MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED)
|
||||
.setMessage(R.string.categories_delete_confirm)
|
||||
.setTitle(R.string.remove_category)
|
||||
.setIcon(R.drawable.ic_delete)
|
||||
|
||||
@@ -76,7 +76,13 @@ class FavouriteCategoriesActivity :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(item: FavouriteCategory, view: View) {
|
||||
override fun onItemClick(item: FavouriteCategory?, view: View) {
|
||||
if (item == null) {
|
||||
if (selectionController.count == 0) {
|
||||
startActivity(FavouritesActivity.newIntent(view.context))
|
||||
}
|
||||
return
|
||||
}
|
||||
if (selectionController.onItemClick(item.id)) {
|
||||
return
|
||||
}
|
||||
@@ -92,8 +98,12 @@ class FavouriteCategoriesActivity :
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: FavouriteCategory, view: View): Boolean {
|
||||
return selectionController.onItemLongClick(item.id)
|
||||
override fun onItemLongClick(item: FavouriteCategory?, view: View): Boolean {
|
||||
return item != null && selectionController.onItemLongClick(item.id)
|
||||
}
|
||||
|
||||
override fun onShowAllClick(isChecked: Boolean) {
|
||||
viewModel.setAllCategoriesVisible(isChecked)
|
||||
}
|
||||
|
||||
override fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean {
|
||||
|
||||
@@ -5,9 +5,11 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
|
||||
interface FavouriteCategoriesListListener : OnListItemClickListener<FavouriteCategory> {
|
||||
interface FavouriteCategoriesListListener : OnListItemClickListener<FavouriteCategory?> {
|
||||
|
||||
fun onDragHandleTouch(holder: RecyclerView.ViewHolder): Boolean
|
||||
|
||||
fun onEditClick(item: FavouriteCategory, view: View)
|
||||
|
||||
fun onShowAllClick(isChecked: Boolean)
|
||||
}
|
||||
|
||||
@@ -5,17 +5,21 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.domain.model.Cover
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.AllCategoriesListModel
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -30,9 +34,13 @@ class FavouritesCategoriesViewModel @Inject constructor(
|
||||
|
||||
private var commitJob: Job? = null
|
||||
|
||||
val content = repository.observeCategoriesWithCovers()
|
||||
.map { it.toUiList() }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
||||
val content = combine(
|
||||
repository.observeCategoriesWithCovers(),
|
||||
observeAllCategories(),
|
||||
settings.observeAsFlow(AppSettings.KEY_ALL_FAVOURITES_VISIBLE) { isAllFavouritesVisible },
|
||||
) { cats, all, showAll ->
|
||||
cats.toUiList(all, showAll)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
||||
|
||||
fun deleteCategories(ids: Set<Long>) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
@@ -74,21 +82,46 @@ class FavouritesCategoriesViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun Map<FavouriteCategory, List<Cover>>.toUiList(): List<ListModel> = map { (category, covers) ->
|
||||
CategoryListModel(
|
||||
mangaCount = covers.size,
|
||||
covers = covers.take(3),
|
||||
category = category,
|
||||
isTrackerEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources,
|
||||
)
|
||||
}.ifEmpty {
|
||||
listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_empty_favourites,
|
||||
textPrimary = R.string.text_empty_holder_primary,
|
||||
textSecondary = R.string.empty_favourite_categories,
|
||||
actionStringRes = 0,
|
||||
private fun Map<FavouriteCategory, List<Cover>>.toUiList(
|
||||
allFavorites: Pair<Int, List<Cover>>,
|
||||
showAll: Boolean
|
||||
): List<ListModel> {
|
||||
if (isEmpty()) {
|
||||
return listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_empty_favourites,
|
||||
textPrimary = R.string.text_empty_holder_primary,
|
||||
textSecondary = R.string.empty_favourite_categories,
|
||||
actionStringRes = 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
val result = ArrayList<ListModel>(size + 1)
|
||||
result.add(
|
||||
AllCategoriesListModel(
|
||||
mangaCount = allFavorites.first,
|
||||
covers = allFavorites.second,
|
||||
isVisible = showAll,
|
||||
),
|
||||
)
|
||||
mapTo(result) { (category, covers) ->
|
||||
CategoryListModel(
|
||||
mangaCount = covers.size,
|
||||
covers = covers.take(3),
|
||||
category = category,
|
||||
isTrackerEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources,
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun observeAllCategories(): Flow<Pair<Int, List<Cover>>> {
|
||||
return settings.observeAsFlow(AppSettings.KEY_FAVORITES_ORDER) {
|
||||
allFavoritesSortOrder
|
||||
}.mapLatest { order ->
|
||||
repository.getAllFavoritesCovers(order, limit = 3)
|
||||
}.combine(repository.observeMangaCount()) { covers, count ->
|
||||
count to covers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.adapter
|
||||
|
||||
import org.koitharu.kotatsu.favourites.domain.model.Cover
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
|
||||
data class AllCategoriesListModel(
|
||||
val mangaCount: Int,
|
||||
val covers: List<Cover>,
|
||||
val isVisible: Boolean,
|
||||
) : ListModel {
|
||||
|
||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||
return other is AllCategoriesListModel
|
||||
}
|
||||
|
||||
override fun getChangePayload(previousState: ListModel): Any? {
|
||||
return if (previousState is AllCategoriesListModel && previousState.isVisible != isVisible) {
|
||||
ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED
|
||||
} else {
|
||||
super.getChangePayload(previousState)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ class CategoriesAdapter(
|
||||
|
||||
init {
|
||||
addDelegate(ListItemType.CATEGORY_LARGE, categoryAD(coil, lifecycleOwner, onItemClickListener))
|
||||
addDelegate(ListItemType.NAV_ITEM, allCategoriesAD(coil, lifecycleOwner, onItemClickListener))
|
||||
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listListener))
|
||||
addDelegate(ListItemType.STATE_LOADING, loadingStateAD())
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemCategoriesAllBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemCategoryBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -92,3 +93,68 @@ fun categoryAD(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun allCategoriesAD(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
clickListener: FavouriteCategoriesListListener,
|
||||
) = adapterDelegateViewBinding<AllCategoriesListModel, ListModel, ItemCategoriesAllBinding>(
|
||||
{ inflater, parent -> ItemCategoriesAllBinding.inflate(inflater, parent, false) },
|
||||
) {
|
||||
val eventListener = OnClickListener { v ->
|
||||
if (v.id == R.id.imageView_visible) {
|
||||
clickListener.onShowAllClick(!item.isVisible)
|
||||
} else {
|
||||
clickListener.onItemClick(null, v)
|
||||
}
|
||||
}
|
||||
val backgroundColor = context.getThemeColor(android.R.attr.colorBackground)
|
||||
ImageViewCompat.setImageTintList(
|
||||
binding.imageViewCover3,
|
||||
ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)),
|
||||
)
|
||||
ImageViewCompat.setImageTintList(
|
||||
binding.imageViewCover2,
|
||||
ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)),
|
||||
)
|
||||
binding.imageViewCover2.backgroundTintList =
|
||||
ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76))
|
||||
binding.imageViewCover3.backgroundTintList =
|
||||
ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153))
|
||||
val fallback = ColorDrawable(Color.TRANSPARENT)
|
||||
val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3)
|
||||
val crossFadeDuration = context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt()
|
||||
itemView.setOnClickListener(eventListener)
|
||||
binding.imageViewVisible.setOnClickListener(eventListener)
|
||||
|
||||
bind {
|
||||
binding.textViewSubtitle.text = if (item.mangaCount == 0) {
|
||||
getString(R.string.empty)
|
||||
} else {
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.items,
|
||||
item.mangaCount,
|
||||
item.mangaCount,
|
||||
)
|
||||
}
|
||||
binding.imageViewVisible.setImageResource(
|
||||
if (item.isVisible) {
|
||||
R.drawable.ic_eye
|
||||
} else {
|
||||
R.drawable.ic_eye_off
|
||||
},
|
||||
)
|
||||
repeat(coverViews.size) { i ->
|
||||
val cover = item.covers.getOrNull(i)
|
||||
coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run {
|
||||
placeholder(R.drawable.ic_placeholder)
|
||||
fallback(fallback)
|
||||
source(cover?.mangaSource)
|
||||
crossfade(crossFadeDuration * (i + 1))
|
||||
error(R.drawable.ic_error_placeholder)
|
||||
allowRgb565(true)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
|
||||
data class FavouriteTabModel(
|
||||
val id: Long,
|
||||
val title: String,
|
||||
val title: String?,
|
||||
) : ListModel {
|
||||
|
||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||
|
||||
class FavouriteTabPopupMenuProvider(
|
||||
private val context: Context,
|
||||
private val viewModel: FavouritesContainerViewModel,
|
||||
private val categoryId: Long
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
val menuResId = if (categoryId == NO_ID) {
|
||||
R.menu.popup_fav_tab_all
|
||||
} else {
|
||||
R.menu.popup_fav_tab
|
||||
}
|
||||
menuInflater.inflate(menuResId, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_hide -> viewModel.hide(categoryId)
|
||||
R.id.action_edit -> context.startActivity(
|
||||
FavouritesCategoryEditActivity.newIntent(context, categoryId),
|
||||
)
|
||||
|
||||
R.id.action_delete -> confirmDelete()
|
||||
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun confirmDelete() {
|
||||
MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED)
|
||||
.setMessage(R.string.categories_delete_confirm)
|
||||
.setTitle(R.string.remove_category)
|
||||
.setIcon(R.drawable.ic_delete)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.remove) { _, _ ->
|
||||
viewModel.deleteCategory(categoryId)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,46 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.parsers.util.replaceWith
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
class FavouritesContainerAdapter(
|
||||
fm: FragmentManager
|
||||
) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT),
|
||||
class FavouritesContainerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment),
|
||||
FlowCollector<List<FavouriteTabModel>> {
|
||||
|
||||
private val dataSet = ArrayList<FavouriteTabModel>()
|
||||
private val differ = AsyncListDiffer(
|
||||
AdapterListUpdateCallback(this),
|
||||
AsyncDifferConfig.Builder(ListModelDiffCallback<FavouriteTabModel>())
|
||||
.setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor())
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun getCount(): Int = dataSet.size
|
||||
override fun getItemCount(): Int = differ.currentList.size
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
val item = dataSet[position]
|
||||
override fun getItemId(position: Int): Long {
|
||||
return differ.currentList[position].id
|
||||
}
|
||||
|
||||
override fun containsItem(itemId: Long): Boolean {
|
||||
return differ.currentList.any { x -> x.id == itemId }
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
val item = differ.currentList[position]
|
||||
return FavouritesListFragment.newInstance(item.id)
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence {
|
||||
return dataSet[position].title
|
||||
override suspend fun emit(value: List<FavouriteTabModel>) = suspendCoroutine { cont ->
|
||||
differ.submitList(value, ContinuationResumeRunnable(cont))
|
||||
}
|
||||
|
||||
override suspend fun emit(value: List<FavouriteTabModel>) {
|
||||
dataSet.replaceWith(value)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
fun getItem(position: Int): FavouriteTabModel = differ.currentList[position]
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.AdapterListUpdateCallback
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
// FIXME migrate to ViewPager2 in FavouritesContainerFragment
|
||||
class FavouritesContainerAdapter2(fragment: Fragment) :
|
||||
FragmentStateAdapter(fragment.childFragmentManager, fragment.viewLifecycleOwner.lifecycle),
|
||||
TabConfigurationStrategy,
|
||||
FlowCollector<List<FavouriteTabModel>> {
|
||||
|
||||
private val differ = AsyncListDiffer(
|
||||
AdapterListUpdateCallback(this),
|
||||
AsyncDifferConfig.Builder(ListModelDiffCallback<FavouriteTabModel>())
|
||||
.setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor())
|
||||
.build(),
|
||||
)
|
||||
|
||||
override fun getItemCount(): Int = differ.currentList.size
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return differ.currentList[position].id
|
||||
}
|
||||
|
||||
override fun containsItem(itemId: Long): Boolean {
|
||||
return differ.currentList.any { x -> x.id == itemId }
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
val item = differ.currentList[position]
|
||||
return FavouritesListFragment.newInstance(item.id)
|
||||
}
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
val item = differ.currentList[position]
|
||||
tab.text = item.title
|
||||
tab.tag = item
|
||||
}
|
||||
|
||||
override suspend fun emit(value: List<FavouriteTabModel>) = suspendCoroutine { cont ->
|
||||
differ.submitList(value, ContinuationResumeRunnable(cont))
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,18 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.viewModels
|
||||
import coil.ImageLoader
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.core.ui.util.ActionModeListener
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.recyclerView
|
||||
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
|
||||
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
||||
import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding
|
||||
@@ -43,15 +47,21 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
|
||||
|
||||
override fun onViewBindingCreated(binding: FragmentFavouritesContainerBinding, savedInstanceState: Bundle?) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
val adapter = FavouritesContainerAdapter(childFragmentManager)
|
||||
binding.pager.adapter = adapter
|
||||
binding.tabs.setupWithViewPager(binding.pager)
|
||||
val pagerAdapter = FavouritesContainerAdapter(this)
|
||||
binding.pager.adapter = pagerAdapter
|
||||
binding.pager.offscreenPageLimit = 1
|
||||
binding.pager.recyclerView?.isNestedScrollingEnabled = false
|
||||
TabLayoutMediator(
|
||||
binding.tabs,
|
||||
binding.pager,
|
||||
FavouritesTabConfigurationStrategy(pagerAdapter, viewModel),
|
||||
).attach()
|
||||
binding.stubEmpty.setOnInflateListener(this)
|
||||
actionModeDelegate.addListener(this)
|
||||
viewModel.categories.observe(viewLifecycleOwner, adapter)
|
||||
viewModel.categories.observe(viewLifecycleOwner, pagerAdapter)
|
||||
viewModel.isEmpty.observe(viewLifecycleOwner, ::onEmptyStateChanged)
|
||||
addMenuProvider(FavouritesContainerMenuProvider(binding.root.context))
|
||||
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.pager))
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -4,30 +4,80 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.mapItems
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class FavouritesContainerViewModel @Inject constructor(
|
||||
favouritesRepository: FavouritesRepository,
|
||||
private val settings: AppSettings,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val onActionDone = MutableEventFlow<ReversibleAction>()
|
||||
|
||||
private val categoriesStateFlow = favouritesRepository.observeCategoriesForLibrary()
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
val categories = categoriesStateFlow.filterNotNull()
|
||||
.mapItems { FavouriteTabModel(it.id, it.title) }
|
||||
.distinctUntilChanged()
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
|
||||
val categories = combine(
|
||||
categoriesStateFlow.filterNotNull(),
|
||||
observeAllFavouritesVisibility(),
|
||||
) { list, showAll ->
|
||||
list.toUi(showAll)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
|
||||
|
||||
val isEmpty = categoriesStateFlow.map {
|
||||
it?.isEmpty() == true
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
|
||||
|
||||
private fun List<FavouriteCategory>.toUi(showAll: Boolean): List<FavouriteTabModel> {
|
||||
if (isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val result = ArrayList<FavouriteTabModel>(if (showAll) size + 1 else size)
|
||||
if (showAll) {
|
||||
result.add(FavouriteTabModel(NO_ID, null))
|
||||
}
|
||||
mapTo(result) { FavouriteTabModel(it.id, it.title) }
|
||||
return result
|
||||
}
|
||||
|
||||
fun hide(categoryId: Long) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
if (categoryId == NO_ID) {
|
||||
settings.isAllFavouritesVisible = false
|
||||
} else {
|
||||
favouritesRepository.updateCategory(categoryId, isVisibleInLibrary = false)
|
||||
val reverse = ReversibleHandle {
|
||||
favouritesRepository.updateCategory(categoryId, isVisibleInLibrary = true)
|
||||
}
|
||||
onActionDone.call(ReversibleAction(R.string.category_hidden_done, reverse))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCategory(categoryId: Long) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
favouritesRepository.removeCategories(setOf(categoryId))
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeAllFavouritesVisibility() = settings.observeAsFlow(
|
||||
key = AppSettings.KEY_ALL_FAVOURITES_VISIBLE,
|
||||
valueProducer = { isAllFavouritesVisible },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.container
|
||||
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator.TabConfigurationStrategy
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.util.PopupMenuMediator
|
||||
|
||||
class FavouritesTabConfigurationStrategy(
|
||||
private val adapter: FavouritesContainerAdapter,
|
||||
private val viewModel: FavouritesContainerViewModel,
|
||||
) : TabConfigurationStrategy {
|
||||
|
||||
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
|
||||
val item = adapter.getItem(position)
|
||||
tab.text = item.title ?: tab.view.context.getString(R.string.all_favourites)
|
||||
tab.tag = item
|
||||
PopupMenuMediator(FavouriteTabPopupMenuProvider(tab.view.context, viewModel, item.id)).attach(tab.view)
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import android.view.View
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
||||
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
|
||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||
import org.koitharu.kotatsu.databinding.FragmentListBinding
|
||||
@@ -30,9 +30,7 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
|
||||
|
||||
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
if (viewModel.categoryId != NO_ID) {
|
||||
addMenuProvider(FavouritesListMenuProvider(binding.root.context, viewModel))
|
||||
}
|
||||
binding.recyclerView.isVP2BugWorkaroundEnabled = true
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
@@ -73,6 +71,18 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_mark_current -> {
|
||||
MaterialAlertDialogBuilder(context ?: return false)
|
||||
.setTitle(item.title)
|
||||
.setMessage(R.string.mark_as_completed_prompt)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
viewModel.markAsRead(selectedItems)
|
||||
mode.finish()
|
||||
}.show()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onActionItemClicked(controller, mode, item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.list
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
|
||||
|
||||
class FavouritesListMenuProvider(
|
||||
private val context: Context,
|
||||
private val viewModel: FavouritesListViewModel,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_favourites, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
return when (menuItem.itemId) {
|
||||
R.id.action_edit -> {
|
||||
context.startActivity(FavouritesCategoryEditActivity.newIntent(context, viewModel.categoryId))
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
@@ -21,6 +23,7 @@ import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.ARG_CATEGORY_ID
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||
import org.koitharu.kotatsu.history.domain.MarkAsReadUseCase
|
||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
@@ -28,6 +31,7 @@ import org.koitharu.kotatsu.list.ui.model.EmptyState
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -35,31 +39,37 @@ class FavouritesListViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val repository: FavouritesRepository,
|
||||
private val listExtraProvider: ListExtraProvider,
|
||||
private val markAsReadUseCase: MarkAsReadUseCase,
|
||||
settings: AppSettings,
|
||||
downloadScheduler: DownloadWorker.Scheduler,
|
||||
) : MangaListViewModel(settings, downloadScheduler) {
|
||||
|
||||
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
|
||||
private val refreshTrigger = MutableStateFlow(Any())
|
||||
|
||||
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_FAVORITES) { favoritesListMode }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode)
|
||||
|
||||
val sortOrder: StateFlow<ListSortOrder?> = if (categoryId == NO_ID) {
|
||||
MutableStateFlow(null)
|
||||
settings.observeAsFlow(AppSettings.KEY_FAVORITES_ORDER) {
|
||||
allFavoritesSortOrder
|
||||
}
|
||||
} else {
|
||||
repository.observeCategory(categoryId)
|
||||
.map { it?.order }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
}
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
override val content = combine(
|
||||
if (categoryId == NO_ID) {
|
||||
repository.observeAll(ListSortOrder.NEWEST)
|
||||
sortOrder.filterNotNull().flatMapLatest {
|
||||
repository.observeAll(it)
|
||||
}
|
||||
} else {
|
||||
repository.observeAll(categoryId)
|
||||
},
|
||||
listMode,
|
||||
) { list, mode ->
|
||||
refreshTrigger,
|
||||
) { list, mode, _ ->
|
||||
when {
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
@@ -80,10 +90,19 @@ class FavouritesListViewModel @Inject constructor(
|
||||
emit(listOf(it.toErrorState(canRetry = false)))
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
|
||||
|
||||
override fun onRefresh() = Unit
|
||||
override fun onRefresh() {
|
||||
refreshTrigger.value = Any()
|
||||
}
|
||||
|
||||
override fun onRetry() = Unit
|
||||
|
||||
fun markAsRead(items: Set<Manga>) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
markAsReadUseCase(items)
|
||||
onRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
fun removeFromFavourites(ids: Set<Long>) {
|
||||
if (ids.isEmpty()) {
|
||||
return
|
||||
|
||||
@@ -35,11 +35,12 @@ abstract class HistoryDao {
|
||||
|
||||
fun observeAll(order: ListSortOrder): Flow<List<HistoryWithManga>> {
|
||||
val orderBy = when (order) {
|
||||
ListSortOrder.UPDATED -> "history.updated_at DESC"
|
||||
ListSortOrder.LAST_READ -> "history.updated_at DESC"
|
||||
ListSortOrder.NEWEST -> "history.created_at DESC"
|
||||
ListSortOrder.PROGRESS -> "history.percent DESC"
|
||||
ListSortOrder.ALPHABETIC -> "manga.title"
|
||||
ListSortOrder.NEW_CHAPTERS -> "(SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id) DESC"
|
||||
ListSortOrder.ALPHABETIC_REVERSE -> "manga.title DESC"
|
||||
ListSortOrder.NEW_CHAPTERS -> "IFNULL((SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC"
|
||||
else -> throw IllegalArgumentException("Sort order $order is not supported")
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.db.entity.toMangaTags
|
||||
import org.koitharu.kotatsu.core.model.MangaHistory
|
||||
import org.koitharu.kotatsu.core.model.findById
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.core.model.isNsfw
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
||||
@@ -161,7 +162,7 @@ class HistoryRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun shouldSkip(manga: Manga): Boolean {
|
||||
return manga.isNsfw && settings.isHistoryExcludeNsfw || settings.isIncognitoModeEnabled
|
||||
return ((manga.source.isNsfw() || manga.isNsfw) && settings.isHistoryExcludeNsfw) || settings.isIncognitoModeEnabled
|
||||
}
|
||||
|
||||
fun observeShouldSkip(manga: Manga): Flow<Boolean> {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.koitharu.kotatsu.history.domain
|
||||
|
||||
import dagger.Reusable
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import javax.inject.Inject
|
||||
|
||||
@Reusable
|
||||
class MarkAsReadUseCase @Inject constructor(
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(manga: Manga) {
|
||||
val repo = mangaRepositoryFactory.create(manga.source)
|
||||
val details = if (manga.chapters.isNullOrEmpty()) {
|
||||
repo.getDetails(manga)
|
||||
} else {
|
||||
manga
|
||||
}
|
||||
val lastChapter = checkNotNull(details.chapters).last()
|
||||
val pages = repo.getPages(lastChapter)
|
||||
historyRepository.addOrUpdate(
|
||||
manga = details,
|
||||
chapterId = lastChapter.id,
|
||||
page = pages.lastIndex,
|
||||
scroll = 0,
|
||||
percent = 1f,
|
||||
)
|
||||
}
|
||||
|
||||
suspend operator fun invoke(manga: Collection<Manga>) {
|
||||
when (manga.size) {
|
||||
0 -> Unit
|
||||
1 -> invoke(manga.first())
|
||||
else -> supervisorScope {
|
||||
manga.map {
|
||||
launch {
|
||||
invoke(it)
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.os.NetworkManageIntent
|
||||
@@ -54,6 +55,18 @@ class HistoryListFragment : MangaListFragment() {
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_mark_current -> {
|
||||
MaterialAlertDialogBuilder(context ?: return false)
|
||||
.setTitle(item.title)
|
||||
.setMessage(R.string.mark_as_completed_prompt)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
viewModel.markAsRead(selectedItems)
|
||||
mode.finish()
|
||||
}.show()
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onActionItemClicked(controller, mode, item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.dialog.RememberSelectionDialogListener
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
@@ -36,7 +37,7 @@ class HistoryListMenuProvider(
|
||||
|
||||
private fun showClearHistoryDialog() {
|
||||
val selectionListener = RememberSelectionDialogListener(2)
|
||||
MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
|
||||
MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED)
|
||||
.setTitle(R.string.clear_history)
|
||||
.setSingleChoiceItems(
|
||||
arrayOf(
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.onFirst
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.domain.MarkAsReadUseCase
|
||||
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
|
||||
import org.koitharu.kotatsu.list.domain.ListExtraProvider
|
||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||
@@ -39,6 +40,7 @@ import org.koitharu.kotatsu.list.ui.model.toGridModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toListModel
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -48,6 +50,7 @@ class HistoryListViewModel @Inject constructor(
|
||||
settings: AppSettings,
|
||||
private val extraProvider: ListExtraProvider,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val markAsReadUseCase: MarkAsReadUseCase,
|
||||
networkState: NetworkState,
|
||||
downloadScheduler: DownloadWorker.Scheduler,
|
||||
) : MangaListViewModel(settings, downloadScheduler) {
|
||||
@@ -121,6 +124,12 @@ class HistoryListViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun markAsRead(items: Set<Manga>) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
markAsReadUseCase(items)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun mapList(
|
||||
list: List<MangaWithHistory>,
|
||||
grouped: Boolean,
|
||||
@@ -163,7 +172,7 @@ class HistoryListViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun MangaHistory.header(order: ListSortOrder): ListHeader? = when (order) {
|
||||
ListSortOrder.UPDATED -> ListHeader(calculateTimeAgo(updatedAt))
|
||||
ListSortOrder.LAST_READ -> ListHeader(calculateTimeAgo(updatedAt))
|
||||
ListSortOrder.NEWEST -> ListHeader(calculateTimeAgo(createdAt))
|
||||
ListSortOrder.PROGRESS -> ListHeader(
|
||||
when (percent) {
|
||||
@@ -175,6 +184,7 @@ class HistoryListViewModel @Inject constructor(
|
||||
)
|
||||
|
||||
ListSortOrder.ALPHABETIC,
|
||||
ListSortOrder.ALPHABETIC_REVERSE,
|
||||
ListSortOrder.RELEVANCE,
|
||||
ListSortOrder.NEW_CHAPTERS,
|
||||
ListSortOrder.RATING -> null
|
||||
|
||||
@@ -9,21 +9,22 @@ enum class ListSortOrder(
|
||||
@StringRes val titleResId: Int,
|
||||
) {
|
||||
|
||||
UPDATED(R.string.updated),
|
||||
NEWEST(R.string.order_added),
|
||||
PROGRESS(R.string.progress),
|
||||
ALPHABETIC(R.string.by_name),
|
||||
ALPHABETIC_REVERSE(R.string.by_name_reverse),
|
||||
RATING(R.string.by_rating),
|
||||
RELEVANCE(R.string.by_relevance),
|
||||
NEW_CHAPTERS(R.string.new_chapters),
|
||||
LAST_READ(R.string.last_read),
|
||||
;
|
||||
|
||||
fun isGroupingSupported() = this == UPDATED || this == NEWEST || this == PROGRESS
|
||||
fun isGroupingSupported() = this == LAST_READ || this == NEWEST || this == PROGRESS
|
||||
|
||||
companion object {
|
||||
|
||||
val HISTORY: Set<ListSortOrder> = EnumSet.of(UPDATED, NEWEST, PROGRESS, ALPHABETIC, NEW_CHAPTERS)
|
||||
val FAVORITES: Set<ListSortOrder> = EnumSet.of(ALPHABETIC, NEWEST, RATING, NEW_CHAPTERS, PROGRESS)
|
||||
val HISTORY: Set<ListSortOrder> = EnumSet.of(LAST_READ, NEWEST, PROGRESS, ALPHABETIC, ALPHABETIC_REVERSE, NEW_CHAPTERS)
|
||||
val FAVORITES: Set<ListSortOrder> = EnumSet.of(ALPHABETIC, ALPHABETIC_REVERSE, NEWEST, RATING, NEW_CHAPTERS, PROGRESS, LAST_READ)
|
||||
val SUGGESTIONS: Set<ListSortOrder> = EnumSet.of(RELEVANCE)
|
||||
|
||||
operator fun invoke(value: String, fallback: ListSortOrder) = entries.find(value) ?: fallback
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.require
|
||||
import org.koitharu.kotatsu.core.util.ext.sortedByOrdinal
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import javax.inject.Inject
|
||||
@@ -65,7 +66,11 @@ class ListConfigViewModel @Inject constructor(
|
||||
val value = getSortOrders()?.getOrNull(position) ?: return
|
||||
when (section) {
|
||||
is ListConfigSection.Favorites -> launchJob {
|
||||
favouritesRepository.setCategoryOrder(section.categoryId, value)
|
||||
if (section.categoryId == NO_ID) {
|
||||
settings.allFavoritesSortOrder = value
|
||||
} else {
|
||||
favouritesRepository.setCategoryOrder(section.categoryId, value)
|
||||
}
|
||||
}
|
||||
|
||||
ListConfigSection.General -> Unit
|
||||
@@ -75,9 +80,13 @@ class ListConfigViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCategorySortOrder(id: Long): ListSortOrder = runBlocking {
|
||||
private fun getCategorySortOrder(id: Long): ListSortOrder = if (id == NO_ID) {
|
||||
settings.allFavoritesSortOrder
|
||||
} else runBlocking {
|
||||
runCatchingCancellable {
|
||||
favouritesRepository.getCategory(id).order
|
||||
}.getOrDefault(ListSortOrder.NEWEST)
|
||||
}.getOrElse {
|
||||
settings.allFavoritesSortOrder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class SingleMangaImporter @Inject constructor(
|
||||
val contentResolver = storageManager.contentResolver
|
||||
val name = contentResolver.resolveName(uri) ?: throw IOException("Cannot fetch name from uri: $uri")
|
||||
if (!hasCbzExtension(name)) {
|
||||
throw UnsupportedFileException("Unsupported file on $uri")
|
||||
throw UnsupportedFileException("Unsupported file $name on $uri")
|
||||
}
|
||||
val dest = File(getOutputDir(), name)
|
||||
runInterruptible {
|
||||
|
||||
@@ -100,6 +100,7 @@ sealed class LocalMangaInput(
|
||||
id = id,
|
||||
name = name,
|
||||
number = number,
|
||||
volume = volume,
|
||||
url = url,
|
||||
scanlator = scanlator,
|
||||
uploadDate = uploadDate,
|
||||
|
||||
@@ -9,13 +9,20 @@ import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||
import org.koitharu.kotatsu.databinding.DialogImportBinding
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.OnClickListener {
|
||||
|
||||
@Inject
|
||||
lateinit var storageManager: LocalStorageManager
|
||||
|
||||
private val importFileCall = registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) {
|
||||
startImport(it)
|
||||
}
|
||||
@@ -55,6 +62,9 @@ class ImportDialogFragment : AlertDialogFragment<DialogImportBinding>(), View.On
|
||||
if (uris.isEmpty()) {
|
||||
return
|
||||
}
|
||||
uris.forEach {
|
||||
storageManager.takePermissions(it)
|
||||
}
|
||||
val ctx = requireContext()
|
||||
ImportWorker.start(ctx, uris)
|
||||
Toast.makeText(ctx, R.string.import_will_start_soon, Toast.LENGTH_LONG).show()
|
||||
|
||||
@@ -26,6 +26,15 @@ import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
|
||||
|
||||
class LocalListFragment : MangaListFragment(), FilterOwner {
|
||||
|
||||
init {
|
||||
withArgs(1) {
|
||||
putSerializable(
|
||||
RemoteListFragment.ARG_SOURCE,
|
||||
MangaSource.LOCAL,
|
||||
) // required by FilterCoordinator
|
||||
}
|
||||
}
|
||||
|
||||
override val viewModel by viewModels<LocalListViewModel>()
|
||||
|
||||
override val filter: MangaFilter
|
||||
@@ -97,14 +106,4 @@ class LocalListFragment : MangaListFragment(), FilterOwner {
|
||||
Snackbar.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance() = LocalListFragment().withArgs(1) {
|
||||
putSerializable(
|
||||
RemoteListFragment.ARG_SOURCE,
|
||||
MangaSource.LOCAL,
|
||||
) // required by FilterCoordinator
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,13 +134,13 @@ class MainNavigationDelegate(
|
||||
private fun onNavigationItemSelected(@IdRes itemId: Int): Boolean {
|
||||
return setPrimaryFragment(
|
||||
when (itemId) {
|
||||
R.id.nav_history -> HistoryListFragment()
|
||||
R.id.nav_favorites -> FavouritesContainerFragment()
|
||||
R.id.nav_explore -> ExploreFragment()
|
||||
R.id.nav_feed -> FeedFragment()
|
||||
R.id.nav_local -> LocalListFragment.newInstance()
|
||||
R.id.nav_suggestions -> SuggestionsFragment()
|
||||
R.id.nav_bookmarks -> BookmarksFragment()
|
||||
R.id.nav_history -> HistoryListFragment::class.java
|
||||
R.id.nav_favorites -> FavouritesContainerFragment::class.java
|
||||
R.id.nav_explore -> ExploreFragment::class.java
|
||||
R.id.nav_feed -> FeedFragment::class.java
|
||||
R.id.nav_local -> LocalListFragment::class.java
|
||||
R.id.nav_suggestions -> SuggestionsFragment::class.java
|
||||
R.id.nav_bookmarks -> BookmarksFragment::class.java
|
||||
else -> return false
|
||||
},
|
||||
)
|
||||
@@ -157,16 +157,17 @@ class MainNavigationDelegate(
|
||||
else -> 0
|
||||
}
|
||||
|
||||
private fun setPrimaryFragment(fragment: Fragment): Boolean {
|
||||
if (fragmentManager.isStateSaved) {
|
||||
private fun setPrimaryFragment(fragmentClass: Class<out Fragment>): Boolean {
|
||||
if (fragmentManager.isStateSaved || fragmentClass.isInstance(primaryFragment)) {
|
||||
return false
|
||||
}
|
||||
val fragment = instantiateFragment(fragmentClass)
|
||||
fragment.enterTransition = MaterialFadeThrough()
|
||||
fragmentManager.beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.replace(R.id.container, fragment, TAG_PRIMARY)
|
||||
.replace(R.id.container, fragmentClass, null, TAG_PRIMARY)
|
||||
.runOnCommit { onFragmentChanged(fragment, fromUser = true) }
|
||||
.commit()
|
||||
onFragmentChanged(fragment, fromUser = true)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -182,6 +183,11 @@ class MainNavigationDelegate(
|
||||
}
|
||||
}
|
||||
|
||||
private fun instantiateFragment(fragmentClass: Class<out Fragment>): Fragment {
|
||||
val classLoader = navBar.context.classLoader
|
||||
return fragmentManager.fragmentFactory.instantiate(classLoader, fragmentClass.name)
|
||||
}
|
||||
|
||||
private fun observeSettings(lifecycleOwner: LifecycleOwner) {
|
||||
settings.observe()
|
||||
.filter { x -> x == AppSettings.KEY_TRACKER_ENABLED || x == AppSettings.KEY_SUGGESTIONS }
|
||||
|
||||
@@ -201,7 +201,7 @@ class PageLoader @Inject constructor(
|
||||
|
||||
private suspend fun loadPageImpl(page: MangaPage, progress: MutableStateFlow<Float>): Uri = semaphore.withPermit {
|
||||
val pageUrl = getPageUrl(page)
|
||||
check(pageUrl.isNotBlank()) { "Cannot obtain full image url" }
|
||||
check(pageUrl.isNotBlank()) { "Cannot obtain full image url for $page" }
|
||||
val uri = Uri.parse(pageUrl)
|
||||
return if (uri.isZipUri()) {
|
||||
if (uri.scheme == URI_SCHEME_ZIP) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.transition.TransitionManager
|
||||
import android.transition.TransitionSet
|
||||
import android.view.Gravity
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
@@ -40,6 +39,7 @@ import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.core.ui.BaseFullscreenActivity
|
||||
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
|
||||
import org.koitharu.kotatsu.core.util.GridTouchHelper
|
||||
import org.koitharu.kotatsu.core.util.IdlingDetector
|
||||
@@ -140,6 +140,7 @@ class ReaderActivity :
|
||||
viewModel.content.observe(this) {
|
||||
onLoadingStateChanged(viewModel.isLoading.value)
|
||||
}
|
||||
viewModel.incognitoMode.observe(this, MenuInvalidator(this))
|
||||
viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure)
|
||||
viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn)
|
||||
viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged)
|
||||
@@ -152,6 +153,7 @@ class ReaderActivity :
|
||||
viewModel.isZoomControlsEnabled.observe(this) {
|
||||
viewBinding.zoomControl.isVisible = it
|
||||
}
|
||||
addMenuProvider(ReaderTopMenuProvider(this, viewModel))
|
||||
}
|
||||
|
||||
override fun getParentActivityIntent(): Intent? {
|
||||
@@ -190,21 +192,12 @@ class ReaderActivity :
|
||||
viewBinding.slider.isRtl = mode == ReaderMode.REVERSED
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.opt_reader_top, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_settings -> {
|
||||
startActivity(SettingsActivity.newReaderSettingsIntent(this))
|
||||
}
|
||||
|
||||
R.id.action_chapters -> {
|
||||
ChaptersSheet.show(supportFragmentManager)
|
||||
}
|
||||
|
||||
R.id.action_pages_thumbs -> {
|
||||
val state = viewModel.getCurrentState() ?: return false
|
||||
PagesThumbnailsSheet.show(
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.koitharu.kotatsu.reader.ui
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
|
||||
class ReaderTopMenuProvider(
|
||||
private val activity: ReaderActivity,
|
||||
private val viewModel: ReaderViewModel,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_reader_top, menu)
|
||||
}
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
menu.findItem(R.id.action_incognito)?.isVisible = viewModel.incognitoMode.value
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
return when (menuItem.itemId) {
|
||||
R.id.action_chapters -> {
|
||||
ChaptersSheet.show(activity.supportFragmentManager)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_incognito -> {
|
||||
showIncognitoModeDialog()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun showIncognitoModeDialog() {
|
||||
MaterialAlertDialogBuilder(activity, DIALOG_THEME_CENTERED)
|
||||
.setIcon(R.drawable.ic_incognito)
|
||||
.setTitle(R.string.incognito_mode)
|
||||
.setMessage(R.string.incognito_mode_hint)
|
||||
.setPositiveButton(R.string.got_it, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,12 @@ class ReaderViewModel @Inject constructor(
|
||||
val onShowToast = MutableEventFlow<Int>()
|
||||
val uiState = MutableStateFlow<ReaderUiState?>(null)
|
||||
|
||||
val incognitoMode = if (isIncognito) {
|
||||
MutableStateFlow(true)
|
||||
} else mangaFlow.map {
|
||||
it != null && historyRepository.shouldSkip(it)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
|
||||
|
||||
val content = MutableStateFlow(ReaderContent(emptyList(), null))
|
||||
val manga: MangaDetails?
|
||||
get() = mangaData.value
|
||||
@@ -256,7 +262,7 @@ class ReaderViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun onCurrentPageChanged(position: Int) {
|
||||
fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) {
|
||||
val prevJob = stateChangeJob
|
||||
val pages = content.value.pages // capture immediately
|
||||
stateChangeJob = launchJob(Dispatchers.Default) {
|
||||
@@ -265,7 +271,8 @@ class ReaderViewModel @Inject constructor(
|
||||
if (pages.size != content.value.pages.size) {
|
||||
return@launchJob // TODO
|
||||
}
|
||||
pages.getOrNull(position)?.let { page ->
|
||||
val centerPos = (lowerPos + upperPos) / 2
|
||||
pages.getOrNull(centerPos)?.let { page ->
|
||||
currentState.update { cs ->
|
||||
cs?.copy(chapterId = page.chapterId, page = page.index)
|
||||
}
|
||||
@@ -275,14 +282,14 @@ class ReaderViewModel @Inject constructor(
|
||||
return@launchJob
|
||||
}
|
||||
ensureActive()
|
||||
if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
|
||||
if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
|
||||
loadPrevNextChapter(pages.last().chapterId, isNext = true)
|
||||
}
|
||||
if (position <= BOUNDS_PAGE_OFFSET) {
|
||||
if (lowerPos <= BOUNDS_PAGE_OFFSET) {
|
||||
loadPrevNextChapter(pages.first().chapterId, isNext = false)
|
||||
}
|
||||
if (pageLoader.isPrefetchApplicable()) {
|
||||
pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT))
|
||||
pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,7 +388,7 @@ class ReaderViewModel @Inject constructor(
|
||||
mangaName = manga?.toManga()?.title,
|
||||
branch = chapter?.branch,
|
||||
chapterName = chapter?.name,
|
||||
chapterNumber = chapter?.number ?: 0,
|
||||
chapterNumber = chapter?.number?.toInt() ?: 0,
|
||||
chaptersTotal = manga?.chapters?.get(chapter?.branch)?.size ?: 0,
|
||||
totalPages = if (chapter != null) chaptersLoader.getPagesCount(chapter.id) else 0,
|
||||
currentPage = state?.page ?: 0,
|
||||
|
||||
@@ -172,7 +172,8 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
|
||||
}
|
||||
|
||||
private fun notifyPageChanged(page: Int) {
|
||||
viewModel.onCurrentPageChanged(reversed(page))
|
||||
val pos = reversed(page)
|
||||
viewModel.onCurrentPageChanged(pos, pos)
|
||||
}
|
||||
|
||||
private fun reversed(position: Int): Int {
|
||||
|
||||
@@ -42,7 +42,6 @@ open class PageHolder(
|
||||
bindingInfo.buttonRetry.setOnClickListener(this)
|
||||
@Suppress("LeakingThis")
|
||||
bindingInfo.buttonErrorDetails.setOnClickListener(this)
|
||||
binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -61,6 +60,7 @@ open class PageHolder(
|
||||
delegate.reload()
|
||||
}
|
||||
binding.ssiv.applyDownsampling(isResumed())
|
||||
binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
|
||||
@@ -170,7 +170,7 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
|
||||
}
|
||||
|
||||
private fun notifyPageChanged(page: Int) {
|
||||
viewModel.onCurrentPageChanged(page)
|
||||
viewModel.onCurrentPageChanged(page, page)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -24,7 +24,8 @@ import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>() {
|
||||
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>(),
|
||||
WebtoonRecyclerView.OnWebtoonScrollListener {
|
||||
|
||||
@Inject
|
||||
lateinit var networkState: NetworkState
|
||||
@@ -46,7 +47,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
||||
with(binding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
adapter = readerAdapter
|
||||
addOnPageScrollListener(PageScrollListener())
|
||||
addOnPageScrollListener(this@WebtoonReaderFragment)
|
||||
recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {
|
||||
addOnScrollListener(it)
|
||||
}
|
||||
@@ -70,6 +71,15 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
||||
exceptionResolver = exceptionResolver,
|
||||
)
|
||||
|
||||
override fun onScrollChanged(
|
||||
recyclerView: WebtoonRecyclerView,
|
||||
dy: Int,
|
||||
firstVisiblePosition: Int,
|
||||
lastVisiblePosition: Int,
|
||||
) {
|
||||
viewModel.onCurrentPageChanged(firstVisiblePosition, lastVisiblePosition)
|
||||
}
|
||||
|
||||
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
|
||||
val setItems = launch {
|
||||
requireAdapter().setItems(pages)
|
||||
@@ -91,7 +101,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
||||
?.restoreScroll(pendingState.scroll)
|
||||
}
|
||||
}
|
||||
notifyPageChanged(position)
|
||||
viewModel.onCurrentPageChanged(position, position)
|
||||
} else {
|
||||
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
@@ -121,10 +131,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
||||
viewBinding?.frame?.onZoomOut()
|
||||
}
|
||||
|
||||
private fun notifyPageChanged(page: Int) {
|
||||
viewModel.onCurrentPageChanged(page)
|
||||
}
|
||||
|
||||
override fun switchPageBy(delta: Int) {
|
||||
with(requireViewBinding().recyclerView) {
|
||||
if (isAnimationEnabled()) {
|
||||
@@ -147,12 +153,4 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private inner class PageScrollListener : WebtoonRecyclerView.OnPageScrollListener() {
|
||||
|
||||
override fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) {
|
||||
super.onPageChanged(recyclerView, index)
|
||||
notifyPageChanged(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import android.view.View
|
||||
import androidx.core.view.ViewCompat.TYPE_TOUCH
|
||||
import androidx.core.view.forEach
|
||||
import androidx.core.view.iterator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.core.util.ext.findCenterViewPosition
|
||||
import java.util.LinkedList
|
||||
import java.util.WeakHashMap
|
||||
|
||||
@@ -15,7 +15,8 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
) : RecyclerView(context, attrs, defStyleAttr) {
|
||||
|
||||
private var onPageScrollListeners: MutableList<OnPageScrollListener>? = null
|
||||
private var onPageScrollListeners = LinkedList<OnWebtoonScrollListener>()
|
||||
private val scrollDispatcher = WebtoonScrollDispatcher()
|
||||
private val detachedViews = WeakHashMap<View, Unit>()
|
||||
private var isFixingScroll: Boolean = false
|
||||
|
||||
@@ -103,22 +104,20 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
return 0
|
||||
}
|
||||
|
||||
fun addOnPageScrollListener(listener: OnPageScrollListener) {
|
||||
val list = onPageScrollListeners ?: LinkedList<OnPageScrollListener>().also { onPageScrollListeners = it }
|
||||
list.add(listener)
|
||||
fun addOnPageScrollListener(listener: OnWebtoonScrollListener) {
|
||||
onPageScrollListeners.add(listener)
|
||||
}
|
||||
|
||||
fun removeOnPageScrollListener(listener: OnPageScrollListener) {
|
||||
onPageScrollListeners?.remove(listener)
|
||||
fun removeOnPageScrollListener(listener: OnWebtoonScrollListener) {
|
||||
onPageScrollListeners.remove(listener)
|
||||
}
|
||||
|
||||
private fun notifyScrollChanged(dy: Int) {
|
||||
val listeners = onPageScrollListeners
|
||||
if (listeners.isNullOrEmpty()) {
|
||||
if (listeners.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val centerPosition = findCenterViewPosition()
|
||||
listeners.forEach { it.dispatchScroll(this, dy, centerPosition) }
|
||||
scrollDispatcher.dispatchScroll(this, dy)
|
||||
}
|
||||
|
||||
fun relayoutChildren() {
|
||||
@@ -162,20 +161,30 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
else -> false
|
||||
}
|
||||
|
||||
abstract class OnPageScrollListener {
|
||||
private class WebtoonScrollDispatcher {
|
||||
|
||||
private var lastPosition = NO_POSITION
|
||||
private var firstPos = NO_POSITION
|
||||
private var lastPos = NO_POSITION
|
||||
|
||||
fun dispatchScroll(recyclerView: WebtoonRecyclerView, dy: Int, centerPosition: Int) {
|
||||
onScroll(recyclerView, dy)
|
||||
if (centerPosition != NO_POSITION && centerPosition != lastPosition) {
|
||||
lastPosition = centerPosition
|
||||
onPageChanged(recyclerView, centerPosition)
|
||||
fun dispatchScroll(rv: WebtoonRecyclerView, dy: Int) {
|
||||
val lm = rv.layoutManager as? LinearLayoutManager
|
||||
if (lm == null) {
|
||||
firstPos = NO_POSITION
|
||||
lastPos = NO_POSITION
|
||||
return
|
||||
}
|
||||
val newFirstPos = lm.findFirstVisibleItemPosition()
|
||||
val newLastPos = lm.findLastVisibleItemPosition()
|
||||
if (newFirstPos != firstPos || newLastPos != lastPos) {
|
||||
firstPos = newFirstPos
|
||||
lastPos = newLastPos
|
||||
rv.onPageScrollListeners.forEach { it.onScrollChanged(rv, dy, newFirstPos, newLastPos) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun onScroll(recyclerView: WebtoonRecyclerView, dy: Int) = Unit
|
||||
interface OnWebtoonScrollListener {
|
||||
|
||||
open fun onPageChanged(recyclerView: WebtoonRecyclerView, index: Int) = Unit
|
||||
fun onScrollChanged(recyclerView: WebtoonRecyclerView, dy: Int, firstVisiblePosition: Int, lastVisiblePosition: Int)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ class RemoteListFragment : MangaListFragment(), FilterOwner {
|
||||
|
||||
override fun onPrepareMenu(menu: Menu) {
|
||||
super.onPrepareMenu(menu)
|
||||
menu.findItem(R.id.action_search)?.isVisible = viewModel.isSearchAvailable
|
||||
menu.findItem(R.id.action_random)?.isEnabled = !viewModel.isRandomLoading.value
|
||||
menu.findItem(R.id.action_filter_reset)?.isVisible = viewModel.header.value.isFilterApplied
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ open class RemoteListViewModel @Inject constructor(
|
||||
private var loadingJob: Job? = null
|
||||
private var randomJob: Job? = null
|
||||
|
||||
val isSearchAvailable: Boolean
|
||||
get() = repository.isSearchSupported
|
||||
|
||||
override val content = combine(
|
||||
mangaList.map { it?.distinctById()?.skipNsfwIfNeeded() },
|
||||
listMode,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.scrobbling.common.domain
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.collection.LongSparseArray
|
||||
import androidx.collection.getOrElse
|
||||
import androidx.core.text.parseAsHtml
|
||||
@@ -11,9 +12,11 @@ import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.util.ext.findKeyByValue
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.sanitize
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
|
||||
@@ -21,13 +24,12 @@ import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import java.util.EnumMap
|
||||
|
||||
abstract class Scrobbler(
|
||||
protected val db: MangaDatabase,
|
||||
val scrobblerService: ScrobblerService,
|
||||
private val repository: org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository,
|
||||
private val repository: ScrobblerRepository,
|
||||
) {
|
||||
|
||||
private val infoCache = LongSparseArray<ScrobblerMangaInfo>()
|
||||
@@ -76,7 +78,12 @@ abstract class Scrobbler(
|
||||
return entity.toScrobblingInfo()
|
||||
}
|
||||
|
||||
abstract suspend fun updateScrobblingInfo(mangaId: Long, rating: Float, status: ScrobblingStatus?, comment: String?)
|
||||
abstract suspend fun updateScrobblingInfo(
|
||||
mangaId: Long,
|
||||
@FloatRange(from = 0.0, to = 1.0) rating: Float,
|
||||
status: ScrobblingStatus?,
|
||||
comment: String?,
|
||||
)
|
||||
|
||||
fun observeScrobblingInfo(mangaId: Long): Flow<ScrobblingInfo?> {
|
||||
return db.getScrobblingDao().observe(scrobblerService.id, mangaId)
|
||||
|
||||
@@ -23,6 +23,8 @@ import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.setProgressIcon
|
||||
import org.koitharu.kotatsu.core.util.ext.setTabsEnabled
|
||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||
import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
|
||||
@@ -80,6 +82,15 @@ class ScrobblingSelectorSheet :
|
||||
viewModel.onClose.observeEvent(viewLifecycleOwner) {
|
||||
dismiss()
|
||||
}
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||
binding.buttonDone.isEnabled = !isLoading
|
||||
if (isLoading) {
|
||||
binding.buttonDone.setProgressIcon()
|
||||
} else {
|
||||
binding.buttonDone.icon = null
|
||||
}
|
||||
binding.tabs.setTabsEnabled(!isLoading)
|
||||
}
|
||||
viewModel.selectedScrobblerIndex.observe(viewLifecycleOwner) { index ->
|
||||
val tab = binding.tabs.getTabAt(index)
|
||||
if (tab != null && !tab.isSelected) {
|
||||
@@ -100,7 +111,7 @@ class ScrobblingSelectorSheet :
|
||||
}
|
||||
|
||||
override fun onItemClick(item: ScrobblerManga, view: View) {
|
||||
viewModel.selectedItemId.value = item.id
|
||||
viewModel.selectItem(item.id)
|
||||
}
|
||||
|
||||
override fun onRetryClick(error: Throwable) {
|
||||
|
||||
@@ -14,20 +14,24 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.findById
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.require
|
||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingFooter
|
||||
import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
|
||||
import org.koitharu.kotatsu.scrobbling.common.ui.selector.model.ScrobblerHint
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -35,6 +39,8 @@ import javax.inject.Inject
|
||||
class ScrobblingSelectorViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga
|
||||
@@ -92,6 +98,13 @@ class ScrobblingSelectorViewModel @Inject constructor(
|
||||
loadList(append = false)
|
||||
}
|
||||
|
||||
fun selectItem(id: Long) {
|
||||
if (doneJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
selectedItemId.value = id
|
||||
}
|
||||
|
||||
fun loadNextPage() {
|
||||
if (scrobblerMangaList.value.isNotEmpty() && hasNextPage.value) {
|
||||
loadList(append = true)
|
||||
@@ -109,7 +122,7 @@ class ScrobblingSelectorViewModel @Inject constructor(
|
||||
if (loadingJob?.isActive == true) {
|
||||
return
|
||||
}
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
loadingJob = launchJob(Dispatchers.Default) {
|
||||
listError.value = null
|
||||
val offset = if (append) scrobblerMangaList.value.size else 0
|
||||
runCatchingCancellable {
|
||||
@@ -136,8 +149,31 @@ class ScrobblingSelectorViewModel @Inject constructor(
|
||||
if (targetId == NO_ID) {
|
||||
onClose.call(Unit)
|
||||
}
|
||||
doneJob = launchJob(Dispatchers.Default) {
|
||||
doneJob = launchLoadingJob(Dispatchers.Default) {
|
||||
val prevInfo = currentScrobbler.getScrobblingInfoOrNull(manga.id)
|
||||
currentScrobbler.linkManga(manga.id, targetId)
|
||||
val history = historyRepository.getOne(manga)
|
||||
currentScrobbler.updateScrobblingInfo(
|
||||
mangaId = manga.id,
|
||||
rating = prevInfo?.rating ?: manga.rating,
|
||||
status = prevInfo?.status ?: if (history == null) {
|
||||
ScrobblingStatus.PLANNED
|
||||
} else {
|
||||
ScrobblingStatus.READING
|
||||
},
|
||||
comment = prevInfo?.comment,
|
||||
)
|
||||
if (history != null) {
|
||||
val chapter = mangaRepositoryFactory.create(manga.source)
|
||||
.getDetails(manga)
|
||||
.chapters?.findById(history.chapterId)
|
||||
if (chapter != null) {
|
||||
currentScrobbler.scrobble(
|
||||
mangaId = manga.id,
|
||||
chapter = chapter,
|
||||
)
|
||||
}
|
||||
}
|
||||
onClose.call(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ class MangaListActivity :
|
||||
fm.commit {
|
||||
setReorderingAllowed(true)
|
||||
val fragment = if (source == MangaSource.LOCAL) {
|
||||
LocalListFragment.newInstance()
|
||||
LocalListFragment()
|
||||
} else {
|
||||
RemoteListFragment.newInstance(source)
|
||||
}
|
||||
|
||||
@@ -113,10 +113,14 @@ class MultiSearchViewModel @Inject constructor(
|
||||
}
|
||||
val semaphore = Semaphore(MAX_PARALLELISM)
|
||||
for (source in sources) {
|
||||
val repository = mangaRepositoryFactory.create(source)
|
||||
if (!repository.isSearchSupported) {
|
||||
continue
|
||||
}
|
||||
launch {
|
||||
val item = runCatchingCancellable {
|
||||
semaphore.withPermit {
|
||||
mangaRepositoryFactory.create(source).getList(offset = 0, filter = MangaListFilter.Search(q))
|
||||
repository.getList(offset = 0, filter = MangaListFilter.Search(q))
|
||||
.toUi(ListMode.GRID, extraProvider)
|
||||
}
|
||||
}.fold(
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import org.koitharu.kotatsu.core.util.ext.resolve
|
||||
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||
import com.google.android.material.R as materialR
|
||||
@@ -43,7 +44,7 @@ class SearchSuggestionMenuProvider(
|
||||
}
|
||||
|
||||
private fun clearSearchHistory() {
|
||||
MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
|
||||
MaterialAlertDialogBuilder(context, DIALOG_THEME_CENTERED)
|
||||
.setTitle(R.string.clear_search_history)
|
||||
.setIcon(R.drawable.ic_clear_all)
|
||||
.setMessage(R.string.text_clear_search_history_prompt)
|
||||
|
||||
@@ -16,7 +16,8 @@ import io.noties.markwon.Markwon
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.github.AppVersion
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import com.google.android.material.R as materialR
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
|
||||
class AppUpdateDialog(private val activity: AppCompatActivity) {
|
||||
|
||||
@@ -42,10 +43,7 @@ class AppUpdateDialog(private val activity: AppCompatActivity) {
|
||||
appendLine()
|
||||
append(Markwon.create(activity).toMarkdown(version.description))
|
||||
}
|
||||
MaterialAlertDialogBuilder(
|
||||
activity,
|
||||
materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered,
|
||||
)
|
||||
MaterialAlertDialogBuilder(activity, DIALOG_THEME_CENTERED)
|
||||
.setTitle(R.string.app_update_available)
|
||||
.setMessage(message)
|
||||
.setIcon(R.drawable.ic_app_update)
|
||||
@@ -68,7 +66,7 @@ class AppUpdateDialog(private val activity: AppCompatActivity) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadUpdateImpl() {
|
||||
private fun downloadUpdateImpl() = runCatching {
|
||||
val version = latestVersion
|
||||
val url = version.apkUrl.toUri()
|
||||
val dm = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
@@ -78,7 +76,10 @@ class AppUpdateDialog(private val activity: AppCompatActivity) {
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setMimeType("application/vnd.android.package-archive")
|
||||
dm.enqueue(request)
|
||||
}.onSuccess {
|
||||
Toast.makeText(activity, R.string.download_started, Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { e ->
|
||||
Toast.makeText(activity, e.getDisplayMessage(activity.resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun openInBrowser() {
|
||||
|
||||
@@ -352,7 +352,7 @@ class SuggestionsWorker @AssistedInject constructor(
|
||||
val request = PeriodicWorkRequestBuilder<SuggestionsWorker>(6, TimeUnit.HOURS)
|
||||
.setConstraints(createConstraints())
|
||||
.addTag(TAG)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.HOURS)
|
||||
.build()
|
||||
workManager
|
||||
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)
|
||||
|
||||
@@ -323,7 +323,7 @@ class TrackWorker @AssistedInject constructor(
|
||||
val request = PeriodicWorkRequestBuilder<TrackWorker>(4, TimeUnit.HOURS)
|
||||
.setConstraints(constraints)
|
||||
.addTag(TAG)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 5, TimeUnit.MINUTES)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
|
||||
.build()
|
||||
workManager
|
||||
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)
|
||||
@@ -373,7 +373,7 @@ class TrackWorker @AssistedInject constructor(
|
||||
const val TAG = "tracking"
|
||||
const val TAG_ONESHOT = "tracking_oneshot"
|
||||
const val MAX_PARALLELISM = 3
|
||||
const val MAX_ATTEMPTS = 4
|
||||
const val MAX_ATTEMPTS = 3
|
||||
const val DATA_KEY_SUCCESS = "success"
|
||||
const val DATA_KEY_FAILED = "failed"
|
||||
const val KEY_RETRY_IDS = "retry"
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword"
|
||||
android:maxLength="24"
|
||||
android:singleLine="true"
|
||||
@@ -80,4 +81,4 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:imeOptions="actionDone"
|
||||
android:importantForAutofill="no"
|
||||
android:inputType="textPassword"
|
||||
android:maxLength="24"
|
||||
android:singleLine="true"
|
||||
@@ -94,4 +95,4 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
app:tabGravity="start"
|
||||
app:tabMode="scrollable" />
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.EnhancedViewPager
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
111
app/src/main/res/layout/item_categories_all.xml
Normal file
111
app/src/main/res/layout/item_categories_all.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/list_selector"
|
||||
android:minHeight="98dp"
|
||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||
tools:ignore="RtlSymmetry">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
app:tintMode="src_atop"
|
||||
tools:backgroundTint="#99FFFFFF"
|
||||
tools:src="@tools:sample/backgrounds/scenic"
|
||||
tools:tint="#99FFFFFF" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
app:tintMode="src_atop"
|
||||
tools:backgroundTint="#4DFFFFFF"
|
||||
tools:src="@tools:sample/backgrounds/scenic"
|
||||
tools:tint="#4DFFFFFF" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:layout_marginEnd="?listPreferredItemPaddingEnd"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/all_favourites"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
app:layout_constraintBottom_toTopOf="@id/textView_subtitle"
|
||||
app:layout_constraintEnd_toStartOf="@id/imageView_visible"
|
||||
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_subtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/imageView_visible"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_title"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_constraintWidth_default="wrap"
|
||||
tools:text="@tools:sample/lorem[1]" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/show_all"
|
||||
android:padding="@dimen/margin_normal"
|
||||
android:src="@drawable/ic_eye"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -34,7 +34,7 @@
|
||||
app:tabMode="scrollable"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<Button
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_done"
|
||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
android:title="@string/categories"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_mark_current"
|
||||
android:icon="@drawable/ic_eye_check"
|
||||
android:title="@string/mark_as_completed"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_select_all"
|
||||
android:icon="?actionModeSelectAllDrawable"
|
||||
|
||||
@@ -27,4 +27,10 @@
|
||||
android:title="@string/add_to_favourites"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
</menu>
|
||||
<item
|
||||
android:id="@+id/action_mark_current"
|
||||
android:icon="@drawable/ic_eye_check"
|
||||
android:title="@string/mark_as_completed"
|
||||
app:showAsAction="ifRoom|withText" />
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="AlwaysShowAction">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_chapters"
|
||||
android:icon="@drawable/ic_expand_more"
|
||||
android:title="@string/chapters"
|
||||
android:orderInCategory="0"
|
||||
android:title="@string/chapters"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
||||
<item
|
||||
android:id="@+id/action_incognito"
|
||||
android:icon="@drawable/ic_incognito"
|
||||
android:title="@string/incognito_mode"
|
||||
android:visible="false"
|
||||
app:showAsAction="always" />
|
||||
|
||||
</menu>
|
||||
|
||||
18
app/src/main/res/menu/popup_fav_tab.xml
Normal file
18
app/src/main/res/menu/popup_fav_tab.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_edit"
|
||||
android:title="@string/edit_category"
|
||||
android:titleCondensed="@string/edit" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:title="@string/delete" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_hide"
|
||||
android:title="@string/hide" />
|
||||
|
||||
</menu>
|
||||
9
app/src/main/res/menu/popup_fav_tab_all.xml
Normal file
9
app/src/main/res/menu/popup_fav_tab_all.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_hide"
|
||||
android:title="@string/hide" />
|
||||
|
||||
</menu>
|
||||
@@ -283,4 +283,8 @@
|
||||
<string name="empty_favourite_categories">لا توجد فئات مفضلة</string>
|
||||
<string name="screenshots_policy">سياسة لقطة الشاشة</string>
|
||||
<string name="done">تم</string>
|
||||
<string name="no_thanks">لا شكرا</string>
|
||||
<string name="enable">تفعيل</string>
|
||||
<string name="cancel_all_downloads_confirm">سيتم إلغاء جميع التنزيلات النشطة، وستُفقَد البيانات المحملة جزئيًا</string>
|
||||
<string name="text_downloads_list_holder">ليس لديك أية تنزيلات</string>
|
||||
</resources>
|
||||
@@ -543,4 +543,12 @@
|
||||
<string name="restore">Аднавіць</string>
|
||||
<string name="backup_date_">Дата стварэння рэзервовай копіі: %s</string>
|
||||
<string name="sync_auth">Увайдзіце, каб сінхранізаваць уліковы запіс</string>
|
||||
<string name="by_name_reverse">Імя (зваротнае)</string>
|
||||
<string name="content_rating">Рэйтынг кантэнту</string>
|
||||
<string name="genres_exclude">Выключыць жанры</string>
|
||||
<string name="rating_safe">Бяспечны</string>
|
||||
<string name="rating_suggestive">З падказкамі</string>
|
||||
<string name="rating_adult">Дарослы</string>
|
||||
<string name="default_tab">Ўкладка па змаўчанні</string>
|
||||
<string name="state_upcoming">Чакаецца</string>
|
||||
</resources>
|
||||
@@ -261,7 +261,7 @@
|
||||
<string name="appwidget_shelf_description">Manga aus deinen Favoriten</string>
|
||||
<string name="appwidget_recent_description">Deine kürzlich gelesenen Manga</string>
|
||||
<string name="report">Melden</string>
|
||||
<string name="tracking">Nachverfolgung</string>
|
||||
<string name="tracking">Externe Plattformen (Tracking)</string>
|
||||
<string name="logout">Abmelden</string>
|
||||
<string name="status_planned">Geplant</string>
|
||||
<string name="status_on_hold">In der Warteschleife</string>
|
||||
@@ -270,7 +270,7 @@
|
||||
<string name="show_reading_indicators_summary">Zeige gelesenen Prozentsatz in Verlauf und Favoriten</string>
|
||||
<string name="clear_cookies_summary">Kann bei einigen Probleme helfen. Alle Anmeldungen werden ungültig</string>
|
||||
<string name="status_completed">Abgeschlossen</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Als NSFW markierte Manga werden nicht dem Verlauf beigefügt und dein Lesefortschritt wird nicht gespeichert</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Als NSFW markierte Manga werden nicht in den Verlauf aufgenommen und Ihr Fortschritt wird nicht gespeichert</string>
|
||||
<string name="data_deletion">Datenlöschung</string>
|
||||
<string name="invalid_domain_message">Ungültige Domain</string>
|
||||
<string name="status_reading">Lese</string>
|
||||
@@ -347,7 +347,7 @@
|
||||
<string name="share_logs">Protokolle teilen</string>
|
||||
<string name="enable_logging">Protokollierung aktivieren</string>
|
||||
<string name="language">Sprache</string>
|
||||
<string name="enable_logging_summary">Einige Aktionen zu Debug-Zwecken aufzeichnen</string>
|
||||
<string name="enable_logging_summary">Einige Aktionen zu Debug-Zwecken aufzeichnen. Aktiviere dies nicht, wenn Du dir nicht sicher bist, was du tust.</string>
|
||||
<string name="history_shortcuts">Zeige Verknüpfungen zu aktuellen Manga</string>
|
||||
<string name="history_shortcuts_summary">Neueste Manga durch langes Drücken auf das Anwendungssymbol verfügbar machen</string>
|
||||
<string name="show_suspicious_content">Verdächtige Inhalte anzeigen</string>
|
||||
@@ -358,10 +358,10 @@
|
||||
<string name="nothing_here">Hier ist nichts</string>
|
||||
<string name="services">Dienste</string>
|
||||
<string name="theme_name_kanade">Kanade</string>
|
||||
<string name="scrobbling_empty_hint">Um deinen Lesefortschritt nachzuverfolgen, wähle Menu → Track auf dem Manga Details Bildschirm.</string>
|
||||
<string name="scrobbling_empty_hint">Um deinen Lesefortschritt nachzuverfolgen, wähle Menü → Lesefortschritt aufzeichnen auf dem Manga Details Bildschirm.</string>
|
||||
<string name="find_similar">Ähnliche finden</string>
|
||||
<string name="theme_name_sakura">Sakura</string>
|
||||
<string name="allow_unstable_updates_summary">Schlage Updates zu Beta-Versionen der App vor</string>
|
||||
<string name="allow_unstable_updates_summary">Benachrichtigungen über instabile Versionen erhalten</string>
|
||||
<string name="allow_unstable_updates">Erlaube instabile Updates</string>
|
||||
<string name="got_it">Alles klar</string>
|
||||
<string name="sources_reorder_tip">Drücke und halte eine Quelle, um diese umzusortieren</string>
|
||||
@@ -382,7 +382,7 @@
|
||||
<string name="sync_settings">Synchronisationseinstellungen</string>
|
||||
<string name="sync_host_description">Du kannst einen Standard- oder einen selbst gehosteten Synchronisations-Server verwenden. Ändere diese Einstellungen nicht, wenn du dich nicht auskennst.</string>
|
||||
<string name="ignore_ssl_errors">Ignoriere SSL Errors</string>
|
||||
<string name="mirror_switching_summary">Wechsel bei Fehlern automatisch die Domains für Manga-Quellen, falls Mirrors verfügbar sind</string>
|
||||
<string name="mirror_switching_summary">Automatischer Domain-Wechsel für Manga-Quellen, falls Spiegelserver verfügbar sind</string>
|
||||
<string name="status_re_reading">Lese erneut</string>
|
||||
<string name="sync_auth_hint">Du kannst dich mit einem bestehenden Account anmelden oder einen neuen erstellen</string>
|
||||
<string name="user_agent">UserAgent-Kopfzeile</string>
|
||||
@@ -465,4 +465,94 @@
|
||||
<string name="in_progress">In Arbeit</string>
|
||||
<string name="related_manga">Verwandte Manga</string>
|
||||
<string name="clear_source_cookies_summary">Cookies nur für bestimmte Domain löschen. In den meisten Fällen wird die Genehmigung ungültig</string>
|
||||
<string name="default_section">Standard-Abschnitt</string>
|
||||
<string name="catalog">Katalog</string>
|
||||
<string name="manage_sources">Quellen verwalten</string>
|
||||
<string name="content_type_manga">Manga</string>
|
||||
<string name="content_type_hentai">Hentai</string>
|
||||
<string name="content_type_other">Sonstige</string>
|
||||
<string name="sources_catalog">Quellenkatalog</string>
|
||||
<string name="source_enabled">Quelle aktiviert</string>
|
||||
<string name="keep_screen_on">Bildschirm eingeschaltet lassen</string>
|
||||
<string name="lock_screen_rotation">Bildschirm-Ausrichtung sperren</string>
|
||||
<string name="manga_list">Manga liste</string>
|
||||
<string name="disable_nsfw">NSFW deaktivieren</string>
|
||||
<string name="too_many_requests_message">Zu viele Anfragen. Probier es später erneut</string>
|
||||
<string name="items_limit_exceeded">Es können keine weiteren Elemente hinzugefügt werden</string>
|
||||
<string name="zoom_out">Herauszoomen</string>
|
||||
<string name="reader_zoom_buttons">Zoom - Schaltflächen anzeigen</string>
|
||||
<string name="periodic_backups">Periodische Backups</string>
|
||||
<string name="backup_frequency">Häufigkeit der Backup-Erstellung</string>
|
||||
<string name="error_multiple_genres_not_supported">Filtern nach mehreren Genres wird von dieser Manga-Quelle nicht unterstützt</string>
|
||||
<string name="webtoon_zoom_summary">Zoom-Geste in Webtoon-Modus erlauben</string>
|
||||
<string name="available_d">Verfügbar: %1$d</string>
|
||||
<string name="on_device">Auf dem Gerät</string>
|
||||
<string name="directories">Verzeichnisse</string>
|
||||
<string name="to_top">Nach oben</string>
|
||||
<string name="moved_to_top">Nach oben verschoben</string>
|
||||
<string name="state_paused">Pausiert</string>
|
||||
<string name="zoom_in">Hereinzoomen</string>
|
||||
<string name="reader_zoom_buttons_summary">Ob die Zoom - Schaltflächen in der unteren rechten Ecke angezeigt werden sollen</string>
|
||||
<string name="reader_optimize">Speicherverbrauch reduzieren (beta)</string>
|
||||
<string name="reader_optimize_summary">Qualität von nicht sichtbaren Seiten verringern, um den Speicherverbrauch zu reduzieren</string>
|
||||
<string name="state">Zustand</string>
|
||||
<string name="error_multiple_states_not_supported">Filtern nach mehreren Zuständen wird von dieser Manga-Quelle nicht unterstützt</string>
|
||||
<string name="error_search_not_supported">Die Suchfunktion wird von dieser Manga-Quelle nicht unterstützt</string>
|
||||
<string name="enhanced_colors">32-bit Farbmodus</string>
|
||||
<string name="suggest_new_sources">Neue Quellen nach einem Update vorschlagen</string>
|
||||
<string name="by_relevance">Relevanz</string>
|
||||
<string name="categories">Kategorien</string>
|
||||
<string name="frequency_every_day">Täglich</string>
|
||||
<string name="frequency_every_2_days">Alle 2 Tage</string>
|
||||
<string name="frequency_twice_per_month">Zweimal pro Monat</string>
|
||||
<string name="frequency_once_per_week">Einmal pro Woche</string>
|
||||
<string name="frequency_once_per_month">Einmal pro Monat</string>
|
||||
<string name="periodic_backups_enable">Periodische Backups aktivieren</string>
|
||||
<string name="backups_output_directory">Speicher-Verzeichnis für Backups</string>
|
||||
<string name="last_successful_backup">Letztes erfolgreiches Backup: %s</string>
|
||||
<string name="state_upcoming">Erwartet</string>
|
||||
<string name="by_name_reverse">Name reserviert</string>
|
||||
<string name="advanced">Erweitert</string>
|
||||
<string name="content_type_comics">Comics</string>
|
||||
<string name="source_summary_pattern">%1$s, %2$s</string>
|
||||
<string name="no_manga_sources_found">Keine verfügbaren Manga-Quellen für deinen Suchbegriff gefunden</string>
|
||||
<string name="welcome_text">Bitte wähle aus, welche Inhalte-Quellen du aktivieren möchtest. Dies kannst du auch später in den Einstellungen konfigurieren</string>
|
||||
<string name="sync_auth">Melde dich beim Sync - Account an</string>
|
||||
<string name="downloads_settings_info">Du kannst die Download-Verlangsamung für jede Manga-Quelle einzeln in den Quelleneinstellungen aktivieren, wenn du Probleme mit der serverseitigen Blockierung hast</string>
|
||||
<string name="skip">Überspringen</string>
|
||||
<string name="restore">Wiederherstellen</string>
|
||||
<string name="backup_date_">Sicherungsdatum: %s</string>
|
||||
<string name="content_rating">Einstufung (Inhalt)</string>
|
||||
<string name="genres_exclude">Genres ausschließen</string>
|
||||
<string name="rating_safe">Sicher</string>
|
||||
<string name="rating_suggestive">Suggestiv</string>
|
||||
<string name="rating_adult">Erwachsene</string>
|
||||
<string name="online_variant">Online-Variante</string>
|
||||
<string name="keep_screen_on_summary">Bildschirm nicht ausschalten, während du Manga liest</string>
|
||||
<string name="default_tab">Standard-Reiter</string>
|
||||
<string name="related_manga_summary">Zeige eine Liste ähnlicher Manga. Diese kann in einigen Fällen ungenau sein oder fehlen</string>
|
||||
<string name="grayscale">Graustufen</string>
|
||||
<string name="globally">Global</string>
|
||||
<string name="this_manga">Dieses Manga</string>
|
||||
<string name="color_correction_apply_text">Diese Einstellungen können global oder nur auf das aktuelle Manga angewendet werden. Werden sie global angewendet, bleiben individuelle Einstellungen erhalten.</string>
|
||||
<string name="apply">Anwenden</string>
|
||||
<string name="manual">Anleitung</string>
|
||||
<string name="speed_value">x%.1f</string>
|
||||
<string name="error_filter_states_genre_not_supported">Die Filterung nach Genre und Status wird von dieser Quelle nicht unterstützt</string>
|
||||
<string name="error_filter_locale_genre_not_supported">Die Filterung nach Genre und Lokalisation wird von dieser Quelle nicht unterstützt</string>
|
||||
<string name="error_corrupted_file">Es wurden ungültige Daten zurückgegeben oder die Datei ist beschädigt</string>
|
||||
<string name="genres_search_hint">Beginne den Genre-Namen einzutippen</string>
|
||||
<string name="no_manga_sources_catalog_text">In diesem Abschnitt sind keine Quellen verfügbar, oder es wurde bereits alles hinzugefügt.
|
||||
\nBleibe dran für neue Quellen</string>
|
||||
<string name="mark_as_completed">Als fertig markieren</string>
|
||||
<string name="mark_as_completed_prompt">Ausgewählte Manga als vollständig gelesen markieren?
|
||||
\n
|
||||
\nAchtung: Der aktuelle Lesefortschritt geht verloren.</string>
|
||||
<string name="main_screen_sections">Hauptbildschirm - Abschnitte</string>
|
||||
<string name="disable_nsfw_summary">Deaktiviere NSFW - Quellen und verberge Erwachsenen-Manga von der Liste, wenn möglich</string>
|
||||
<string name="disable_battery_optimization_summary_downloads">Könnte dir damit helfen, den Download zu starten, wenn du Probleme damit hast</string>
|
||||
<string name="state_abandoned">Verworfen</string>
|
||||
<string name="enhanced_colors_summary">Reduziert das Banding (harte Farbverläufe), kann aber die Leistung beeinträchtigen</string>
|
||||
<string name="suggest_new_sources_summary">Aufforderung zur Aktivierung neu hinzugefügter Quellen nach Aktualisierung der Anwendung</string>
|
||||
<string name="list_options">Listenoptionen</string>
|
||||
</resources>
|
||||
@@ -1,27 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">%1$d μέρα πριν</item>
|
||||
<item quantity="other">%1$d μέρες πριν</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$dστοιχείο</item>
|
||||
<item quantity="other">%1$dστοιχεία</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$dνέο κεφάλαιο</item>
|
||||
<item quantity="other">%1$dνέα κεφάλαια</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%1$dώρα πριν</item>
|
||||
<item quantity="other">%1$dώρες πριν</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$dκεφάλαιο</item>
|
||||
<item quantity="other">%1$dκεφάλαια</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">%1$dλεπτό πριν</item>
|
||||
<item quantity="other">%1$d λεπτά πριν</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">%1$d μέρα πριν</item>
|
||||
<item quantity="other">%1$d μέρες πριν</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d στοιχείο</item>
|
||||
<item quantity="other">%1$d στοιχεία</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d νέο κεφάλαιο</item>
|
||||
<item quantity="other">%1$d νέα κεφάλαια</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%1$d ώρα πριν</item>
|
||||
<item quantity="other">%1$d ώρες πριν</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d κεφάλαιο</item>
|
||||
<item quantity="other">%1$d κεφάλαια</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">%1$d λεπτό πριν</item>
|
||||
<item quantity="other">%1$d λεπτά πριν</item>
|
||||
</plurals>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="one">%1$d μήνας πριν</item>
|
||||
<item quantity="other">%1$d μήνες πριν</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="local_storage">Εσωτερικός χώρος</string>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="local_storage">Τοπικός χώρος αποθήκευσης</string>
|
||||
<string name="favourites">Αγαπημένα</string>
|
||||
<string name="history">Ιστορικό</string>
|
||||
<string name="error_occurred">Προέκυψε σφάλμα</string>
|
||||
@@ -8,7 +8,7 @@
|
||||
<string name="grid">Πλέγμα</string>
|
||||
<string name="list_mode">Εμφάνιση ως λίστα</string>
|
||||
<string name="settings">Ρυθμίσεις</string>
|
||||
<string name="remote_sources">Πηγές μάνγκα</string>
|
||||
<string name="remote_sources">Πηγές manga</string>
|
||||
<string name="computing_">Επεξεργασία…</string>
|
||||
<string name="close">Κλείσιμο</string>
|
||||
<string name="clear_history">Εκκαθάριση ιστορικού</string>
|
||||
@@ -22,7 +22,7 @@
|
||||
<string name="create_shortcut">Δημιουργία συντόμευσης…</string>
|
||||
<string name="share_s">Κοινοποίηση %s</string>
|
||||
<string name="search">Αναζήτηση</string>
|
||||
<string name="search_manga">Αναζήτηση μάνγκα</string>
|
||||
<string name="search_manga">Αναζήτηση manga</string>
|
||||
<string name="manga_downloading_">Λήψη…</string>
|
||||
<string name="download_complete">Κατεβασμένο</string>
|
||||
<string name="downloads">Λήψεις</string>
|
||||
@@ -37,18 +37,18 @@
|
||||
<string name="remove">Διαγραφή</string>
|
||||
<string name="save_page">Αποθήκευση σελίδας</string>
|
||||
<string name="page_saved">Αποθηκευμένα</string>
|
||||
<string name="share_image">Κοινή χρήση εικόνας</string>
|
||||
<string name="share_image">Κοινοποίηση εικόνας</string>
|
||||
<string name="_import">Εισαγωγή</string>
|
||||
<string name="delete">Διαγραφή</string>
|
||||
<string name="text_file_not_supported">Επιλέξτε ένα αρχείο ZIP ή CBZ.</string>
|
||||
<string name="no_description">Χωρίς περιγραφή</string>
|
||||
<string name="clear_pages_cache">Εκκαθάριση μνήμης cache της σελίδας</string>
|
||||
<string name="clear_pages_cache">Εκκαθάριση προσωρινής μνήμης της σελίδας</string>
|
||||
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
||||
<string name="standard">Τυπικό</string>
|
||||
<string name="webtoon">Μάνχγουα</string>
|
||||
<string name="webtoon">Webtoon</string>
|
||||
<string name="search_on_s">Αναζήτηση στο %s</string>
|
||||
<string name="delete_manga">Διαγραφή μάνγκα</string>
|
||||
<string name="text_delete_local_manga">Μόνιμη διαγραφή του \"%s\" από τη συσκευή;</string>
|
||||
<string name="delete_manga">Διαγραφή manga</string>
|
||||
<string name="text_delete_local_manga">Διαγραφή του \"%s\" από τη συσκευή;</string>
|
||||
<string name="reader_settings">Ρυθμίσεις λειτουργίας ανάγνωσης</string>
|
||||
<string name="switch_pages">Αλλαγή σελίδων</string>
|
||||
<string name="network_error">Σφάλμα δικτύου</string>
|
||||
@@ -63,7 +63,7 @@
|
||||
<string name="processing_">Επεξεργασία…</string>
|
||||
<string name="by_name">Όνομα</string>
|
||||
<string name="popular">Δημοφιλή</string>
|
||||
<string name="sort_order">Τρόπος Ταξινόμησης</string>
|
||||
<string name="sort_order">Τρόπος ταξινόμησης</string>
|
||||
<string name="theme">Θέμα</string>
|
||||
<string name="light">Φωτεινό</string>
|
||||
<string name="pages">Σελίδες</string>
|
||||
@@ -79,4 +79,480 @@
|
||||
<string name="theme_name_mion">Μιόν</string>
|
||||
<string name="theme_name_rikka">Ρίκκα</string>
|
||||
<string name="theme_name_sakura">Σακούρα</string>
|
||||
</resources>
|
||||
<string name="notifications">Ειδοποιήσεις</string>
|
||||
<string name="vibration">Δόνηση</string>
|
||||
<string name="favourites_categories">Αγαπημένες κατηγορίες</string>
|
||||
<string name="text_shelf_holder_primary">Τα manga σου θα εμφανίζονται εδώ</string>
|
||||
<string name="text_shelf_holder_secondary">Βρες κάτι να διαβάσεις στο τμήμα «Εξερεύνηση»</string>
|
||||
<string name="manga_save_location">Φάκελος λήψεων</string>
|
||||
<string name="not_available">Μη διαθέσιμο</string>
|
||||
<string name="all_favourites">Όλα τα αγαπημένα</string>
|
||||
<string name="favourites_category_empty">Άδεια κατηγορία</string>
|
||||
<string name="read_later">Θα διαβαστούν αργότερα</string>
|
||||
<string name="updates">Ενημερώσεις</string>
|
||||
<string name="search_results">Αποτελέσματα αναζήτησης</string>
|
||||
<string name="new_version_s">Νέα έκδοση: %s</string>
|
||||
<string name="size_s">Μέγεθος: %s</string>
|
||||
<string name="clear_updates_feed">Εκκαθάριση τροφοδοτικού ενημερώσεων</string>
|
||||
<string name="updates_feed_cleared">Καθαρίστηκε</string>
|
||||
<string name="rotate_screen">Περιστροφή οθόνης</string>
|
||||
<string name="update">Ενημέρωση</string>
|
||||
<string name="feed_will_update_soon">Η ενημέρωση του τροφοδοτικού θα αρχίσει σύντομα</string>
|
||||
<string name="dont_check">Μην κοιτάξεις</string>
|
||||
<string name="enter_password">Εισαγωγή κωδικού</string>
|
||||
<string name="check_for_updates">Κοίτα για ενημερώσεις</string>
|
||||
<string name="right_to_left">Δεξιά προς τα αριστερά</string>
|
||||
<string name="create_category">Νέα κατηγορία</string>
|
||||
<string name="scale_mode">Είδος κλίμακας</string>
|
||||
<string name="zoom_mode_fit_center">Γέμισμα στο κέντρο</string>
|
||||
<string name="zoom_mode_fit_height">Γέμισμα βάσει ύψους</string>
|
||||
<string name="zoom_mode_fit_width">Γέμισμα βάσει πλάτους</string>
|
||||
<string name="zoom_mode_keep_start">Κράτα το κατά την εκκίνηση</string>
|
||||
<string name="black_dark_theme">Μαύρο</string>
|
||||
<string name="black_dark_theme_summary">Λιγότερη κατανάλωση ενέργειας με οθόνες AMOLED</string>
|
||||
<string name="backup_restore">Αντίγραφο ασφαλείας και επαναφορά</string>
|
||||
<string name="create_backup">Δημιουργία αντιγράφου ασφαλείας</string>
|
||||
<string name="restore_backup">Επαναφορά αντιγράφου ασφαλείας</string>
|
||||
<string name="data_restored">Επαναφέρθηκε</string>
|
||||
<string name="preparing_">Ετοιμάζεται…</string>
|
||||
<string name="file_not_found">Δεν βρέθηκε αρχείο</string>
|
||||
<string name="data_restored_with_errors">Τα δεδομένα επαναφέρθηκαν, αλλά υπήρξαν σφάλματα</string>
|
||||
<string name="backup_information">Μπορείς να δημιουργήσεις αντίγραφα ασφαλείας του ιστορικού σου και των αγαπημένων σου και να τα επαναφέρεις</string>
|
||||
<string name="cookies_cleared">Όλα τα cookies καθαρίστηκαν</string>
|
||||
<string name="check_for_new_chapters">Κοίτα για νέα κεφάλαια</string>
|
||||
<string name="genres">Είδη</string>
|
||||
<string name="suggestions_info">Όλα τα δεδομένα αναλύονται στην συσκευή αυτή και δεν στέλνονται πουθενά αλλού.</string>
|
||||
<string name="text_suggestion_holder">Ξεκίνα να διαβάσεις manga και θα λαμβάνεις εξατομικευμένες προτάσεις</string>
|
||||
<string name="exclude_nsfw_from_suggestions">Να μην προτείνονται manga που είναι NSFW</string>
|
||||
<string name="filter_load_error">Η φόρτωση της λίστας ειδών απέτυχε</string>
|
||||
<string name="search_chapters">Βρες το κεφάλαιο</string>
|
||||
<string name="chapters_empty">Αυτό το manga δεν έχει κεφάλαια</string>
|
||||
<string name="chapters_will_removed_background">Τα κεφάλαια θα διαγράφονται στο παρασκήνιο</string>
|
||||
<string name="account_already_exists">Ο λογαριασμός υπάρχει ήδη</string>
|
||||
<string name="back">Πίσω</string>
|
||||
<string name="hide">Κρύψε</string>
|
||||
<string name="check_new_chapters_title">Κοίτα για νέα κεφάλαια και ενημέρωσε με</string>
|
||||
<string name="taps_on_edges">Πλευρικά πατήματα</string>
|
||||
<string name="volume_buttons">Κουμπιά έντασης</string>
|
||||
<string name="clear_thumbs_cache">Εκκαθάριση προσωρινής μνήμης σκίτσων</string>
|
||||
<string name="clear_search_history">Εκκαθάριση ιστορικού αναζήτησης</string>
|
||||
<string name="search_history_cleared">Καθαρίστηκε</string>
|
||||
<string name="gestures_only">Μόνο με χειρονομίες</string>
|
||||
<string name="internal_storage">Εσωτερικός χώρος αποθήκευσης</string>
|
||||
<string name="external_storage">Εξωτερικός χώρος αποθήκευσης</string>
|
||||
<string name="domain">Τομέας</string>
|
||||
<string name="app_update_available">Μια νέα έκδοση της εφαρμογής έγινε διαθέσιμη</string>
|
||||
<string name="open_in_browser">Άνοιγμα μέσω περιηγητή</string>
|
||||
<string name="large_manga_save_confirm">Αυτό το manga έχει %s. Να αποθηκευτεί ολόκληρο;</string>
|
||||
<string name="save_manga">Αποθήκευση</string>
|
||||
<string name="text_empty_holder_primary">Λίγο άδειο δεν είναι το μέρος…</string>
|
||||
<string name="text_search_holder_secondary">Προσπάθησε να αναδιατυπώσεις την αναζήτηση.</string>
|
||||
<string name="text_history_holder_primary">Ό,τι διαβάζεις θα εμφανίζεται εδώ</string>
|
||||
<string name="text_history_holder_secondary">Βρες κάτι να διαβάσεις στο τμήμα «Εξερεύνηση»</string>
|
||||
<string name="text_local_holder_primary">Αποθήκευσε κάτι πρώτα</string>
|
||||
<string name="text_local_holder_secondary">Αποθήκευσε κάτι από διαδικτυακές πηγές ή εισήγαγε το από αρχείο.</string>
|
||||
<string name="manga_shelf">Ράφι</string>
|
||||
<string name="recent_manga">Πρόσφατα</string>
|
||||
<string name="pages_animation">Κίνηση σελίδας</string>
|
||||
<string name="wrong_password">Λανθασμένος κωδικός</string>
|
||||
<string name="protect_application">Προστάτευσε την εφαρμογή</string>
|
||||
<string name="protect_application_summary">Ζήτηση κωδικού κατά την εκκίνηση του Kotatsu</string>
|
||||
<string name="repeat_password">Επανάληψη κωδικού</string>
|
||||
<string name="passwords_mismatch">Ασύμφωνοι κωδικοί</string>
|
||||
<string name="about">Σχετικά</string>
|
||||
<string name="app_version">Έκδοση %s</string>
|
||||
<string name="just_now">Μόλις τώρα</string>
|
||||
<string name="group">Ομάδα</string>
|
||||
<string name="today">Σήμερα</string>
|
||||
<string name="yesterday">Χθες</string>
|
||||
<string name="long_ago">Πριν πολύ καιρό</string>
|
||||
<string name="tap_to_try_again">Πάτα για να ξαναπροσπαθήσεις</string>
|
||||
<string name="reader_mode_hint">Η επιλεγμένες ρυθμίσεις θα αποθηκευτούν για το παρόν manga</string>
|
||||
<string name="silent">Αθόρυβο</string>
|
||||
<string name="read_more">Διάβασε περισσότερα</string>
|
||||
<string name="queued">Στην ουρά</string>
|
||||
<string name="chapter_is_missing">Λείπει το κεφάλαιο</string>
|
||||
<string name="about_app_translation_summary">Μετάφρασε την εφαρμογή</string>
|
||||
<string name="about_app_translation">Μετάφραση</string>
|
||||
<string name="auth_complete">Εξουσιοδοτημένο</string>
|
||||
<string name="auth_not_supported_by">Η σύνδεση στο %s δεν υποστηρίζεται</string>
|
||||
<string name="text_clear_cookies_prompt">Θα αποσυνδεθείς από όλες τις πηγές</string>
|
||||
<string name="state_finished">Ολοκληρώθηκε</string>
|
||||
<string name="state_ongoing">Σε εξέλιξη</string>
|
||||
<string name="system_default">Προεπιλεγμένο</string>
|
||||
<string name="exclude_nsfw_from_history">Να μην συμπεριλαμβάνονται στο ιστορικό manga που είναι NSFW</string>
|
||||
<string name="show_pages_numbers">Αριθμημένες σελίδες</string>
|
||||
<string name="enabled">Ενεργοποιημένο</string>
|
||||
<string name="onboard_text">Επίλεξε γλώσσες στις οποίες θέλεις να διαβάσεις manga. Μπορείς να αλλάξεις την επιλογή σου αργότερα από τις ρυθμίσεις.</string>
|
||||
<string name="never">Ποτέ</string>
|
||||
<string name="preload_pages">Προφόρτωση σελίδων</string>
|
||||
<string name="logged_in_as">Συνδέθηκες ως %s</string>
|
||||
<string name="always">Πάντα</string>
|
||||
<string name="nsfw">18+</string>
|
||||
<string name="various_languages">Διάφορες γλώσσες</string>
|
||||
<string name="percent_string_pattern">%1$s%%</string>
|
||||
<string name="appearance">Εμφάνιση</string>
|
||||
<string name="suggestions_updating">Οι προτάσεις ενημερώνονται</string>
|
||||
<string name="suggestions_excluded_genres">Να μην συμπεριλαμβάνονται τα είδη</string>
|
||||
<string name="suggestions_excluded_genres_summary">Ξεκαθάρισε ποια είδη δεν δες να βλέπεις στα προτεινόμενα</string>
|
||||
<string name="text_delete_local_manga_batch">Διαγραφή των επιλεγμένων στοιχείων από την συσκευή;</string>
|
||||
<string name="removal_completed">Η εκκαθάριση ολοκληρώθηκε</string>
|
||||
<string name="sync">Συγχρονισμός</string>
|
||||
<string name="sync_title">Συγχρόνισε τα δεδομένα σου</string>
|
||||
<string name="email_enter_hint">Εισήγαγε το email σου για να προχωρήσεις</string>
|
||||
<string name="new_sources_text">Νέες πηγές manga είναι διαθέσιμες</string>
|
||||
<string name="notifications_enable">Ενεργοποίηση ειδοποιήσεων</string>
|
||||
<string name="download">Λήψη</string>
|
||||
<string name="notifications_settings">Ρυθμίσεις ειδοποιήσεων</string>
|
||||
<string name="done">Ολοκληρώθηκε</string>
|
||||
<string name="reverse">Αντιστροφή</string>
|
||||
<string name="sign_in">Σύνδεση</string>
|
||||
<string name="auth_required">Συνδέσου για να δεις το περιεχόμενο</string>
|
||||
<string name="reset_filter">Επαναφορά φίλτρου</string>
|
||||
<string name="download_slowdown">Καθυστέρηση λήψεων</string>
|
||||
<string name="local_manga_processing">Επεξεργασία αποθηκευμένων manga</string>
|
||||
<string name="canceled">Ακυρώθηκε</string>
|
||||
<string name="_continue">Συνέχισε</string>
|
||||
<string name="error">Σφάλμα</string>
|
||||
<string name="new_chapters">Νέα κεφάλαια</string>
|
||||
<string name="notification_sound">Ήχος ειδοποιήσεων</string>
|
||||
<string name="cannot_find_available_storage">Μη διαθέσιμος χώρος αποθήκευσης</string>
|
||||
<string name="light_indicator">Δείκτης LED</string>
|
||||
<string name="other_storage">Άλλος χώρος αποθήκευσης</string>
|
||||
<string name="remove_category">Αφαίρεση</string>
|
||||
<string name="text_feed_holder">Νέα κεφάλαια από ό,τι διαβάζεις θα φαίνονται εδώ</string>
|
||||
<string name="track_sources">Ψάξε για ενημερώσεις</string>
|
||||
<string name="no_update_available">Δεν είναι διαθέσιμη κάποια ενημέρωση</string>
|
||||
<string name="data_restored_success">Όλα τα δεδομένα επαναφέρθηκαν</string>
|
||||
<string name="protect_application_subtitle">Εισήγαγε κωδικό με τον οποίο θα ανοίγεις την εφαρμογή</string>
|
||||
<string name="captcha_required">Απαιτείται έλεγχος CAPTCHA</string>
|
||||
<string name="captcha_solve">Επίλυση</string>
|
||||
<string name="clear_cookies">Εκκαθάριση cookies</string>
|
||||
<string name="clear_feed">Εκκαθάριση τροφοδοτικού</string>
|
||||
<string name="text_clear_updates_feed_prompt">Εκκαθάριση ολόκληρου του ιστορικού;</string>
|
||||
<string name="next">Επόμενο</string>
|
||||
<string name="confirm">Επιβεβαίωση</string>
|
||||
<string name="welcome">Καλώς ήρθες</string>
|
||||
<string name="available_sources">Διαθέσιμες πηγές</string>
|
||||
<string name="default_s">Προεπιλεγμένο: %s</string>
|
||||
<string name="text_clear_search_history_prompt">Εκκαθάριση όλων των πρόσφατων αναζητήσεων;</string>
|
||||
<string name="backup_saved">Το αντίγραφο ασφαλείας αποθηκεύτηκε</string>
|
||||
<string name="password_length_hint">Ο κωδικός πρέπει να αποτελείται από 4, ή περισσότερους, χαρακτήρες</string>
|
||||
<string name="tracker_warning">Το συστήματα μερικών συσκευών λειτουργούν διαφορετικά, και αυτό μπορεί να επηρεάσει τις λειτουργίες στο παρασκήνιο.</string>
|
||||
<string name="screenshots_policy">Πολιτική στιγμιότυπων οθόνης</string>
|
||||
<string name="screenshots_block_nsfw">Να μην επιτρέπονται σε NSFW manga</string>
|
||||
<string name="enabled_sources">Χρησιμοποιημένες πηγές</string>
|
||||
<string name="screenshots_allow">Επίτρεψε</string>
|
||||
<string name="screenshots_block_all">Να απαγορεύονται πάντα</string>
|
||||
<string name="suggestions_summary">Να προτείνονται manga βάσει των προτιμήσεων σου</string>
|
||||
<string name="disabled">Απενεργοποιημένο</string>
|
||||
<string name="suggestions">Προτάσεις</string>
|
||||
<string name="only_using_wifi">Μόνο με Wi-Fi</string>
|
||||
<string name="suggestions_enable">Ενεργοποίηση προτάσεων</string>
|
||||
<string name="download_slowdown_summary">Βοηθάει στην αποφυγή μπλοκαρίσματος της διεύθυνσης IP σου</string>
|
||||
<string name="show_notification_new_chapters_on">Θα λαμβάνεις ειδοποιήσεις για ενημερώσεις σχετικά με τα manga που διαβάζεις</string>
|
||||
<string name="show_notification_new_chapters_off">Δεν θα λαμβάνεις ειδοποιήσεις αλλά νέα κεφάλαια θα φαίνονται στην λίστα</string>
|
||||
<string name="state_upcoming">Ανερχόμενο</string>
|
||||
<string name="by_name_reverse">Όνομα - αντεστρεμένα</string>
|
||||
<string name="advanced">Για προχωρημένους</string>
|
||||
<string name="default_section">Προεπιλεγμένο τμήμα</string>
|
||||
<string name="catalog">Κατάλογος</string>
|
||||
<string name="manage_sources">Διαχείριση πηγών</string>
|
||||
<string name="content_type_comics">Comics</string>
|
||||
<string name="content_type_manga">Manga</string>
|
||||
<string name="content_type_hentai">Hentai</string>
|
||||
<string name="content_type_other">Άλλα</string>
|
||||
<string name="source_summary_pattern">%1$s, %2$s</string>
|
||||
<string name="sources_catalog">Κατάλογος πηγών</string>
|
||||
<string name="source_enabled">Η πηγή είναι ενεργοποιημένη</string>
|
||||
<string name="no_manga_sources_catalog_text">Δεν υπάρχουν διαθέσιμες πηγές σε αυτό το τμήμα, ή πιθανώς όλες έχουν προστεθεί.
|
||||
\nΜείνετε σε επαφή</string>
|
||||
<string name="no_manga_sources_found">Η αναζήτηση σας δεν επέφερε διαθέσιμα manga</string>
|
||||
<string name="invalid_port_number">Μη έγκυρος αριθμός θύρας</string>
|
||||
<string name="user_agent">Επικεφαλίδα UserAgent</string>
|
||||
<string name="history_cleared">Το ιστορικό καθαρίστηκε</string>
|
||||
<string name="no_bookmarks_summary">Μπορείς να χρησιμοποιήσεις σελιδοδείκτη όσο διαβάζεις manga</string>
|
||||
<string name="random">Τυχαίο</string>
|
||||
<string name="exit_confirmation">Επιβεβαίωση εξόδου</string>
|
||||
<string name="saved_manga">Αποθηκευμένα manga</string>
|
||||
<string name="pages_cache">Προσωρινή μνήμη σελίδων</string>
|
||||
<string name="other_cache">Προσωρινή μνήμη άλλων</string>
|
||||
<string name="removed_from_favourites">Αφαιρέθηκε από τα αγαπημένα</string>
|
||||
<string name="no_chapters">Δεν έχει κεφάλαια</string>
|
||||
<string name="reader_info_pattern">Κεφ. %1$d/%2$d Σελ. %3$d/%4$d</string>
|
||||
<string name="reader_info_bar">Εμφάνιση μπάρας πληροφοριών στο εργαλείο ανάγνωσης</string>
|
||||
<string name="import_completed_hint">Μπορείς να διαγράψεις το αρχικό αρχείο από τον χώρο αποθήκευσης για να απελευθερώσεις χώρο</string>
|
||||
<string name="import_will_start_soon">Η εισαγωγή θα ξεκινήσει σύντομα</string>
|
||||
<string name="reset">Επαναφορά</string>
|
||||
<string name="color_correction_hint">Οι επιλεγμένες ρυθμίσεις χρώματος θα αποθηκευτούν για αυτό το manga</string>
|
||||
<string name="compact">Συμπαγής</string>
|
||||
<string name="language">Γλώσσα</string>
|
||||
<string name="scrobbling_empty_hint">Για να καταγράψεις την πρόοδο σου, επίλεξε Μενού → Καταγραφή στην οθόνη λεπτομερειών για το manga.</string>
|
||||
<string name="services">Υπηρεσίες</string>
|
||||
<string name="allow_unstable_updates">Επίτρεψε ασταθείς ενημερώσεις</string>
|
||||
<string name="sources_reorder_tip">Πάτα παρατεταμένα ένα στοιχείο για να το αναταξινομήσεις</string>
|
||||
<string name="settings_apply_restart_required">Κάνε επανεκκίνηση της εφαρμογής για να εφαρμοστούν οι αλλαγές, παρακαλώ</string>
|
||||
<string name="comics_archive_import_description">Μπορείς να επιλέξεις ένα ή περισσότερα .cbz ή .zip αρχεία, κάθε ένα θα αναγνωρίζεται ως ξεχωριστό manga.</string>
|
||||
<string name="speed">Ταχύτητα</string>
|
||||
<string name="restore_backup_description">Εισήγαγε ένα αντίγραφο ασφαλείας δεδομένων χρήστη</string>
|
||||
<string name="show_on_shelf">Εμφάνιζε στο Ράφι</string>
|
||||
<string name="find_similar">Βρες παρόμοια</string>
|
||||
<string name="ignore_ssl_errors">Αγνόησε σφάλματα SSL</string>
|
||||
<string name="no_thanks">Όχι ευχαριστώ</string>
|
||||
<string name="cancel_all_downloads_confirm">Όλες οι τρέχουσες λήψεις θα ακυρωθούν, και δεδομένα που έχουν ληφθεί μερικώς θα χαθούν</string>
|
||||
<string name="downloads_cancelled">Οι λήψεις ακυρώθηκαν</string>
|
||||
<string name="translations">Μεταφράσεις</string>
|
||||
<string name="proxy">Διακομιστής μεσολάβησης</string>
|
||||
<string name="images_proxy_title">Διακομιστής μεσολάβησης βελτιστοποίησης εικόνων</string>
|
||||
<string name="invert_colors">Αντιστροφή χρωμάτων</string>
|
||||
<string name="username">Όνομα χρήστη</string>
|
||||
<string name="network">Δίκτυο</string>
|
||||
<string name="data_and_privacy">Δεδομένα και ιδιωτικότητα</string>
|
||||
<string name="restore_summary">Επανάφερε κάποιο αντίγραφο ασφαλείας</string>
|
||||
<string name="webtoon_zoom_summary">Επίτρεψε την χειρονομία μεγέθυνσης στην λειτουργία Webtoon</string>
|
||||
<string name="show_pages_numbers_summary">Εμφάνιζε τους αριθμούς των σελίδων στην κάτω γωνία</string>
|
||||
<string name="pages_animation_summary">Δείχνε την κίνηση των σελίδων κατά την αλλαγή τους</string>
|
||||
<string name="detect_reader_mode">Αυτόματη αναγνώριση κατάλληλης λειτουργίας ανάγνωσης</string>
|
||||
<string name="bookmarks_removed">Οι σελιδοδείκτες αφαιρέθηκαν</string>
|
||||
<string name="no_manga_sources">Δεν υπάρχουν πηγές για manga</string>
|
||||
<string name="no_manga_sources_text">Ενεργοποίησε πηγές για manga ώστε να διαβάσεις manga διαδικτυακά</string>
|
||||
<string name="reorder">Αναταξινόμηση</string>
|
||||
<string name="empty">Άδειο</string>
|
||||
<string name="explore">Εξερεύνησε</string>
|
||||
<string name="confirm_exit">Πάτα Πίσω ξανά για να επιστρέψεις στην προηγούμενη οθόνη</string>
|
||||
<string name="exit_confirmation_summary">Πάτα Πίσω δύο φορές για να βγεις από την εφαρμογή</string>
|
||||
<string name="storage_usage">Χρησιμοποίηση χώρου αποθήκευσης</string>
|
||||
<string name="available">Διαθέσιμο</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="options">Επιλογές</string>
|
||||
<string name="not_found_404">Το περιεχόμενο δεν βρέθηκε, και πιθανώς να έχει αφαιρεθεί</string>
|
||||
<string name="incognito_mode">Λειτουργία Incognito</string>
|
||||
<string name="automatic_scroll">Αυτόματη ολίσθηση</string>
|
||||
<string name="comics_archive">Αρχειοθήκη για κόμικς</string>
|
||||
<string name="folder_with_images">Φάκελος με εικόνες</string>
|
||||
<string name="importing_manga">Εισαγωγή manga σε εξέλιξη</string>
|
||||
<string name="import_completed">Η εισαγωγή ολοκληρώθηκε</string>
|
||||
<string name="feed">Τροφοδοτικό</string>
|
||||
<string name="history_shortcuts">Εμφάνιζε συντομεύσεις σχετικά με πρόσφατα manga</string>
|
||||
<string name="history_shortcuts_summary">Κάνε πρόσφατα manga διαθέσιμα πατώντας παρατεταμένα το εικονίδιο της εφαρμογής</string>
|
||||
<string name="contrast">Αντίθεση</string>
|
||||
<string name="reader_control_ltr_summary">Το πάτημα της δεξιάς πλευράς, ή του κουμπιού μείωσης της έντασης, πάντα προχωράει στην επόμενη σελίδα</string>
|
||||
<string name="reader_control_ltr">Εργονομικός χειρισμός του εργαλείου ανάγνωσης</string>
|
||||
<string name="color_correction">Χρωματική διόρθωση</string>
|
||||
<string name="brightness">Φωτεινότητα</string>
|
||||
<string name="text_unsaved_changes_prompt">Αποθήκευση ή αναίρεση των μη αποθηκευμένων επιλογών;</string>
|
||||
<string name="discard">Απόρριψη</string>
|
||||
<string name="error_no_space_left">Δεν έχει μείνει επαρκής αποθηκευτικός χώρος στην συσκευή</string>
|
||||
<string name="reader_slider">Εμφάνιζε τον σύρτη αλλαγής σελίδων</string>
|
||||
<string name="webtoon_zoom">Μεγέθυνση Γουέμπτουν</string>
|
||||
<string name="different_languages">Διαφορετικές γλώσσες</string>
|
||||
<string name="network_unavailable">Δεν υπάρχει διαθέσιμο δίκτυο</string>
|
||||
<string name="server_error">Σφάλμα διακομιστή (%1$d). Παρακαλώ ξαναπροσπαθήστε αργότερα</string>
|
||||
<string name="clear_new_chapters_counters">Επιπλέον, σβήσε πληροφορίες περί νέων κεφαλαίων</string>
|
||||
<string name="network_unavailable_hint">Άνοιξε το Wi-Fi ή τα δεδομένα για να διαβάσεις manga διαδικτυακώς</string>
|
||||
<string name="welcome_text">Παρακαλώ επίλεξε ποιες πηγές περιεχομένου θα ήθελες να ενεργοποιήσεις. Αυτό μπορεί να ρυθμιστεί ναι αργότερα από τις ρυθμίσεις</string>
|
||||
<string name="sync_auth">Συνδέσου στον λογαριασμό συγχρονισμού</string>
|
||||
<string name="downloads_settings_info">Μπορείς να ενεργοποιήσεις την καθυστέρηση λήψεων για κάθε πηγή manga ξεχωριστά μέσω των ρυθμίσεων των πηγών αν αντιμετωπίζεις προβλήματα με μπλοκάρισμα από πλευράς του διακομιστή</string>
|
||||
<string name="details_button_tip">Πάτα παρατεταμένα το κουμπί Διάβασε για να δεις περαιτέρω επιλογές</string>
|
||||
<string name="prefetch_content">Προφόρτωση περιεχομένου</string>
|
||||
<string name="skip">Προσπέραση</string>
|
||||
<string name="restore">Επαναφορά</string>
|
||||
<string name="backup_date_">Ημερομηνία δημιουργίας αντιγράφου ασφαλείας: %s</string>
|
||||
<string name="content_rating">Ηλικιακή βαθμολογία περιεχομένου</string>
|
||||
<string name="genres_exclude">Να μην συμπεριλαμβάνονται τα είδη</string>
|
||||
<string name="rating_safe">Ασφαλές</string>
|
||||
<string name="rating_suggestive">Άσεμνο</string>
|
||||
<string name="rating_adult">Για ενήλικες</string>
|
||||
<string name="clear_source_cookies_summary">Σβήσε τα cookies για τους επιλεγμένους τομέες μόνο. Στις περισσότερες περιπτώσεις θα ακυρωθεί η εξουσιοδότηση</string>
|
||||
<string name="online_variant">Διαδικτυακή έκδοση</string>
|
||||
<string name="keep_screen_on">Κράτα την οθόνη ανοιχτή</string>
|
||||
<string name="keep_screen_on_summary">Να μην κλείνει η οθόνη κατά την ανάγνωση manga</string>
|
||||
<string name="lock_screen_rotation">Κλείδωσε την περιστροφή οθόνης</string>
|
||||
<string name="allow_unstable_updates_summary">Λάβε ειδοποιήσεις για ασταθείς εκδόσεις</string>
|
||||
<string name="download_started">Η λήψη ξεκίνησε</string>
|
||||
<string name="manga_list">Λίστα manga</string>
|
||||
<string name="theme_name_dynamic">Δυναμικό</string>
|
||||
<string name="disable_nsfw">Απενεργοποιήση NSFW</string>
|
||||
<string name="got_it">Έγινε</string>
|
||||
<string name="default_tab">Προεπιλεγμένη καρτέλα</string>
|
||||
<string name="manga_branch_title_template">%1$s (%2$s)</string>
|
||||
<string name="download_option_all_chapters">Όλα τα μεταφρασμένα κεφάλαια %s</string>
|
||||
<string name="download_option_whole_manga">Ολόκληρο το manga</string>
|
||||
<string name="download_option_first_n_chapters">Πρώτα %s</string>
|
||||
<string name="download_option_next_unread_n_chapters">Τα επόμενα μη αναγνωσμένα %s</string>
|
||||
<string name="download_option_all_unread">Όλα τα μη αναγνωσμένα κεφάλαια</string>
|
||||
<string name="download_option_all_unread_b">Όλα τα μην αναγνωσμένα κεφάλαια (%s)</string>
|
||||
<string name="download_option_manual_selection">Χειροκίνητη επιλογή κεφαλαίων</string>
|
||||
<string name="description">Περιγραφή</string>
|
||||
<string name="this_month">Αυτόν τον μήνα</string>
|
||||
<string name="voice_search">Φωνητική αναζήτηση</string>
|
||||
<string name="related_manga">Σχετικά manga</string>
|
||||
<string name="color_light">Φωτεινό</string>
|
||||
<string name="color_white">Άσπρο</string>
|
||||
<string name="color_black">Μαύρο</string>
|
||||
<string name="background">Παρασκήνιο</string>
|
||||
<string name="data_not_restored">Τα δεδομένα δεν επαναφέρθηκαν</string>
|
||||
<string name="suggestions_wifi_only_summary">Να μην ενημερώνονται οι προτιμήσεις κατά την χρήση μετρημένων συνδέσεων δικτύου</string>
|
||||
<string name="search_hint">Εισήγαγε τίτλο manga, είδος, ή το όνομα πηγής</string>
|
||||
<string name="progress">Πρόοδος</string>
|
||||
<string name="order_added">Προστέθηκε</string>
|
||||
<string name="manage_categories">Διαχείριση κατηγοριών</string>
|
||||
<string name="view_list">Δες την λίστα</string>
|
||||
<string name="show">Εμφάνισε</string>
|
||||
<string name="downloaded">Ληφθέντα</string>
|
||||
<string name="too_many_requests_message">Υπερβολικά πολλά αιτήματα. Ξαναδοκιμάστε αργότερα</string>
|
||||
<string name="manage">Διαχείριση</string>
|
||||
<string name="no_bookmarks_yet">Δεν έχεις κάποιον σελιδοδείκτη ακόμα</string>
|
||||
<string name="share_logs">Κοινοποίηση logs</string>
|
||||
<string name="custom_directory">Directory της επιλογής σου</string>
|
||||
<string name="no_access_to_file">Δεν έχεις πρόσβαση σε αυτό το αρχείο ή directory</string>
|
||||
<string name="local_manga_directories">Directory για τοπικά manga</string>
|
||||
<string name="captcha_required_summary">%s απαιτεί την επίλυση ενός CAPTCHA για να λειτουργήσει κανονικά</string>
|
||||
<string name="password">Κωδικός</string>
|
||||
<string name="authorization_optional">Εξουσιοδότηση (προαιρετική)</string>
|
||||
<string name="source_disabled">Η πηγή είναι απενεργοποιημένη</string>
|
||||
<string name="grayscale">Κλίμακα του γκρι</string>
|
||||
<string name="globally">Σε όλη την έκταση της εφαρμογής</string>
|
||||
<string name="this_manga">Αυτό το manga</string>
|
||||
<string name="color_correction_apply_text">Αυτές οι ρυθμίσεις μπορούν να εφαρμοστούν σε όλη την έκταση της βιβλιοθήκης ή μόνο για αυτό το manga. Αν εφαρμοστούν σε όλη την έκταση της βιβλιοθήκης, οι ξεχωριστές ρυθμίσεις δεν θα επηρεαστούν.</string>
|
||||
<string name="apply">Εφαρμογή</string>
|
||||
<string name="type">Τύπος</string>
|
||||
<string name="address">Διεύθυνση</string>
|
||||
<string name="port">Θύρα</string>
|
||||
<string name="mark_as_current">Σημείωσε ως τρέχων</string>
|
||||
<string name="show_suspicious_content">Εμφάνιζε ύποπτο περιεχόμενο</string>
|
||||
<string name="color_theme">Χρωματικός συνδυασμός</string>
|
||||
<string name="state_abandoned">Παρατήθηκε</string>
|
||||
<string name="sync_settings">Ρυθμίσεις συγχρονισμού</string>
|
||||
<string name="server_address">Διεύθυνση διακομιστή</string>
|
||||
<string name="manual">Χειροκίνητα</string>
|
||||
<string name="available_d">Διαθέσιμα: %1$d</string>
|
||||
<string name="disable_nsfw_summary">Απενεργοποίηση NSFW πηγών και απόκρυψη manga για ενηλίκους από την λίστα αν είναι δυνατό</string>
|
||||
<string name="speed_value">x%.1f</string>
|
||||
<string name="mirror_switching">Αυτόματη επιλογή mirror</string>
|
||||
<string name="pause">Παύση</string>
|
||||
<string name="resume">Συνέχιση</string>
|
||||
<string name="paused">Σε παύση</string>
|
||||
<string name="cancel_all">Ακύρωση όλων</string>
|
||||
<string name="downloads_wifi_only">Λήψη μόνο μέσω Wi-Fi</string>
|
||||
<string name="downloads_wifi_only_summary">Σταματημός της λήψης με την αλλαγή σε δεδομένα</string>
|
||||
<string name="suggestion_manga">Προτάσεις: %s</string>
|
||||
<string name="suggestions_notifications_summary">Εμφάνιζε μερικές φορές ειδοποιήσεις με προτεινόμενα manga</string>
|
||||
<string name="more">Περισσότερα</string>
|
||||
<string name="enable">Ενεργοποίησε</string>
|
||||
<string name="remove_completed">Αφαίρεσε τα ολοκληρωμένα</string>
|
||||
<string name="languages">Γλώσσες</string>
|
||||
<string name="error_filter_locale_genre_not_supported">Το φιλτράρισμα δια είδους και τοποθεσίας δεν υποστηρίζεται από αυτήν την πηγή</string>
|
||||
<string name="error_filter_states_genre_not_supported">Το φιλτράρισμα δια είδους και κατάστασης δεν υποστηρίζεται από αυτήν την πηγή</string>
|
||||
<string name="invalid_value_message">Μη έγκυρη τιμή</string>
|
||||
<string name="error_corrupted_file">Μη έγκυρα δεδομένα επιστρέφονται ή το αρχείο είναι διεφθαρμένο</string>
|
||||
<string name="in_progress">Σε εξέλιξη</string>
|
||||
<string name="on_device">Στην συσκευή</string>
|
||||
<string name="directories">Directories</string>
|
||||
<string name="main_screen_sections">Τμήματα αρχικής οθόνης</string>
|
||||
<string name="to_top">Στην κορυφή</string>
|
||||
<string name="moved_to_top">Κινήθηκε στην κορυφή</string>
|
||||
<string name="genres_search_hint">Ξεκίνα να γράφεις το όνομα του είδους</string>
|
||||
<string name="state_paused">Σε παύση</string>
|
||||
<string name="zoom_out">Σμίκρυνση</string>
|
||||
<string name="zoom_in">Μεγέθυνση</string>
|
||||
<string name="reader_zoom_buttons">Εμφάνιζε κουμπιά για μεγέθυνση</string>
|
||||
<string name="reader_zoom_buttons_summary">Εμφάνιση κουμπιών μεγέθυνσης στην κάτω δεξιά γωνία ή όχι</string>
|
||||
<string name="reader_optimize">Μείωση κατανάλωσης μνήμης (beta)</string>
|
||||
<string name="mark_as_completed">Σημείωσε ως ολοκληρωμένο</string>
|
||||
<string name="mark_as_completed_prompt">Σημείωση του επιλεγμένου manga ως ολοκληρωμένου;
|
||||
\n
|
||||
\nΠροσοχή: η πρόοδος σου θα χαθεί.</string>
|
||||
<string name="disable_battery_optimization_summary_downloads">Ενδέχεται να βοηθήσει με την εκκίνηση της λήψης αν αντιμετωπίζεις προβλήματα σχετικά με αυτήν</string>
|
||||
<string name="categories_delete_confirm">Είσαι σίγουρος πως θες να διαγράψεις τις επιλεγμένες αγαπημένες κατηγορίες;
|
||||
\nΌλα τα manga που ανήκουν σε αυτές θα χαθούν, και αυτό είναι μη αναστρέψιμο.</string>
|
||||
<string name="downloads_resumed">Οι λήψεις έχουν ξαναξεκινήσει</string>
|
||||
<string name="downloads_paused">Οι λήψεις παύθηκαν</string>
|
||||
<string name="remove_completed_downloads_confirm">Το ιστορικό λήψεων σου θα διαγραφεί</string>
|
||||
<string name="text_downloads_list_holder">Δεν έχεις κάποια λήψη</string>
|
||||
<string name="downloads_removed">Οι λήψεις αφαιρέθηκαν</string>
|
||||
<string name="suggestions_enable_prompt">Θέλεις να λαμβάνεις εξατομικευμένες προτάσεις για manga;</string>
|
||||
<string name="web_view_unavailable">WebView μη διαθέσιμο: κοίτα αν κάποιος πάροχος WebView είναι εγκατεστημένο.</string>
|
||||
<string name="manga_error_description_pattern">Πληροφορίες σφάλματος:<br><tt>%1$s</tt><br><br>1. Προσπάθησε να <a href=%2$s>ανοίξεις το manga μέσω ενός περιηγητή</a> για να βεβαιωθείς για την διαθεσιμότητα του στην πηγή.<br>2. Βεβαιώσου πως χρησιμοποιείς την <a href=kotatsu://about>πιο πρόσφατη έκδοση του Kotatsu.</a><br>3. Αν είναι διαθέσιμο, στείλε αναφορά του σφάλματος στους δημιουργούς.</string>
|
||||
<string name="clear_network_cache">Καθάρισε προσωρινή μνήμη δικτύου</string>
|
||||
<string name="images_procy_description">Χρησιμοποίησε την υπηρεσία wsrv.nl για να μειώσεις την κίνηση δικτύου και να αυξήσεις την ταχύτητα φόρτωσης εικόνων αν είναι δυνατό</string>
|
||||
<string name="reader_info_bar_summary">Εμφάνιζε την ώρα και την πρόοδο στο πάνω μέρος της οθόνης</string>
|
||||
<string name="enable_logging">Ενεργοποίηση καταγραφής logs</string>
|
||||
<string name="pick_custom_directory">Διάλεξε directory της επιλογής σου</string>
|
||||
<string name="enable_logging_summary">Κατάγραψε μερικές δράσεις που θα προορίζονται για την εξφαλμάτωση. Μην το ενεργοποιήσεις αν δεν είσαι βέβαιος για το τι κάνεις</string>
|
||||
<string name="show_in_grid_view">Εμφάνιζε με μορφή πλέγματος</string>
|
||||
<string name="mirror_switching_summary">Αυτόματη αλλαγή τομέων για πήγες manga σε περίπτωση σφαλμάτων αν υπάρχει διαθεσιμότητα mirror</string>
|
||||
<string name="folder_with_images_import_description">Μπορείς να επιλέξεις ένα directory με φακέλους αρχειοθέτησης ή εικόνες. Κάθε φάκελος αρχειοθέτησης (ή subdirectory) θα αναγνωρίζεται ως κεφάλαιο.</string>
|
||||
<string name="sync_host_description">Μπορείς να χρησιμοποιήσεις έναν self-hosted διακομιστή συγχρονισμού, ή έναν από τους προεπιλεγμένους. Μην πειράξεις αυτή την ρύθμιση αν δεν ξέρεις τι κάνεις.</string>
|
||||
<string name="sync_auth_hint">Μπορείς να συνδεθείς σε έναν προϋπάρχον λογαριασμό ή να φτιάξεις έναν καινούριο</string>
|
||||
<string name="color_dark">Σκοτεινό</string>
|
||||
<string name="data_not_restored_text">Βεβαιώσου πως έχεις επιλέξει το σωστό αντίγραφο ασφαλείας</string>
|
||||
<string name="tracker_wifi_only_summary">Να μην γίνεται έλεγχος για νέα κεφάλαια κατά την χρήση μετρημένων συνδέσεων δικτύου</string>
|
||||
<string name="unknown">Άγνωστο</string>
|
||||
<string name="related_manga_summary">Εμφάνιζε μία λίστα σχετικών manga. Υπάρχει το ενδεχόμενο ανακρίβειας ή πλήρης απώλειας</string>
|
||||
<string name="items_limit_exceeded">Δεν μπορούν να προστεθούν περαιτέρω στοιχεία</string>
|
||||
<string name="reader_optimize_summary">Μείωση της ποιότητας των άφαντων σελίδων για την μείωση της χρησιμοποιούμενης μνήμης</string>
|
||||
<string name="state">Κατάσταση</string>
|
||||
<string name="error_multiple_genres_not_supported">Το φιλτράρισμα δια πολλών ειδών δεν υποστηρίζεται από αυτήν την πηγή manga</string>
|
||||
<string name="error_multiple_states_not_supported">Το φιλτράρισμα δια πολλών καταστάσεων δεν υποστηρίζεται από αυτήν την πηγή manga</string>
|
||||
<string name="error_search_not_supported">Η αναζήτηση δεν υποστηρίζεται από αυτήν την πηγή manga</string>
|
||||
<string name="enhanced_colors_summary">Μειώνει την ένταση της χρωματικής ζώνης, αλλά μπορεί να επηρεάσει την επίδοση</string>
|
||||
<string name="enhanced_colors">Λειτουργία χρώματος 32-bit</string>
|
||||
<string name="suggest_new_sources">Να προτείνονται νέες πηγές μετά από ενημέρωση της εφαρμογής</string>
|
||||
<string name="suggest_new_sources_summary">Εντολή για ενεργοποίηση καινούριων πηγών μετά την ενημέρωση της εφαρμογής</string>
|
||||
<string name="list_options">Επιλογές λίστας</string>
|
||||
<string name="by_relevance">Σχετικότητα</string>
|
||||
<string name="categories">Κατηγορίες</string>
|
||||
<string name="periodic_backups">Περιοδική δημιουργία αντιγράφων ασφαλείας</string>
|
||||
<string name="backup_frequency">Συχνότητα δημιουργίας αντιγράφων ασφαλείας</string>
|
||||
<string name="frequency_every_day">Κάθε μέρα</string>
|
||||
<string name="frequency_every_2_days">Κάθε δύο μέρες</string>
|
||||
<string name="frequency_once_per_week">Μία φορά την εβδομάδα</string>
|
||||
<string name="frequency_twice_per_month">Δύο φορές τον μήνα</string>
|
||||
<string name="frequency_once_per_month">Μία φορά τον μήνα</string>
|
||||
<string name="periodic_backups_enable">Ενεργοποίηση περιοδικής δημιουργίας αντιγράφων ασφαλείας</string>
|
||||
<string name="backups_output_directory">Directory όπου θα αποθηκεύονται τα αντίγραφα ασφαλείας</string>
|
||||
<string name="last_successful_backup">Τελευταία επιτυχής δημιουργία αντιγράφου ασφαλείας: %s</string>
|
||||
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d από %2$d σέ</string>
|
||||
<string name="edit_category">Επεξεργασία κατηγορίας</string>
|
||||
<string name="tracking">Καταγραφή</string>
|
||||
<string name="empty_favourite_categories">Χωρίς αγαπημένες κατηγορίες</string>
|
||||
<string name="logout">Αποσύνδεση</string>
|
||||
<string name="bookmarks">Σελιδοδείκτες</string>
|
||||
<string name="bookmark_removed">Σελιδοδείκτης αφαιρέθηκε</string>
|
||||
<string name="bookmark_added">Σελιδοδείκτης προστέθηκε</string>
|
||||
<string name="undo">Αναίρεση</string>
|
||||
<string name="removed_from_history">Αφαιρέθηκε από το ιστορικό</string>
|
||||
<string name="detect_reader_mode_summary">Αυτόματη ανίχνευση αν το manga είναι webtoon</string>
|
||||
<string name="status_planned">Προγραμματισμένο</string>
|
||||
<string name="status_reading">Ανάγνωση</string>
|
||||
<string name="status_re_reading">Επανάναγνωση</string>
|
||||
<string name="status_completed">Ολοκληρώθηκε</string>
|
||||
<string name="status_on_hold">Σε αναμονή</string>
|
||||
<string name="status_dropped">Διακόπηκε</string>
|
||||
<string name="disable_all">Απενεργοποίηση όλων</string>
|
||||
<string name="use_fingerprint">Χρήση ανιχνευτή δακτυλικού αποτυπώματος αν διατίθεται</string>
|
||||
<string name="show_reading_indicators_summary">Εμφάνιση ποσοστού ανάγνωσης στο ιστορικό και τα αγαπημένα</string>
|
||||
<string name="name">Όνομα</string>
|
||||
<string name="dns_over_https">DNS μέσω HTTPS</string>
|
||||
<string name="default_mode">Προεπιλεγμένη λειτουργία</string>
|
||||
<string name="disable_battery_optimization">Απενεργοποίηση βελτιστοποίησης μπαταρίας</string>
|
||||
<string name="disable_battery_optimization_summary">Βοηθά στους ελέγχους ενημέρωσης στο παρασκήνιο</string>
|
||||
<string name="crash_text">Κάτι πήγε στραβά. Παρακαλούμε υποβάλετε αναφορά σφάλματος στους προγραμματιστές για να μας βοηθήσετε να το διορθώσουμε.</string>
|
||||
<string name="send">Αποστολή</string>
|
||||
<string name="report">Αναφορά</string>
|
||||
<string name="show_reading_indicators">Εμφάνιση δεικτών προόδου ανάγνωσης</string>
|
||||
<string name="data_deletion">Διαγραφή δεδομένων</string>
|
||||
<string name="show_all">Εμφάνιση όλων</string>
|
||||
<string name="invalid_domain_message">Μη έγκυρο domain</string>
|
||||
<string name="select_range">Επιλογή εύρους</string>
|
||||
<string name="last_2_hours">Τελευταίες 2 ώρες</string>
|
||||
<string name="edit">Επεξεργασία</string>
|
||||
<string name="appwidget_shelf_description">Manga από τα αγαπημένα σας</string>
|
||||
<string name="bookmark_add">Προσθήκη σελιδοδείκτη</string>
|
||||
<string name="bookmark_remove">Αφαίρεση σελιδοδείκτη</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Manga που χαρακτηρίζονται ως NSFW δεν θα προστίθενται ποτέ στο ιστορικό και η πρόοδος σου δεν θα αποθηκεύεται</string>
|
||||
<string name="clear_all_history">Εκκαθάριση όλου του ιστορικού</string>
|
||||
<string name="appwidget_recent_description">Τα πρόσφατα διαβασμένα manga σου</string>
|
||||
<string name="clear_cookies_summary">Μπορεί να βοηθήσει σε περίπτωση κάποιων προβλημάτων. Όλες οι εξουσιοδοτήσεις θα ακυρωθούν</string>
|
||||
</resources>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<string name="grid_size">Tamaño de la cuadrícula</string>
|
||||
<string name="search_on_s">Buscar en %s</string>
|
||||
<string name="delete_manga">Borrar manga</string>
|
||||
<string name="text_delete_local_manga">¿Borrar \"%s\" del dispositivo permanentemente\?</string>
|
||||
<string name="text_delete_local_manga">¿Borrar permanentemente \"%s\" del dispositivo?</string>
|
||||
<string name="reader_settings">Ajustes del lector</string>
|
||||
<string name="switch_pages">Cambiar de página</string>
|
||||
<string name="taps_on_edges">Toques de borde</string>
|
||||
@@ -98,7 +98,7 @@
|
||||
<string name="manga_shelf">Estante</string>
|
||||
<string name="recent_manga">Reciente</string>
|
||||
<string name="pages_animation">Animación de página</string>
|
||||
<string name="manga_save_location">Carpeta para descargas</string>
|
||||
<string name="manga_save_location">Carpeta de descargas</string>
|
||||
<string name="not_available">No disponible</string>
|
||||
<string name="cannot_find_available_storage">No hay almacenamiento disponible</string>
|
||||
<string name="other_storage">Otro almacenamiento</string>
|
||||
@@ -107,7 +107,7 @@
|
||||
<string name="favourites_category_empty">Categoría vacía</string>
|
||||
<string name="read_later">Leer más tarde</string>
|
||||
<string name="updates">Actualizaciones</string>
|
||||
<string name="text_feed_holder">Aquí verás los nuevos episodios del manga que estás leyendo</string>
|
||||
<string name="text_feed_holder">Aquí se muestran los nuevos capítulos de lo que está leyendo</string>
|
||||
<string name="search_results">Resultados de la búsqueda</string>
|
||||
<string name="new_version_s">Nueva versión: %s</string>
|
||||
<string name="size_s">Tamaño: %s</string>
|
||||
@@ -169,7 +169,7 @@
|
||||
<string name="protect_application_subtitle">Introduce la contraseña que se requerirá cuando se inicie la aplicación</string>
|
||||
<string name="confirm">Confirmar</string>
|
||||
<string name="password_length_hint">La contraseña debe tener al menos 4 caracteres</string>
|
||||
<string name="text_local_holder_secondary">Puedes guardarlo desde fuentes in línea o importarlo desde un archivo.</string>
|
||||
<string name="text_local_holder_secondary">Guarda algo de un catálogo en línea o impórtalo desde un archivo.</string>
|
||||
<string name="text_local_holder_primary">Todavía no tienes ningún manga guardado</string>
|
||||
<string name="text_history_holder_secondary">Encuentra qué leer en la sección «Explorar»</string>
|
||||
<string name="text_history_holder_primary">Lo que leas se mostrará aquí</string>
|
||||
@@ -551,4 +551,8 @@
|
||||
<string name="rating_adult">Adulto</string>
|
||||
<string name="content_rating">Clasificación del contenido</string>
|
||||
<string name="default_tab">Pestaña por defecto</string>
|
||||
<string name="mark_as_completed">Marcar como completado</string>
|
||||
<string name="mark_as_completed_prompt">¿Marcar el manga seleccionado como completamente leído?
|
||||
\n
|
||||
\nAdvertencia: el progreso de lectura actual se perderá.</string>
|
||||
</resources>
|
||||
@@ -1,33 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">Il y a %1$d minute</item>
|
||||
<item quantity="many">Il y a %1$d minutes</item>
|
||||
<item quantity="other">Il y a %1$d minutes</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d élément</item>
|
||||
<item quantity="many">%1$d éléments</item>
|
||||
<item quantity="other">%1$d éléments</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d nouveau chapitre</item>
|
||||
<item quantity="many">%1$d nouveaux chapitres</item>
|
||||
<item quantity="other">%1$d nouveaux chapitres</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d chapitre</item>
|
||||
<item quantity="many">%1$d chapitres</item>
|
||||
<item quantity="other">%1$d chapitres</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">Il y a %1$d heure</item>
|
||||
<item quantity="many">Il y a %1$d heures</item>
|
||||
<item quantity="other">Il y a %1$d heures</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">Il y a %1$d jour</item>
|
||||
<item quantity="many">Il y a %1$d jours</item>
|
||||
<item quantity="other">Il y a %1$d jours</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">Il y a %1$d minute</item>
|
||||
<item quantity="many">Il y a %1$d minutes</item>
|
||||
<item quantity="other">Il y a %1$d minutes</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d élément</item>
|
||||
<item quantity="many">%1$d éléments</item>
|
||||
<item quantity="other">%1$d éléments</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d nouveau chapitre</item>
|
||||
<item quantity="many">%1$d nouveaux chapitres</item>
|
||||
<item quantity="other">%1$d nouveaux chapitres</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d chapitre</item>
|
||||
<item quantity="many">%1$d chapitres</item>
|
||||
<item quantity="other">%1$d chapitres</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">Il y a %1$d heure</item>
|
||||
<item quantity="many">Il y a %1$d heures</item>
|
||||
<item quantity="other">Il y a %1$d heures</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">Il y a %1$d jour</item>
|
||||
<item quantity="many">Il y a %1$d jours</item>
|
||||
<item quantity="other">Il y a %1$d jours</item>
|
||||
</plurals>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="one">Il y a %1$d mois</item>
|
||||
<item quantity="many">Il y a %1$d mois</item>
|
||||
<item quantity="other">Il y a %1$d mois</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -528,4 +528,26 @@
|
||||
<string name="backups_output_directory">Sauvegardes répertoire de sortie</string>
|
||||
<string name="suggest_new_sources_summary">Prompt pour permettre des sources nouvellement ajoutées après la mise à jour de l\'application</string>
|
||||
<string name="content_type_other">Autre</string>
|
||||
<string name="state_upcoming">A venir</string>
|
||||
<string name="welcome_text">S\'il vous plaît choisissez les sources que vous souhaitez activer. Cela peux aussi être fait plus tard dans les paramètres</string>
|
||||
<string name="sync_auth">Connectez-vous pour synchroniser le compte</string>
|
||||
<string name="skip">Passer</string>
|
||||
<string name="restore">Restaurer</string>
|
||||
<string name="content_rating">Note du contenu</string>
|
||||
<string name="genres_exclude">Exclure les genres</string>
|
||||
<string name="rating_safe">Sûr</string>
|
||||
<string name="rating_adult">Adulte</string>
|
||||
<string name="default_tab">Onglet par défaut</string>
|
||||
<string name="color_correction_apply_text">Ces paramètres peuvent être appliqué globalement ou uniquement au manga actuel. S\'il est activé globalement il ne remplacera les paramètres individuel.</string>
|
||||
<string name="globally">Globalement</string>
|
||||
<string name="this_manga">Ce manga</string>
|
||||
<string name="apply">Appliqué</string>
|
||||
<string name="genres_search_hint">Commence a écrit un nom de genre</string>
|
||||
<string name="downloads_settings_info">Vous pouvez activer la vitesse de téléchargement réduite individuellement pour chaque source de manga dans les paramètres si vous rencontrez des problèmes de bloquage avec le serveur.</string>
|
||||
<string name="backup_date_">Date de sauvegarde: %s</string>
|
||||
<string name="by_name_reverse">Nom réservé</string>
|
||||
<string name="mark_as_completed">Marque comme terminé</string>
|
||||
<string name="mark_as_completed_prompt">Marqué le manga sélectionné en terminé?
|
||||
\n
|
||||
\nAttention: la progression actuelle sera perdu.</string>
|
||||
</resources>
|
||||
@@ -91,7 +91,7 @@
|
||||
<string name="feed_will_update_soon">फीड अपडेट शीघ्र ही आरंभ होगा</string>
|
||||
<string name="app_update_available">इस ऐप का नया संस्करण उपलब्ध हैं</string>
|
||||
<string name="new_version_s">नया संस्करण: %s</string>
|
||||
<string name="text_delete_local_manga">डिवाइस में से %s ko सदा के लिए निकाल दे\?</string>
|
||||
<string name="text_delete_local_manga">डिवाइस से \"% s\" को स्थायी रूप से हटा दें?</string>
|
||||
<string name="text_history_holder_primary">जो भी आप पढ़ोगे वे सब यहां दिखेगा</string>
|
||||
<string name="delete_manga">मांगा को नष्ट करे</string>
|
||||
<string name="notification_sound">सूचना की ध्वनि</string>
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
<string name="error_occurred">Terjadi kesalahan</string>
|
||||
<string name="network_error">Kesalahan jaringan</string>
|
||||
<string name="details">Detail</string>
|
||||
<string name="grid">Kisi</string>
|
||||
<string name="list_mode">Mode daftar</string>
|
||||
<string name="grid">Kotak</string>
|
||||
<string name="list_mode">Gaya list</string>
|
||||
<string name="settings">Pengaturan</string>
|
||||
<string name="remote_sources">Sumber manga</string>
|
||||
<string name="remote_sources">Sumber</string>
|
||||
<string name="loading_">Memuat…</string>
|
||||
<string name="computing_">Menghitung…</string>
|
||||
<string name="chapter_d_of_d">Bab %1$d dari %2$d</string>
|
||||
<string name="close">Tutup</string>
|
||||
<string name="try_again">Coba lagi</string>
|
||||
<string name="nothing_found">Hasil tidak ditemukan</string>
|
||||
<string name="history_is_empty">Belum ada riwayat</string>
|
||||
<string name="nothing_found">Tidak ketemu</string>
|
||||
<string name="history_is_empty">Histori kosong</string>
|
||||
<string name="read">Baca</string>
|
||||
<string name="you_have_not_favourites_yet">Belum ada favorit</string>
|
||||
<string name="add_to_favourites">Favoritkan ini</string>
|
||||
<string name="add_to_favourites">Buat favorit</string>
|
||||
<string name="add_new_category">Kategori baru</string>
|
||||
<string name="add">Tambah</string>
|
||||
<string name="save">Simpan</string>
|
||||
@@ -36,8 +36,8 @@
|
||||
<string name="popular">Populer</string>
|
||||
<string name="updated">Diperbarui</string>
|
||||
<string name="newest">Terbaru</string>
|
||||
<string name="by_rating">Peringkat</string>
|
||||
<string name="sort_order">Urutkan berdasarkan</string>
|
||||
<string name="by_rating">Nilai</string>
|
||||
<string name="sort_order">Urutkan</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="theme">Tema</string>
|
||||
<string name="light">Terang</string>
|
||||
@@ -46,12 +46,12 @@
|
||||
<string name="pages">Halaman</string>
|
||||
<string name="clear">Bersihkan</string>
|
||||
<string name="remove">Hapus</string>
|
||||
<string name="clear_history">Bersihkan riwayat</string>
|
||||
<string name="clear_history">Hapus histori</string>
|
||||
<string name="delete">Hapus</string>
|
||||
<string name="operation_not_supported">Tindakan ini tidak didukung</string>
|
||||
<string name="text_file_not_supported">Pilih antara berkas ZIP atau CBZ.</string>
|
||||
<string name="no_description">Tidak ada deskripsi</string>
|
||||
<string name="_s_deleted_from_local_storage">\"%s\" dihapus dari penyimpanan lokal</string>
|
||||
<string name="_s_deleted_from_local_storage">\"%s\" dihapus dari penyimpanan</string>
|
||||
<string name="save_page">Simpan halaman</string>
|
||||
<string name="page_saved">Disimpan</string>
|
||||
<string name="share_image">Bagikan gambar</string>
|
||||
@@ -67,8 +67,8 @@
|
||||
<string name="switch_pages">Ganti halaman</string>
|
||||
<string name="chapters">Bab</string>
|
||||
<string name="list">Daftar</string>
|
||||
<string name="detailed_list">Daftar terperinci</string>
|
||||
<string name="text_clear_history_prompt">Bersihkan semua riwayat baca secara permanen\?</string>
|
||||
<string name="detailed_list">Daftar rinci</string>
|
||||
<string name="text_clear_history_prompt">Yakin ingin mereset history?</string>
|
||||
<string name="webtoon">Webtoon</string>
|
||||
<string name="read_mode">Mode baca</string>
|
||||
<string name="volume_buttons">Tombol volume</string>
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d nuovo capitolo</item>
|
||||
<item quantity="other">%1$d nuovi capitoli</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d élément</item>
|
||||
<item quantity="other">%1$d éléments</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d capitolo</item>
|
||||
<item quantity="other">%1$d capitoli</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">%1$d minuto fa</item>
|
||||
<item quantity="other">%1$d minuti fa</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%1$d ora fa</item>
|
||||
<item quantity="other">%1$d ore fa</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">%1$d giorno fa</item>
|
||||
<item quantity="other">%1$d giorni fa</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d nuovo capitolo</item>
|
||||
<item quantity="many">%1$d nuovi capitoli</item>
|
||||
<item quantity="other" />
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d elemento</item>
|
||||
<item quantity="many">%1$d elementi</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d capitolo</item>
|
||||
<item quantity="many">%1$d capitoli</item>
|
||||
<item quantity="other" />
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">%1$d minuto fa</item>
|
||||
<item quantity="many">%1$d minuti fa</item>
|
||||
<item quantity="other" />
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%1$d ora fa</item>
|
||||
<item quantity="many">%1$d ore fa</item>
|
||||
<item quantity="other" />
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">%1$d giorno fa</item>
|
||||
<item quantity="many">%1$d giorni fa</item>
|
||||
<item quantity="other" />
|
||||
</plurals>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="one">%1$d mese fa</item>
|
||||
<item quantity="many">%1$d mesi fa</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<string name="list_mode">Modalità elenco</string>
|
||||
<string name="grid">Griglia</string>
|
||||
<string name="detailed_list">Elenco dettagliato</string>
|
||||
<string name="text_delete_local_manga">Vuoi davvero eliminare «%s» dalla memoria locale del tuo dispositivo\?</string>
|
||||
<string name="text_delete_local_manga">Vuoi davvero eliminare «%s» dalla memoria locale del tuo dispositivo?</string>
|
||||
<string name="_s_deleted_from_local_storage">«%s» eliminato dall\'archiviazione locale</string>
|
||||
<string name="list">Lista</string>
|
||||
<string name="chapters">Capitoli</string>
|
||||
@@ -113,11 +113,11 @@
|
||||
<string name="other_storage">Altro spazio di archiviazione</string>
|
||||
<string name="cannot_find_available_storage">Impossibile trovare uno spazio di archiviazione disponibile</string>
|
||||
<string name="not_available">Non disponibile</string>
|
||||
<string name="manga_save_location">Posizione per lo scaricamento dei manga</string>
|
||||
<string name="manga_save_location">Cartella di scaricamento</string>
|
||||
<string name="pages_animation">Animazione delle pagine</string>
|
||||
<string name="recent_manga">Manga recenti</string>
|
||||
<string name="manga_shelf">Scaffale da manga</string>
|
||||
<string name="text_local_holder_secondary">Puoi salvarlo da fonti in linea o importarlo da un file.</string>
|
||||
<string name="text_local_holder_secondary">Salva qualcosa da un catalogo online o importalo da un file.</string>
|
||||
<string name="text_local_holder_primary">Non hai ancora salvato nessun manga</string>
|
||||
<string name="text_history_holder_secondary">Trova cosa leggere nella sezione «Esplora»</string>
|
||||
<string name="text_history_holder_primary">I manga che stai leggendo saranno visualizzati qui</string>
|
||||
@@ -543,4 +543,16 @@
|
||||
<string name="got_it">Ho capito</string>
|
||||
<string name="comics_archive_import_description">Puoi selezionare uno o più file .cbz o .zip, ogni file sarà riconosciuto come un manga a parte.</string>
|
||||
<string name="show_on_shelf">Mostra sullo Scaffale</string>
|
||||
<string name="state_upcoming">Prossime</string>
|
||||
<string name="by_name_reverse">Nome invertito</string>
|
||||
<string name="content_rating">Classificazione dei contenuti</string>
|
||||
<string name="genres_exclude">Escludi generi</string>
|
||||
<string name="rating_safe">Sicuro</string>
|
||||
<string name="rating_suggestive">Suggestivi</string>
|
||||
<string name="rating_adult">Adulto</string>
|
||||
<string name="default_tab">Scheda predefinita</string>
|
||||
<string name="mark_as_completed">Segna come completo</string>
|
||||
<string name="mark_as_completed_prompt">Segna il manga selezionato come completamente letto?
|
||||
\n
|
||||
\nAttenzione: l\'attuale avanzamento della lettura sarà perso.</string>
|
||||
</resources>
|
||||
24
app/src/main/res/values-ms/plurals.xml
Normal file
24
app/src/main/res/values-ms/plurals.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="other">%1$d bab baharu</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="other">%1$d bab</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="other">%1$d minit lalu</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="other">%1$d jam lalu</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="other">%1$d hari lalu</item>
|
||||
</plurals>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="other">%1$d bulan lalu</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="other">%1$d item</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
311
app/src/main/res/values-ms/strings.xml
Normal file
311
app/src/main/res/values-ms/strings.xml
Normal file
@@ -0,0 +1,311 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="enter_password">Masukkan kata laluan</string>
|
||||
<string name="protect_application_summary">Minta kata laluan ketika memulakan Kotatsu</string>
|
||||
<string name="repeat_password">Ulang kata laluan</string>
|
||||
<string name="zoom_mode_fit_center">Muat di tengah</string>
|
||||
<string name="create_category">Kategori baharu</string>
|
||||
<string name="scale_mode">Mod skala</string>
|
||||
<string name="zoom_mode_fit_height">Muat ke ketinggian</string>
|
||||
<string name="favourites">Kegemaran</string>
|
||||
<string name="history">Sejarah</string>
|
||||
<string name="network_error">Ralat rangkaian</string>
|
||||
<string name="details">Perincian</string>
|
||||
<string name="chapters">Bab</string>
|
||||
<string name="list">Senarai</string>
|
||||
<string name="detailed_list">Senarai terperinci</string>
|
||||
<string name="grid">Grid</string>
|
||||
<string name="list_mode">Mod senarai</string>
|
||||
<string name="settings">Tetapan</string>
|
||||
<string name="remote_sources">Sumber Manga</string>
|
||||
<string name="loading_">Memuat…</string>
|
||||
<string name="computing_">Mengira…</string>
|
||||
<string name="chapter_d_of_d">Bab %1$d daripada %2$d</string>
|
||||
<string name="close">Tutup</string>
|
||||
<string name="try_again">Cuba semula</string>
|
||||
<string name="clear_history">Padam sejarah</string>
|
||||
<string name="history_is_empty">Tiada sejarah lagi</string>
|
||||
<string name="read">Baca</string>
|
||||
<string name="add_new_category">Kategori baharu</string>
|
||||
<string name="add">Tambah</string>
|
||||
<string name="save">Simpan</string>
|
||||
<string name="share">Kongsi</string>
|
||||
<string name="create_shortcut">Buat pintasan…</string>
|
||||
<string name="search">Cari</string>
|
||||
<string name="search_manga">Cari manga</string>
|
||||
<string name="manga_downloading_">Memuat turun…</string>
|
||||
<string name="download_complete">Dimuat turun</string>
|
||||
<string name="downloads">Muat turun</string>
|
||||
<string name="by_name">Nama</string>
|
||||
<string name="popular">Popular</string>
|
||||
<string name="updated">Dikemaskini</string>
|
||||
<string name="newest">Terbaharu</string>
|
||||
<string name="by_rating">Rating</string>
|
||||
<string name="sort_order">Turutan isihan</string>
|
||||
<string name="filter">Tapis</string>
|
||||
<string name="light">Terang</string>
|
||||
<string name="dark">Gelap</string>
|
||||
<string name="automatic">Ikut sistem</string>
|
||||
<string name="pages">Muka surat</string>
|
||||
<string name="clear">Kosongkan</string>
|
||||
<string name="text_clear_history_prompt">Kosongkan semua sejarah pembacaan selama-lamanya?</string>
|
||||
<string name="remove">Buang</string>
|
||||
<string name="_s_deleted_from_local_storage">\"%s\" dibuang daripada storan tempatan</string>
|
||||
<string name="save_page">Simpan muka surat</string>
|
||||
<string name="share_image">Kongsi imej</string>
|
||||
<string name="_import">Import</string>
|
||||
<string name="text_file_not_supported">Pilih antara fail ZIP atau CBZ.</string>
|
||||
<string name="no_description">Tiada huraian</string>
|
||||
<string name="clear_pages_cache">Kosongkan cache muka surat</string>
|
||||
<string name="standard">Standard</string>
|
||||
<string name="webtoon">Webtoon</string>
|
||||
<string name="_continue">Sambung</string>
|
||||
<string name="error">Ralat</string>
|
||||
<string name="clear_search_history">Kosongkan sejarah carian</string>
|
||||
<string name="search_history_cleared">Dikosongkan</string>
|
||||
<string name="gestures_only">Gerak isyarat sahaja</string>
|
||||
<string name="internal_storage">Storan dalaman</string>
|
||||
<string name="external_storage">Storan luaran</string>
|
||||
<string name="app_update_available">Sebuah versi baru apl tersedia</string>
|
||||
<string name="open_in_browser">Buka dalam pelayar web</string>
|
||||
<string name="large_manga_save_confirm">Manga ini mempunyai %s. Simpan kesemuanya?</string>
|
||||
<string name="save_manga">Simpan</string>
|
||||
<string name="notifications">Notifikasi</string>
|
||||
<string name="new_chapters">Bab baharu</string>
|
||||
<string name="notification_sound">Suara pemberitahuan</string>
|
||||
<string name="light_indicator">Penunjuk LED</string>
|
||||
<string name="vibration">Getaran</string>
|
||||
<string name="remove_category">Buang</string>
|
||||
<string name="text_history_holder_primary">Apa yang anda baca akan dipaparkan di sini</string>
|
||||
<string name="text_shelf_holder_primary">Manga anda akan dipaparkan di sini</string>
|
||||
<string name="text_shelf_holder_secondary">Cari apa yang boleh dibaca melalui seksyen «Jelajah»</string>
|
||||
<string name="text_local_holder_primary">Simpan sesuatu dahulu</string>
|
||||
<string name="text_local_holder_secondary">Simpan sesuatu daripada katalog dalam talian atau import daripada fail.</string>
|
||||
<string name="manga_shelf">Rak</string>
|
||||
<string name="recent_manga">Terbaharu</string>
|
||||
<string name="cannot_find_available_storage">Tiada storan tersedia</string>
|
||||
<string name="other_storage">Storan lain</string>
|
||||
<string name="done">Tamat</string>
|
||||
<string name="favourites_category_empty">Kategori kosong</string>
|
||||
<string name="read_later">Baca kemudian</string>
|
||||
<string name="search_results">Hasil carian</string>
|
||||
<string name="track_sources">Cari kemas kini</string>
|
||||
<string name="protect_application">Lindung apl</string>
|
||||
<string name="passwords_mismatch">Kata laluan tidak padan</string>
|
||||
<string name="about">Tentang</string>
|
||||
<string name="app_version">Versi %s</string>
|
||||
<string name="check_for_updates">Semak kemas kini</string>
|
||||
<string name="no_update_available">Tiada kemas kini tersedia</string>
|
||||
<string name="right_to_left">Kanan-ke-kiri</string>
|
||||
<string name="zoom_mode_fit_width">Muat ke kelebaran</string>
|
||||
<string name="zoom_mode_keep_start">Kekalkan di permulaan</string>
|
||||
<string name="black_dark_theme">Hitam</string>
|
||||
<string name="black_dark_theme_summary">Menggunakan kurang kuasa pada skrin AMOLED</string>
|
||||
<string name="backup_restore">Penyandaran dan pemulihan</string>
|
||||
<string name="create_backup">Buat penyandaran data</string>
|
||||
<string name="restore_backup">Pulihkan daripada sandaran</string>
|
||||
<string name="data_restored">Dipulihkan</string>
|
||||
<string name="preparing_">Bersedia…</string>
|
||||
<string name="file_not_found">Fail tidak ditemui</string>
|
||||
<string name="data_restored_success">Semua data telah dipulih</string>
|
||||
<string name="data_restored_with_errors">Data telah dipulih, namun terdapat ralat</string>
|
||||
<string name="just_now">Sebentar tadi</string>
|
||||
<string name="yesterday">Semalam</string>
|
||||
<string name="long_ago">Lama dahulu</string>
|
||||
<string name="group">Kumpul</string>
|
||||
<string name="today">Hari ini</string>
|
||||
<string name="tap_to_try_again">Ketik untuk cuba semula</string>
|
||||
<string name="silent">Senyap</string>
|
||||
<string name="captcha_required">CAPTCHA diperlukan</string>
|
||||
<string name="captcha_solve">Selesai</string>
|
||||
<string name="clear_cookies">Kosongkan kuki</string>
|
||||
<string name="cookies_cleared">Semua kuki telah dibuang</string>
|
||||
<string name="clear_feed">Kosongkan siaran</string>
|
||||
<string name="text_clear_updates_feed_prompt">Kosongkan semua sejarah kemas kini selama-lamanya?</string>
|
||||
<string name="check_for_new_chapters">Semak untuk bab baharu</string>
|
||||
<string name="reverse">Songsang</string>
|
||||
<string name="sign_in">Log masuk</string>
|
||||
<string name="auth_required">Log masuk untuk melihat kandungan ini</string>
|
||||
<string name="next">Seterusnya</string>
|
||||
<string name="protect_application_subtitle">Masukkan kata laluan untuk memulakan apl dengan</string>
|
||||
<string name="confirm">Sah</string>
|
||||
<string name="password_length_hint">Kata laluan mesti mempunyai 4 aksara atau lebih</string>
|
||||
<string name="welcome">Selamat Datang</string>
|
||||
<string name="read_more">Baca lagi</string>
|
||||
<string name="auth_complete">Diizinkan</string>
|
||||
<string name="auth_not_supported_by">Log masuk pada %s tidak disokong</string>
|
||||
<string name="text_clear_cookies_prompt">Anda akan dilog keluar daripada semua sumber</string>
|
||||
<string name="genres">Genre</string>
|
||||
<string name="state_finished">Selesai</string>
|
||||
<string name="state_ongoing">Sedang berlangsung</string>
|
||||
<string name="system_default">Lalai</string>
|
||||
<string name="exclude_nsfw_from_history">Kecualikan manga NSFW daripada sejarah</string>
|
||||
<string name="show_pages_numbers">Muka surat bernombor</string>
|
||||
<string name="enabled_sources">Sumber digunakan</string>
|
||||
<string name="screenshots_policy">Polisi tangkapan skrin</string>
|
||||
<string name="screenshots_allow">Benarkan</string>
|
||||
<string name="screenshots_block_nsfw">Sekat pada NSFW</string>
|
||||
<string name="screenshots_block_all">Sentiasa sekat</string>
|
||||
<string name="suggestions">Pengesyoran</string>
|
||||
<string name="suggestions_enable">Bolehkan pengesyoran</string>
|
||||
<string name="suggestions_summary">Syor manga berdasarkan keutamaan</string>
|
||||
<string name="text_suggestion_holder">Mula baca manga dan anda akan mendapat pengesyoran peribadi</string>
|
||||
<string name="enabled">Dibolehkan</string>
|
||||
<string name="disabled">Dilumpuhkan</string>
|
||||
<string name="filter_load_error">Tidak boleh memuat senarai genre</string>
|
||||
<string name="reset_filter">Set semula tapisan</string>
|
||||
<string name="never">Jangan</string>
|
||||
<string name="only_using_wifi">Hanya pada Wi-Fi</string>
|
||||
<string name="always">Sentiasa</string>
|
||||
<string name="preload_pages">Pramuat muka surat</string>
|
||||
<string name="logged_in_as">Dilog masuk sebagai %s</string>
|
||||
<string name="nsfw">18+</string>
|
||||
<string name="various_languages">Pelbagai bahasa</string>
|
||||
<string name="search_chapters">Cari bab</string>
|
||||
<string name="chapters_empty">Tiada bab dalam manga ini</string>
|
||||
<string name="percent_string_pattern">%1$s%%</string>
|
||||
<string name="appearance">Penampilan</string>
|
||||
<string name="suggestions_updating">Pengemaskinian pengesyoran</string>
|
||||
<string name="suggestions_excluded_genres_summary">Tentukan genre yang anda tidak mahu lihat dalam pengesyoran</string>
|
||||
<string name="text_delete_local_manga_batch">Buang item yang dipilih daripada peranti selama-lamanya?</string>
|
||||
<string name="removal_completed">Pembuangan selesai</string>
|
||||
<string name="local_manga_processing">Pemprosesan manga disimpan</string>
|
||||
<string name="canceled">Dibatalkan</string>
|
||||
<string name="account_already_exists">Akaun telahpun wujud</string>
|
||||
<string name="back">Kembali</string>
|
||||
<string name="sync">Penyegerakkan</string>
|
||||
<string name="sync_title">Segerakkan data anda</string>
|
||||
<string name="email_enter_hint">Masukkan email anda untuk menyambung</string>
|
||||
<string name="hide">Sembunyi</string>
|
||||
<string name="new_sources_text">Sumber manga baharu tersedia</string>
|
||||
<string name="check_new_chapters_title">Semak untuk bab baharu dan beritahu mengenainya</string>
|
||||
<string name="show_notification_new_chapters_on">Anda akan menerima pemberitahuan mengenai kemas kini manga yang anda sedang baca</string>
|
||||
<string name="show_notification_new_chapters_off">Anda tidak akan menerima pemberitahuan namun bab baharu akan ditonjolkan dalam senarai</string>
|
||||
<string name="edit_category">Sunting kategori</string>
|
||||
<string name="tracking">Penjejakan</string>
|
||||
<string name="empty_favourite_categories">Tiada kategori kegemaran</string>
|
||||
<string name="logout">Log keluar</string>
|
||||
<string name="bookmark_add">Tambah penanda</string>
|
||||
<string name="bookmark_remove">Buang penanda</string>
|
||||
<string name="bookmark_removed">Penanda dibuang</string>
|
||||
<string name="bookmark_added">Penanda ditambah</string>
|
||||
<string name="undo">Buat asal</string>
|
||||
<string name="removed_from_history">Dibuang daripada sejarah</string>
|
||||
<string name="dns_over_https">DNS atas HTTPS</string>
|
||||
<string name="default_mode">Mod lalai</string>
|
||||
<string name="detect_reader_mode">Autokesan mod pembaca</string>
|
||||
<string name="disable_battery_optimization">Lumpuhkan pengoptimuman bateri</string>
|
||||
<string name="disable_battery_optimization_summary">Membantu penyemakan kemas kini dalam latar belakang</string>
|
||||
<string name="send">Hantar</string>
|
||||
<string name="status_planned">Dirancang</string>
|
||||
<string name="status_reading">Dibaca</string>
|
||||
<string name="status_re_reading">Dibaca semula</string>
|
||||
<string name="status_completed">Selesai</string>
|
||||
<string name="status_on_hold">Dijeda</string>
|
||||
<string name="status_dropped">Dihentikan</string>
|
||||
<string name="disable_all">Lumpuhkan semua</string>
|
||||
<string name="use_fingerprint">Guna cap jari sekiranya tersedia</string>
|
||||
<string name="appwidget_shelf_description">Manga daripada kegemaran anda</string>
|
||||
<string name="appwidget_recent_description">Manga yang anda baru baca</string>
|
||||
<string name="report">Lapor</string>
|
||||
<string name="show_reading_indicators">Tunjukkan penunjuk kemajuan pembacaan</string>
|
||||
<string name="data_deletion">Pembuangan data</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Manga ditanda sebagai NSFW tidak akan ditambahkan dalam sejarah dan kemajuan tidak akan disimpan</string>
|
||||
<string name="show_all">Tunjuk semua</string>
|
||||
<string name="invalid_domain_message">Domain tidak sah</string>
|
||||
<string name="select_range">Pilih julat</string>
|
||||
<string name="clear_all_history">Kosongkan semua sejarah</string>
|
||||
<string name="last_2_hours">2 jam lepas</string>
|
||||
<string name="history_cleared">Sejarah dikosongkan</string>
|
||||
<string name="manage">Urus</string>
|
||||
<string name="no_bookmarks_yet">Tiada penanda lagi</string>
|
||||
<string name="no_bookmarks_summary">Anda boleh membuat penanda ketika membaca manga</string>
|
||||
<string name="no_manga_sources">Tiada sumber manga</string>
|
||||
<string name="no_manga_sources_text">Bolehkan sumber manga untuk membaca manga di dalam talian</string>
|
||||
<string name="random">Rawak</string>
|
||||
<string name="categories_delete_confirm">Adakah anda pasti anda mahu membuang kategori kegemaran yang dipilih?
|
||||
\nSemua manga di dalamnya akan hilang dan ini tidak boleh diundur semula.</string>
|
||||
<string name="reorder">Susun semula</string>
|
||||
<string name="empty">Kosong</string>
|
||||
<string name="explore">Jelajah</string>
|
||||
<string name="confirm_exit">Tekan Kembali sekali lagi untuk keluar</string>
|
||||
<string name="exit_confirmation_summary">Tekan Kembali dua kali untuk keluar apl</string>
|
||||
<string name="exit_confirmation">Pengesahan keluar</string>
|
||||
<string name="pages_cache">Cache muka surat</string>
|
||||
<string name="other_cache">Cache lain-lain</string>
|
||||
<string name="storage_usage">Penggunaan storan</string>
|
||||
<string name="available">Tersedia</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="removed_from_favourites">Dibuang daripada kegemaran</string>
|
||||
<string name="options">Pilihan</string>
|
||||
<string name="local_storage">Storan tempatan</string>
|
||||
<string name="reader_mode_hint">Tetapan yang telah dipilih akan diperingati untuk manga ini</string>
|
||||
<string name="backup_information">Anda boleh membuat sandaran sejarah dan kegemaran anda dan pulihkannya</string>
|
||||
<string name="default_s">Lalai: %s</string>
|
||||
<string name="text_clear_search_history_prompt">Buang semua carian terkini selama-lamanya?</string>
|
||||
<string name="tracker_warning">Sesetengah peranti mempunyai kelakuan sistem berlainan, yang mampu merosakkan tugasan latar belakang.</string>
|
||||
<string name="queued">Dibariskan</string>
|
||||
<string name="chapter_is_missing">Bab ini hilang</string>
|
||||
<string name="backup_saved">Sandaran disimpan</string>
|
||||
<string name="about_app_translation">Terjemahan</string>
|
||||
<string name="about_app_translation_summary">Terjemahkan apl ini</string>
|
||||
<string name="error_occurred">Sebuah ralat telah berlaku</string>
|
||||
<string name="share_s">Kongsi %s</string>
|
||||
<string name="available_sources">Sumber tersedia</string>
|
||||
<string name="nothing_found">Tiada dijumpai</string>
|
||||
<string name="suggestions_info">Semua data hanya dianalisa secara tempatan pada peranti ini dan tidak dihantar ke mana-mana.</string>
|
||||
<string name="you_have_not_favourites_yet">Tiada kegemaran lagi</string>
|
||||
<string name="exclude_nsfw_from_suggestions">Jangan syor manga NSFW</string>
|
||||
<string name="processing_">Memproses…</string>
|
||||
<string name="onboard_text">Pilih bahasa untuk manga yang anda mahu baca. Anda boleh mengubah ini kemudian melalui tetapan.</string>
|
||||
<string name="add_to_favourites">Tambah dalam kegemaran</string>
|
||||
<string name="theme">Tema</string>
|
||||
<string name="suggestions_excluded_genres">Kecualikan genre</string>
|
||||
<string name="download_slowdown_summary">Membantu mengelakkan alamat IP anda disekat</string>
|
||||
<string name="delete">Buang</string>
|
||||
<string name="search_on_s">Cari pada %s</string>
|
||||
<string name="clear_thumbs_cache">Kosongkan cache imej kenit</string>
|
||||
<string name="chapters_will_removed_background">Bab akan dibuang dalam latar belakang</string>
|
||||
<string name="operation_not_supported">Operasi ini tidak disokong</string>
|
||||
<string name="delete_manga">Buang manga</string>
|
||||
<string name="taps_on_edges">Ketik tepi</string>
|
||||
<string name="page_saved">Disimpan</string>
|
||||
<string name="domain">Domain</string>
|
||||
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d daripada %2$d pada</string>
|
||||
<string name="read_mode">Mod pembacaan</string>
|
||||
<string name="text_delete_local_manga">Buang \"%s\" selama-lamanya daripada peranti?</string>
|
||||
<string name="reader_settings">Tetapan pembaca</string>
|
||||
<string name="download">Muat turun</string>
|
||||
<string name="notifications_settings">Tetapan pemberitahuan</string>
|
||||
<string name="grid_size">Saiz grid</string>
|
||||
<string name="switch_pages">Tukar muka surat</string>
|
||||
<string name="text_empty_holder_primary">Ianya agak kosong di sini…</string>
|
||||
<string name="text_search_holder_secondary">Cuba untuk merumuskan semula carian.</string>
|
||||
<string name="updates">Kemas kini</string>
|
||||
<string name="all_favourites">Semua kegemaran</string>
|
||||
<string name="favourites_categories">Kategori kegemaran</string>
|
||||
<string name="text_history_holder_secondary">Cari apa yang boleh dibaca melalui seksyen «Jelajah»</string>
|
||||
<string name="pages_animation">Animasi muka surat</string>
|
||||
<string name="manga_save_location">Folder muat turun</string>
|
||||
<string name="not_available">Tidak tersedia</string>
|
||||
<string name="notifications_enable">Bolehkan pemberitahuan</string>
|
||||
<string name="name">Nama</string>
|
||||
<string name="edit">Sunting</string>
|
||||
<string name="bookmarks">Penanda</string>
|
||||
<string name="clear_cookies_summary">Boleh membantu sekiranya terdapat isu. Semua keizinan akan dinyahsahkan</string>
|
||||
<string name="detect_reader_mode_summary">Kesan sekiranya manga adalah webtoon secara automatik</string>
|
||||
<string name="show_reading_indicators_summary">Tunjuk peratusan dibaca dalam sejarah dan kegemaran</string>
|
||||
<string name="bookmarks_removed">Penanda dibuang</string>
|
||||
<string name="crash_text">Sebuah ralat telah berlaku. Sila hantar laporan pepijat kepada pembangun untuk membantu kami membetulkannya.</string>
|
||||
<string name="saved_manga">Manga disimpan</string>
|
||||
<string name="text_feed_holder">Bab-bab baharu bacaan anda ditunjukkan di sini</string>
|
||||
<string name="size_s">Saiz: %s</string>
|
||||
<string name="new_version_s">Versi baharu: %s</string>
|
||||
<string name="clear_updates_feed">Kosongkan siaran kemaskini</string>
|
||||
<string name="updates_feed_cleared">Dikosongkan</string>
|
||||
<string name="wrong_password">Kata laluan salah</string>
|
||||
<string name="rotate_screen">Putar skrin</string>
|
||||
<string name="update">Kemas kini</string>
|
||||
<string name="feed_will_update_soon">Kemaskini siaran akan bermula sebentar</string>
|
||||
<string name="dont_check">Jangan semak</string>
|
||||
</resources>
|
||||
2
app/src/main/res/values-pa-rPK/strings.xml
Normal file
2
app/src/main/res/values-pa-rPK/strings.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
||||
21
app/src/main/res/values-pa/strings.xml
Normal file
21
app/src/main/res/values-pa/strings.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="favourites">ਮਨਪਸੰਦ</string>
|
||||
<string name="history">ਇਤਿਹਾਸ</string>
|
||||
<string name="error_occurred">ਇੱਕ ਗਲਤੀ ਆਈ ਹੈ</string>
|
||||
<string name="details">ਵੇਰਵਾ</string>
|
||||
<string name="chapters">ਅਧਿਆਇ</string>
|
||||
<string name="grid">ਗਰਿੱਡ</string>
|
||||
<string name="list_mode">ਲਿਸਟ ਮੋਡ</string>
|
||||
<string name="settings">ਸੈਟਿੰਗ</string>
|
||||
<string name="remote_sources">ਮਾਂਗਾ ਸਰੋਤ</string>
|
||||
<string name="loading_">ਲੋਡ…</string>
|
||||
<string name="computing_">ਹਿਸਾਬ ਲਾ ਰਿਹਾ</string>
|
||||
<string name="chapter_d_of_d">%2$d ਦਾ %1$d ਅਧਿਆਇ</string>
|
||||
<string name="close">ਬੰਦ</string>
|
||||
<string name="try_again">ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ</string>
|
||||
<string name="local_storage">ਸਥਾਨਕ ਸਟੋਰੇਜ</string>
|
||||
<string name="network_error">ਨੈੱਟਵਰਕ ਖਰਾਬ</string>
|
||||
<string name="list">ਲਿਸਟ</string>
|
||||
<string name="detailed_list">ਵੇਰਵਾ ਲਿਸਟ</string>
|
||||
</resources>
|
||||
@@ -543,4 +543,16 @@
|
||||
<string name="backup_date_">Data de backup: %s</string>
|
||||
<string name="grayscale">Tons de Cinza</string>
|
||||
<string name="sync_auth">Faça login para sincronizar a conta</string>
|
||||
<string name="state_upcoming">por vir</string>
|
||||
<string name="by_name_reverse">Nome invertido</string>
|
||||
<string name="rating_safe">seguro</string>
|
||||
<string name="rating_suggestive">sugestivo</string>
|
||||
<string name="genres_exclude">excluir gênero</string>
|
||||
<string name="rating_adult">Adulto</string>
|
||||
<string name="default_tab">Aba padrão</string>
|
||||
<string name="content_rating">Classificação do Conteúdo</string>
|
||||
<string name="mark_as_completed_prompt">Deseja marcar o mangá selecionado como completo?
|
||||
\n
|
||||
\nAviso: o progresso de leitura atual será perdido.</string>
|
||||
<string name="mark_as_completed">Marcar como completo</string>
|
||||
</resources>
|
||||
@@ -3,36 +3,36 @@
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">%1$d minuto atrás</item>
|
||||
<item quantity="many">%1$d minutos atrás</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other">%1$d minutos atrás</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d item</item>
|
||||
<item quantity="many">%1$d itens</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="one">%1$d elemento</item>
|
||||
<item quantity="many">%1$d elementos</item>
|
||||
<item quantity="other">%1$d elementos</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d capítulo</item>
|
||||
<item quantity="many">%1$d capítulos</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other">%1$d capítulos</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d novo capítulo</item>
|
||||
<item quantity="many">%1$d novos capítulos</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other">%1$d capítulos novos</item>
|
||||
</plurals>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="one">%1$d mês atrás</item>
|
||||
<item quantity="many">%1$d meses atrás</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other">%1$d meses atrás</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">%1$d dia atrás</item>
|
||||
<item quantity="many">%1$d dias atrás</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other">%1$d dias atrás</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%1$d hora atrás</item>
|
||||
<item quantity="many">%1$d horas atrás</item>
|
||||
<item quantity="other"></item>
|
||||
<item quantity="other">%1$d horas atrás</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -543,4 +543,12 @@
|
||||
<string name="backups_output_directory">Local de saída de backups</string>
|
||||
<string name="content_type_other">Outros</string>
|
||||
<string name="sync_auth">Faça login para sincronizar a conta</string>
|
||||
<string name="state_upcoming">Próximo</string>
|
||||
<string name="content_rating">Avaliação do conteúdo</string>
|
||||
<string name="genres_exclude">Excluir gêneros</string>
|
||||
<string name="rating_safe">Seguro</string>
|
||||
<string name="rating_suggestive">Sugestivo</string>
|
||||
<string name="rating_adult">Adulto</string>
|
||||
<string name="default_tab">Guia padrão</string>
|
||||
<string name="by_name_reverse">Nome invertido</string>
|
||||
</resources>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user