Merge branch 'devel' into feature/suggestions

pull/100/head
Koitharu 4 years ago
commit 3afa782e91
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -83,7 +83,7 @@ dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1' implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'com.google.android.material:material:1.6.0-alpha02' implementation 'com.google.android.material:material:1.6.0-alpha03'
//noinspection LifecycleAnnotationProcessorWithJava8 //noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.4.1' kapt 'androidx.lifecycle:lifecycle-compiler:2.4.1'

@ -3,15 +3,14 @@ package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
import com.google.android.material.imageview.ShapeableImageView
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import kotlin.math.roundToInt import kotlin.math.roundToInt
class CoverImageView @JvmOverloads constructor( class CoverImageView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,
) : AppCompatImageView(context, attrs, defStyleAttr) { ) : ShapeableImageView(context, attrs, defStyleAttr) {
private var orientation: Int = HORIZONTAL private var orientation: Int = HORIZONTAL

@ -13,6 +13,9 @@ abstract class TracksDao {
@Query("SELECT * FROM tracks WHERE manga_id = :mangaId") @Query("SELECT * FROM tracks WHERE manga_id = :mangaId")
abstract suspend fun find(mangaId: Long): TrackEntity? abstract suspend fun find(mangaId: Long): TrackEntity?
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
abstract suspend fun findNewChapters(mangaId: Long): Int?
@Query("DELETE FROM tracks") @Query("DELETE FROM tracks")
abstract suspend fun clear() abstract suspend fun clear()

@ -56,10 +56,10 @@ class AppSettings(context: Context) {
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true) get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) } set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) }
val appUpdateAuto: Boolean val isUpdateCheckingEnabled: Boolean
get() = prefs.getBoolean(KEY_APP_UPDATE_AUTO, true) get() = prefs.getBoolean(KEY_APP_UPDATE_AUTO, true)
var appUpdate: Long var lastUpdateCheckTimestamp: Long
get() = prefs.getLong(KEY_APP_UPDATE, 0L) get() = prefs.getLong(KEY_APP_UPDATE, 0L)
set(value) = prefs.edit { putLong(KEY_APP_UPDATE, value) } set(value) = prefs.edit { putLong(KEY_APP_UPDATE, value) }
@ -123,6 +123,12 @@ class AppSettings(context: Context) {
val isPagesNumbersEnabled: Boolean val isPagesNumbersEnabled: Boolean
get() = prefs.getBoolean(KEY_PAGES_NUMBERS, false) get() = prefs.getBoolean(KEY_PAGES_NUMBERS, false)
val screenshotsPolicy: ScreenshotsPolicy
get() = runCatching {
val key = prefs.getString(KEY_SCREENSHOTS_POLICY, null)?.uppercase(Locale.ROOT)
if (key == null) ScreenshotsPolicy.ALLOW else ScreenshotsPolicy.valueOf(key)
}.getOrDefault(ScreenshotsPolicy.ALLOW)
var mangaStorageDir: File? var mangaStorageDir: File?
get() = prefs.getString(KEY_LOCAL_STORAGE, null)?.let { get() = prefs.getString(KEY_LOCAL_STORAGE, null)?.let {
File(it) File(it)
@ -230,6 +236,7 @@ class AppSettings(context: Context) {
const val KEY_REVERSE_CHAPTERS = "reverse_chapters" const val KEY_REVERSE_CHAPTERS = "reverse_chapters"
const val KEY_HISTORY_EXCLUDE_NSFW = "history_exclude_nsfw" const val KEY_HISTORY_EXCLUDE_NSFW = "history_exclude_nsfw"
const val KEY_PAGES_NUMBERS = "pages_numbers" const val KEY_PAGES_NUMBERS = "pages_numbers"
const val KEY_SCREENSHOTS_POLICY = "screenshots_policy"
const val KEY_SUGGESTIONS = "suggestions" const val KEY_SUGGESTIONS = "suggestions"
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw" const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"

@ -0,0 +1,7 @@
package org.koitharu.kotatsu.core.prefs
enum class ScreenshotsPolicy {
// Do not rename this
ALLOW, BLOCK_NSFW, BLOCK_ALL;
}

@ -13,7 +13,7 @@ val favouritesModule
single { FavouritesRepository(get()) } single { FavouritesRepository(get()) }
viewModel { categoryId -> viewModel { categoryId ->
FavouritesListViewModel(categoryId.get(), get(), get()) FavouritesListViewModel(categoryId.get(), get(), get(), get())
} }
viewModel { FavouritesCategoriesViewModel(get()) } viewModel { FavouritesCategoriesViewModel(get()) }
viewModel { manga -> viewModel { manga ->

@ -148,7 +148,7 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
override fun onMove( override fun onMove(
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder target: RecyclerView.ViewHolder,
): Boolean = true ): Boolean = true
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
@ -160,7 +160,7 @@ class CategoriesActivity : BaseActivity<ActivityCategoriesBinding>(),
target: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder,
toPos: Int, toPos: Int,
x: Int, x: Int,
y: Int y: Int,
) { ) {
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y) super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
viewModel.reorderCategories(fromPos, toPos) viewModel.reorderCategories(fromPos, toPos)

@ -9,18 +9,21 @@ import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.list.domain.CountersProvider
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
class FavouritesListViewModel( class FavouritesListViewModel(
private val categoryId: Long, private val categoryId: Long,
private val repository: FavouritesRepository, private val repository: FavouritesRepository,
settings: AppSettings private val trackingRepository: TrackingRepository,
) : MangaListViewModel(settings) { settings: AppSettings,
) : MangaListViewModel(settings), CountersProvider {
override val content = combine( override val content = combine(
if (categoryId == 0L) { if (categoryId == 0L) {
@ -42,7 +45,7 @@ class FavouritesListViewModel(
} }
) )
) )
else -> list.toUi(mode) else -> list.toUi(mode, this)
} }
}.catch { }.catch {
emit(listOf(it.toErrorState(canRetry = false))) emit(listOf(it.toErrorState(canRetry = false)))
@ -61,4 +64,8 @@ class FavouritesListViewModel(
} }
} }
} }
override suspend fun getCounter(mangaId: Long): Int {
return trackingRepository.getNewChaptersCount(mangaId)
}
} }

@ -9,5 +9,5 @@ val historyModule
get() = module { get() = module {
single { HistoryRepository(get(), get(), get()) } single { HistoryRepository(get(), get(), get()) }
viewModel { HistoryListViewModel(get(), get(), get()) } viewModel { HistoryListViewModel(get(), get(), get(), get()) }
} }

@ -14,6 +14,7 @@ import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.MangaWithHistory import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.daysDiff import org.koitharu.kotatsu.utils.ext.daysDiff
@ -24,7 +25,8 @@ import java.util.concurrent.TimeUnit
class HistoryListViewModel( class HistoryListViewModel(
private val repository: HistoryRepository, private val repository: HistoryRepository,
private val settings: AppSettings, private val settings: AppSettings,
private val shortcutsRepository: ShortcutsRepository private val shortcutsRepository: ShortcutsRepository,
private val trackingRepository: TrackingRepository,
) : MangaListViewModel(settings) { ) : MangaListViewModel(settings) {
val onItemRemoved = SingleLiveEvent<Manga>() val onItemRemoved = SingleLiveEvent<Manga>()
@ -75,7 +77,7 @@ class HistoryListViewModel(
settings.historyGrouping = isGroupingEnabled settings.historyGrouping = isGroupingEnabled
} }
private fun mapList( private suspend fun mapList(
list: List<MangaWithHistory>, list: List<MangaWithHistory>,
grouped: Boolean, grouped: Boolean,
mode: ListMode mode: ListMode
@ -93,10 +95,11 @@ class HistoryListViewModel(
} }
prevDate = date prevDate = date
} }
val counter = trackingRepository.getNewChaptersCount(manga.id)
result += when (mode) { result += when (mode) {
ListMode.LIST -> manga.toListModel() ListMode.LIST -> manga.toListModel(counter)
ListMode.DETAILED_LIST -> manga.toListDetailedModel() ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter)
ListMode.GRID -> manga.toGridModel() ListMode.GRID -> manga.toGridModel(counter)
} }
} }
return result return result

@ -0,0 +1,6 @@
package org.koitharu.kotatsu.list.domain
fun interface CountersProvider {
suspend fun getCounter(mangaId: Long): Int
}

@ -257,7 +257,7 @@ abstract class MangaListFragment : BaseFragment<FragmentListBinding>(),
} }
ListMode.DETAILED_LIST -> { ListMode.DETAILED_LIST -> {
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
val spacing = resources.getDimensionPixelOffset(R.dimen.grid_spacing) val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
updatePadding(left = spacing, right = spacing) updatePadding(left = spacing, right = spacing)
addItemDecoration(SpacingItemDecoration(spacing)) addItemDecoration(SpacingItemDecoration(spacing))
} }

@ -0,0 +1,44 @@
@file:SuppressLint("UnsafeOptInUsageError")
package org.koitharu.kotatsu.list.ui.adapter
import android.annotation.SuppressLint
import android.view.View
import androidx.core.view.doOnNextLayout
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
import org.koitharu.kotatsu.R
fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {
return if (counter > 0) {
val badgeDrawable = badge ?: initBadge(this)
badgeDrawable.number = counter
badgeDrawable.isVisible = true
badgeDrawable.align()
badgeDrawable
} else {
badge?.isVisible = false
badge
}
}
fun View.clearBadge(badge: BadgeDrawable?) {
BadgeUtils.detachBadgeDrawable(badge, this)
}
private fun initBadge(anchor: View): BadgeDrawable {
val badge = BadgeDrawable.create(anchor.context)
val resources = anchor.resources
badge.maxCharacterCount = resources.getInteger(R.integer.manga_badge_max_character_count)
badge.horizontalOffsetWithoutText = resources.getDimensionPixelOffset(R.dimen.manga_badge_offset_horizontal)
badge.verticalOffsetWithoutText = resources.getDimensionPixelOffset(R.dimen.manga_badge_offset_vertical)
anchor.doOnNextLayout {
BadgeUtils.attachBadgeDrawable(badge, it)
badge.align()
}
return badge
}
private fun BadgeDrawable.align() {
horizontalOffsetWithText = horizontalOffsetWithoutText + intrinsicWidth / 2
verticalOffsetWithText = verticalOffsetWithoutText + intrinsicHeight / 2
}

@ -4,6 +4,7 @@ import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import coil.request.Disposable import coil.request.Disposable
import coil.util.CoilUtils import coil.util.CoilUtils
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
@ -24,6 +25,7 @@ fun mangaGridItemAD(
) { ) {
var imageRequest: Disposable? = null var imageRequest: Disposable? = null
var badge: BadgeDrawable? = null
itemView.setOnClickListener { itemView.setOnClickListener {
clickListener.onItemClick(item.manga, it) clickListener.onItemClick(item.manga, it)
@ -43,9 +45,12 @@ fun mangaGridItemAD(
.allowRgb565(true) .allowRgb565(true)
.lifecycle(lifecycleOwner) .lifecycle(lifecycleOwner)
.enqueueWith(coil) .enqueueWith(coil)
badge = itemView.bindBadge(badge, item.counter)
} }
onViewRecycled { onViewRecycled {
itemView.clearBadge(badge)
badge = null
imageRequest?.dispose() imageRequest?.dispose()
CoilUtils.clear(binding.imageViewCover) CoilUtils.clear(binding.imageViewCover)
binding.imageViewCover.setImageDrawable(null) binding.imageViewCover.setImageDrawable(null)

@ -4,6 +4,7 @@ import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import coil.request.Disposable import coil.request.Disposable
import coil.util.CoilUtils import coil.util.CoilUtils
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
@ -25,6 +26,7 @@ fun mangaListDetailedItemAD(
) { ) {
var imageRequest: Disposable? = null var imageRequest: Disposable? = null
var badge: BadgeDrawable? = null
itemView.setOnClickListener { itemView.setOnClickListener {
clickListener.onItemClick(item.manga, it) clickListener.onItemClick(item.manga, it)
@ -47,9 +49,12 @@ fun mangaListDetailedItemAD(
.enqueueWith(coil) .enqueueWith(coil)
binding.textViewRating.textAndVisible = item.rating binding.textViewRating.textAndVisible = item.rating
binding.textViewTags.text = item.tags binding.textViewTags.text = item.tags
itemView.bindBadge(badge, item.counter)
} }
onViewRecycled { onViewRecycled {
itemView.clearBadge(badge)
badge = null
imageRequest?.dispose() imageRequest?.dispose()
CoilUtils.clear(binding.imageViewCover) CoilUtils.clear(binding.imageViewCover)
binding.imageViewCover.setImageDrawable(null) binding.imageViewCover.setImageDrawable(null)

@ -4,6 +4,7 @@ import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader import coil.ImageLoader
import coil.request.Disposable import coil.request.Disposable
import coil.util.CoilUtils import coil.util.CoilUtils
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
@ -25,6 +26,7 @@ fun mangaListItemAD(
) { ) {
var imageRequest: Disposable? = null var imageRequest: Disposable? = null
var badge: BadgeDrawable? = null
itemView.setOnClickListener { itemView.setOnClickListener {
clickListener.onItemClick(item.manga, it) clickListener.onItemClick(item.manga, it)
@ -45,9 +47,12 @@ fun mangaListItemAD(
.allowRgb565(true) .allowRgb565(true)
.lifecycle(lifecycleOwner) .lifecycle(lifecycleOwner)
.enqueueWith(coil) .enqueueWith(coil)
itemView.bindBadge(badge, item.counter)
} }
onViewRecycled { onViewRecycled {
itemView.clearBadge(badge)
badge = null
imageRequest?.dispose() imageRequest?.dispose()
CoilUtils.clear(binding.imageViewCover) CoilUtils.clear(binding.imageViewCover)
binding.imageViewCover.setImageDrawable(null) binding.imageViewCover.setImageDrawable(null)

@ -6,44 +6,71 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.list.domain.CountersProvider
fun Manga.toListModel() = MangaListModel( fun Manga.toListModel(counter: Int) = MangaListModel(
id = id, id = id,
title = title, title = title,
subtitle = tags.joinToString(", ") { it.title }, subtitle = tags.joinToString(", ") { it.title },
coverUrl = coverUrl, coverUrl = coverUrl,
manga = this manga = this,
counter = counter,
) )
fun Manga.toListDetailedModel() = MangaListDetailedModel( fun Manga.toListDetailedModel(counter: Int) = MangaListDetailedModel(
id = id, id = id,
title = title, title = title,
subtitle = altTitle, subtitle = altTitle,
rating = if (rating == Manga.NO_RATING) null else String.format("%.1f", rating * 5), rating = if (rating == Manga.NO_RATING) null else String.format("%.1f", rating * 5),
tags = tags.joinToString(", ") { it.title }, tags = tags.joinToString(", ") { it.title },
coverUrl = coverUrl, coverUrl = coverUrl,
manga = this manga = this,
counter = counter,
) )
fun Manga.toGridModel() = MangaGridModel( fun Manga.toGridModel(counter: Int) = MangaGridModel(
id = id, id = id,
title = title, title = title,
coverUrl = coverUrl, coverUrl = coverUrl,
manga = this manga = this,
counter = counter,
) )
fun List<Manga>.toUi(mode: ListMode): List<ListModel> = when (mode) { suspend fun List<Manga>.toUi(
ListMode.LIST -> map(Manga::toListModel) mode: ListMode,
ListMode.DETAILED_LIST -> map(Manga::toListDetailedModel) countersProvider: CountersProvider,
ListMode.GRID -> map(Manga::toGridModel) ): List<ListModel> = when (mode) {
ListMode.LIST -> map { it.toListModel(countersProvider.getCounter(it.id)) }
ListMode.DETAILED_LIST -> map { it.toListDetailedModel(countersProvider.getCounter(it.id)) }
ListMode.GRID -> map { it.toGridModel(countersProvider.getCounter(it.id)) }
} }
fun <C : MutableCollection<ListModel>> List<Manga>.toUi(destination: C, mode: ListMode): C = suspend fun <C : MutableCollection<ListModel>> List<Manga>.toUi(
when (mode) { destination: C,
ListMode.LIST -> mapTo(destination, Manga::toListModel) mode: ListMode,
ListMode.DETAILED_LIST -> mapTo(destination, Manga::toListDetailedModel) countersProvider: CountersProvider,
ListMode.GRID -> mapTo(destination, Manga::toGridModel) ): C = when (mode) {
} ListMode.LIST -> mapTo(destination) { it.toListModel(countersProvider.getCounter(it.id)) }
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(countersProvider.getCounter(it.id)) }
ListMode.GRID -> mapTo(destination) { it.toGridModel(countersProvider.getCounter(it.id)) }
}
fun List<Manga>.toUi(
mode: ListMode,
): List<ListModel> = when (mode) {
ListMode.LIST -> map { it.toListModel(0) }
ListMode.DETAILED_LIST -> map { it.toListDetailedModel(0) }
ListMode.GRID -> map { it.toGridModel(0) }
}
fun <C : MutableCollection<ListModel>> List<Manga>.toUi(
destination: C,
mode: ListMode,
): C = when (mode) {
ListMode.LIST -> mapTo(destination) { it.toListModel(0) }
ListMode.DETAILED_LIST -> mapTo(destination) { it.toListDetailedModel(0) }
ListMode.GRID -> mapTo(destination) { it.toGridModel(0) }
}
fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState( fun Throwable.toErrorState(canRetry: Boolean = true) = ErrorState(
exception = this, exception = this,

@ -6,5 +6,6 @@ data class MangaGridModel(
val id: Long, val id: Long,
val title: String, val title: String,
val coverUrl: String, val coverUrl: String,
val manga: Manga val manga: Manga,
val counter: Int,
) : ListModel ) : ListModel

@ -9,5 +9,6 @@ data class MangaListDetailedModel(
val tags: String, val tags: String,
val coverUrl: String, val coverUrl: String,
val rating: String?, val rating: String?,
val manga: Manga val manga: Manga,
val counter: Int,
) : ListModel ) : ListModel

@ -7,5 +7,6 @@ data class MangaListModel(
val title: String, val title: String,
val subtitle: String, val subtitle: String,
val coverUrl: String, val coverUrl: String,
val manga: Manga val manga: Manga,
val counter: Int,
) : ListModel ) : ListModel

@ -43,7 +43,13 @@ class LocalListViewModel(
when { when {
error != null -> listOf(error.toErrorState(canRetry = true)) error != null -> listOf(error.toErrorState(canRetry = true))
list == null -> listOf(LoadingState) list == null -> listOf(LoadingState)
list.isEmpty() -> listOf(EmptyState(R.drawable.ic_storage, R.string.text_local_holder_primary, R.string.text_local_holder_secondary)) list.isEmpty() -> listOf(
EmptyState(
R.drawable.ic_storage,
R.string.text_local_holder_primary,
R.string.text_local_holder_secondary
)
)
else -> ArrayList<ListModel>(list.size + 1).apply { else -> ArrayList<ListModel>(list.size + 1).apply {
add(headerModel) add(headerModel)
list.toUi(this, mode) list.toUi(this, mode)

@ -15,12 +15,16 @@ import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -28,6 +32,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSection import org.koitharu.kotatsu.core.prefs.AppSection
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ActivityMainBinding import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.databinding.NavigationHeaderBinding import org.koitharu.kotatsu.databinding.NavigationHeaderBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
@ -356,10 +361,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>(),
} }
private fun onFirstStart() { private fun onFirstStart() {
TrackWorker.setup(applicationContext) lifecycleScope.launch(Dispatchers.Default) {
SuggestionsWorker.setup(applicationContext) TrackWorker.setup(applicationContext)
AppUpdateChecker(this@MainActivity).launchIfNeeded() SuggestionsWorker.setup(applicationContext)
OnboardDialogFragment.showWelcome(get(), supportFragmentManager) AppUpdateChecker(this@MainActivity).checkIfNeeded()
if (!get<AppSettings>().isSourcesSelected) {
withContext(Dispatchers.Main) {
OnboardDialogFragment.showWelcome(supportFragmentManager)
}
}
}
} }
private companion object { private companion object {

@ -7,6 +7,7 @@ import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
@ -23,6 +24,7 @@ class ProtectActivity : BaseActivity<ActivityProtectBinding>(), TextView.OnEdito
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
setContentView(ActivityProtectBinding.inflate(layoutInflater)) setContentView(ActivityProtectBinding.inflate(layoutInflater))
binding.editPassword.setOnEditorActionListener(this) binding.editPassword.setOnEditorActionListener(this)
binding.editPassword.addTextChangedListener(this) binding.editPassword.addTextChangedListener(this)

@ -94,6 +94,7 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
viewModel.content.observe(this) { viewModel.content.observe(this) {
onLoadingStateChanged(viewModel.isLoading.value == true) onLoadingStateChanged(viewModel.isLoading.value == true)
} }
viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure)
} }
private fun onInitReader(mode: ReaderMode) { private fun onInitReader(mode: ReaderMode) {
@ -299,6 +300,14 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
}.show() }.show()
} }
private fun setWindowSecure(isSecure: Boolean) {
if (isSecure) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
private fun setUiIsVisible(isUiVisible: Boolean) { private fun setUiIsVisible(isUiVisible: Boolean) {
if (binding.appbarTop.isVisible != isUiVisible) { if (binding.appbarTop.isVisible != isUiVisible) {
if (isUiVisible) { if (isUiVisible) {

@ -20,6 +20,7 @@ import org.koitharu.kotatsu.core.os.ShortcutsRepository
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy
import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
@ -69,6 +70,17 @@ class ReaderViewModel(
.onStart { emit(settings.readerAnimation) } .onStart { emit(settings.readerAnimation) }
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.IO) .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.IO)
val isScreenshotsBlockEnabled = combine(
mangaData,
settings.observe()
.filter { it == AppSettings.KEY_SCREENSHOTS_POLICY }
.onStart { emit("") }
.map { settings.screenshotsPolicy },
) { manga, policy ->
policy == ScreenshotsPolicy.BLOCK_ALL ||
(policy == ScreenshotsPolicy.BLOCK_NSFW && manga != null && manga.isNsfw)
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.IO)
val onZoomChanged = SingleLiveEvent<Unit>() val onZoomChanged = SingleLiveEvent<Unit>()
init { init {

@ -7,11 +7,8 @@ import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
@ -37,44 +34,31 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
private val settings = activity.get<AppSettings>() private val settings = activity.get<AppSettings>()
private val repo = activity.get<GithubRepository>() private val repo = activity.get<GithubRepository>()
fun launchIfNeeded(): Job? { suspend fun checkIfNeeded(): Boolean? = if (
return if (settings.appUpdateAuto && settings.appUpdate + PERIOD < System.currentTimeMillis()) { settings.isUpdateCheckingEnabled &&
launch() settings.lastUpdateCheckTimestamp + PERIOD < System.currentTimeMillis()
} else { ) {
null checkNow()
} } else {
} null
fun launch(): Job? {
return if (isUpdateSupported(activity)) {
launchInternal()
} else {
null
}
} }
suspend fun checkNow() = runCatching { suspend fun checkNow() = runCatching {
withContext(Dispatchers.Default) { val version = repo.getLatestVersion()
val version = repo.getLatestVersion() val newVersionId = VersionId.parse(version.name)
val newVersionId = VersionId.parse(version.name) val currentVersionId = VersionId.parse(BuildConfig.VERSION_NAME)
val currentVersionId = VersionId.parse(BuildConfig.VERSION_NAME) val result = newVersionId > currentVersionId
val result = newVersionId > currentVersionId if (result) {
if (result) { withContext(Dispatchers.Main) {
withContext(Dispatchers.Main) { showUpdateDialog(version)
showUpdateDialog(version)
}
} }
settings.appUpdate = System.currentTimeMillis()
result
} }
settings.lastUpdateCheckTimestamp = System.currentTimeMillis()
result
}.onFailure { }.onFailure {
it.printStackTrace() it.printStackTrace()
}.getOrNull() }.getOrNull()
private fun launchInternal() = activity.lifecycleScope.launch {
checkNow()
}
@MainThread @MainThread
private fun showUpdateDialog(version: AppVersion) { private fun showUpdateDialog(version: AppVersion) {
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)

@ -11,7 +11,6 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.DialogOnboardBinding import org.koitharu.kotatsu.databinding.DialogOnboardBinding
import org.koitharu.kotatsu.settings.onboard.adapter.SourceLocalesAdapter import org.koitharu.kotatsu.settings.onboard.adapter.SourceLocalesAdapter
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
@ -75,12 +74,10 @@ class OnboardDialogFragment : AlertDialogFragment<DialogOnboardBinding>(),
fun show(fm: FragmentManager) = OnboardDialogFragment().show(fm, TAG) fun show(fm: FragmentManager) = OnboardDialogFragment().show(fm, TAG)
fun showWelcome(settings: AppSettings, fm: FragmentManager) { fun showWelcome(fm: FragmentManager) {
if (!settings.isSourcesSelected) { OnboardDialogFragment().withArgs(1) {
OnboardDialogFragment().withArgs(1) { putBoolean(ARG_WELCOME, true)
putBoolean(ARG_WELCOME, true) }.show(fm, TAG)
}.show(fm, TAG)
}
} }
} }
} }

@ -5,6 +5,7 @@ import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
@ -21,6 +22,7 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
setContentView(ActivitySetupProtectBinding.inflate(layoutInflater)) setContentView(ActivitySetupProtectBinding.inflate(layoutInflater))
binding.editPassword.addTextChangedListener(this) binding.editPassword.addTextChangedListener(this)
binding.editPassword.setOnEditorActionListener(this) binding.editPassword.setOnEditorActionListener(this)

@ -5,11 +5,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.* 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.suggestions.domain.SuggestionRepository import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.onFirst import org.koitharu.kotatsu.utils.ext.onFirst
@ -29,7 +30,7 @@ class SuggestionsViewModel(
textPrimary = R.string.nothing_found, textPrimary = R.string.nothing_found,
textSecondary = R.string.text_suggestion_holder, textSecondary = R.string.text_suggestion_holder,
)) ))
else -> mapList(list, mode) else -> list.toUi(mode)
} }
}.onFirst { }.onFirst {
isLoading.postValue(false) isLoading.postValue(false)
@ -43,15 +44,4 @@ class SuggestionsViewModel(
override fun onRefresh() = Unit override fun onRefresh() = Unit
override fun onRetry() = Unit override fun onRetry() = Unit
private fun mapList(
list: List<Manga>,
mode: ListMode,
): List<ListModel> = list.map { manga ->
when (mode) {
ListMode.LIST -> manga.toListModel()
ListMode.DETAILED_LIST -> manga.toListDetailedModel()
ListMode.GRID -> manga.toGridModel()
}
}
} }

@ -12,8 +12,7 @@ class TrackingRepository(
) { ) {
suspend fun getNewChaptersCount(mangaId: Long): Int { suspend fun getNewChaptersCount(mangaId: Long): Int {
val entity = db.tracksDao.find(mangaId) ?: return 0 return db.tracksDao.findNewChapters(mangaId) ?: 0
return entity.newChapters
} }
suspend fun getAllTracks(useFavourites: Boolean, useHistory: Boolean): List<MangaTracking> { suspend fun getAllTracks(useFavourites: Boolean, useHistory: Boolean): List<MangaTracking> {

@ -4,7 +4,6 @@ import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.Network import android.net.Network
import android.net.NetworkRequest import android.net.NetworkRequest
import android.os.Build
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine

@ -1,21 +0,0 @@
package org.koitharu.kotatsu.utils.ext
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.graphics.drawable.DrawableCompat
import org.koitharu.kotatsu.R
fun navigationItemBackground(context: Context): Drawable? {
// Need to inflate the drawable and CSL via AppCompatResources to work on Lollipop
// From Google I/O repo (https://github.com/google/iosched)
var background = AppCompatResources.getDrawable(context, R.drawable.navigation_item_background)
if (background != null) {
val tint = AppCompatResources.getColorStateList(
context, R.color.navigation_item_background_tint
)
background = DrawableCompat.wrap(background.mutate())
background.setTintList(tint)
}
return background
}

@ -5,9 +5,11 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.retry
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.model.SortOrder
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.history.domain.HistoryRepository
@ -21,14 +23,14 @@ class WidgetUpdater(private val context: Context) {
repository.observeAll(SortOrder.NEWEST) repository.observeAll(SortOrder.NEWEST)
.onEach { updateWidget(ShelfWidgetProvider::class.java) } .onEach { updateWidget(ShelfWidgetProvider::class.java) }
.retry { error -> error !is CancellationException } .retry { error -> error !is CancellationException }
.launchIn(processLifecycleScope) .launchIn(processLifecycleScope + Dispatchers.Default)
} }
fun subscribeToHistory(repository: HistoryRepository) { fun subscribeToHistory(repository: HistoryRepository) {
repository.observeAll() repository.observeAll()
.onEach { updateWidget(RecentWidgetProvider::class.java) } .onEach { updateWidget(RecentWidgetProvider::class.java) }
.retry { error -> error !is CancellationException } .retry { error -> error !is CancellationException }
.launchIn(processLifecycleScope) .launchIn(processLifecycleScope + Dispatchers.Default)
} }
private fun updateWidget(cls: Class<*>) { private fun updateWidget(cls: Class<*>) {

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.7" android:color="?attr/colorSurface" />
</selector>

@ -10,7 +10,7 @@
android:bottom="2dp" android:bottom="2dp"
android:left="2dp"> android:left="2dp">
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<corners android:radius="4dp" /> <corners android:radius="12dp" />
<solid android:color="@color/selector_overlay" /> <solid android:color="@color/selector_overlay" />
</shape> </shape>
</item> </item>
@ -22,7 +22,7 @@
android:bottom="2dp" android:bottom="2dp"
android:left="2dp"> android:left="2dp">
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<corners android:radius="4dp" /> <corners android:radius="12dp" />
<solid android:color="@color/selector_overlay" /> <solid android:color="@color/selector_overlay" />
</shape> </shape>
</item> </item>
@ -33,7 +33,7 @@
android:bottom="2dp" android:bottom="2dp"
android:left="2dp"> android:left="2dp">
<shape android:shape="rectangle"> <shape android:shape="rectangle">
<corners android:radius="4dp" /> <corners android:radius="12dp" />
<solid android:color="?android:attr/windowBackground" /> <solid android:color="?android:attr/windowBackground" />
</shape> </shape>
</item> </item>

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<inset
android:insetLeft="@dimen/nav_item_background_inset_left"
android:insetRight="@dimen/nav_item_background_inset_right">
<shape>
<corners
android:bottomLeftRadius="@dimen/nav_item_background_corner_radius_left"
android:bottomRightRadius="@dimen/nav_item_background_corner_radius_right"
android:topLeftRadius="@dimen/nav_item_background_corner_radius_left"
android:topRightRadius="@dimen/nav_item_background_corner_radius_right" />
</shape>
</inset>
</item>
<item>
<color android:color="@android:color/transparent" />
</item>
</selector>

@ -15,7 +15,7 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"/> android:theme="?attr/actionBarTheme" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

@ -27,21 +27,21 @@
<FrameLayout <FrameLayout
android:id="@+id/toolbar_card" android:id="@+id/toolbar_card"
android:background="@drawable/toolbar_background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"> android:layout_marginBottom="8dp"
android:background="@drawable/toolbar_background">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar" android:id="@id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:background="@android:color/transparent"
app:contentInsetStartWithNavigation="0dp" app:contentInsetStartWithNavigation="0dp"
app:titleTextAppearance="@style/TextAppearance.Kotatsu.PersistentToolbarTitle" app:titleTextAppearance="@style/TextAppearance.Kotatsu.PersistentToolbarTitle"
app:titleTextColor="?android:colorControlNormal" app:titleTextColor="?android:colorControlNormal"

@ -3,9 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" 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"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:paddingStart="8dp"
android:paddingEnd="8dp">
<org.koitharu.kotatsu.base.ui.widgets.ChipsView <org.koitharu.kotatsu.base.ui.widgets.ChipsView
android:id="@+id/chips_tags" android:id="@+id/chips_tags"

@ -5,8 +5,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical|start" android:gravity="center_vertical|start"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader" android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
tools:text="@tools:sample/lorem[2]" /> tools:text="@tools:sample/lorem[2]" />

@ -4,16 +4,16 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/list_selector" android:background="@drawable/list_selector"
android:clipChildren="false"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/card" android:id="@+id/card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp" app:cardCornerRadius="12dp">
android:layout_marginTop="8dp"
android:layout_marginBottom="4dp">
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView <org.koitharu.kotatsu.base.ui.widgets.CoverImageView
android:id="@+id/imageView_cover" android:id="@+id/imageView_cover"
@ -28,15 +28,15 @@
<TextView <TextView
android:id="@+id/textView_title" android:id="@+id/textView_title"
style="?attr/textAppearanceBodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elegantTextHeight="false"
android:ellipsize="end" android:ellipsize="end"
android:lineSpacingExtra="-2dp"
android:maxLines="2" android:maxLines="2"
android:paddingHorizontal="8dp" android:padding="4dp"
android:paddingBottom="4dp" android:textAppearance="?attr/textAppearanceTitleSmall"
android:textColor="?android:attr/textColorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/thumbnail"
tools:text="Sample name" /> tools:text="Sample name" />
</LinearLayout> </LinearLayout>

@ -1,17 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android" 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/manga_list_item_height" android:layout_height="@dimen/manga_list_item_height"
android:background="@drawable/list_selector" app:cardCornerRadius="12dp">
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="8dp"> android:orientation="horizontal">
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView <org.koitharu.kotatsu.base.ui.widgets.CoverImageView
android:id="@+id/imageView_cover" android:id="@+id/imageView_cover"
@ -19,37 +18,38 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
tools:src="@tools:sample/backgrounds/scenic" /> tools:src="@tools:sample/backgrounds/scenic" />
</com.google.android.material.card.MaterialCardView> <LinearLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingEnd="16dp">
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:layout_gravity="center_vertical"
android:maxLines="2" android:orientation="vertical"
android:textAppearance="?attr/textAppearanceBodyLarge" android:paddingStart="20dp"
tools:text="@tools:sample/lorem/random" /> android:paddingEnd="16dp">
<TextView <TextView
android:id="@+id/textView_subtitle" android:id="@+id/textView_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="2"
android:textAppearance="?attr/textAppearanceBodyMedium" android:textAppearance="?attr/textAppearanceBodyLarge"
android:textColor="?android:textColorSecondary" tools:text="@tools:sample/lorem/random" />
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?android:textColorSecondary"
tools:text="@tools:sample/lorem/random" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </com.google.android.material.card.MaterialCardView>

@ -1,17 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android" 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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/manga_list_details_item_height" android:layout_height="@dimen/manga_list_details_item_height"
android:background="@drawable/list_selector" app:cardCornerRadius="12dp">
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="8dp"> android:orientation="horizontal">
<org.koitharu.kotatsu.base.ui.widgets.CoverImageView <org.koitharu.kotatsu.base.ui.widgets.CoverImageView
android:id="@+id/imageView_cover" android:id="@+id/imageView_cover"
@ -19,70 +18,71 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic"/> tools:src="@tools:sample/backgrounds/scenic"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"/>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_margin="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?attr/textAppearanceTitleMedium"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:ellipsize="none"
android:gravity="center_vertical"
android:requiresFadingEdge="horizontal"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyMedium"
tools:text="@tools:sample/lorem/random" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_gravity="center_vertical"
android:gravity="center"> android:layout_margin="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?attr/textAppearanceTitleMedium"
tools:text="@tools:sample/lorem/random" />
<TextView <TextView
android:id="@+id/textView_tags" android:id="@+id/textView_subtitle"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_marginBottom="4dp"
android:ellipsize="none" android:ellipsize="none"
android:gravity="center_vertical" android:gravity="center_vertical"
android:requiresFadingEdge="horizontal" android:requiresFadingEdge="horizontal"
android:singleLine="true" android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall" android:textAppearance="?attr/textAppearanceBodyMedium"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
<TextView <LinearLayout
android:id="@+id/textView_rating" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:orientation="horizontal"
android:drawablePadding="4dp" android:gravity="center">
android:paddingStart="6dp"
app:drawableEndCompat="@drawable/ic_star" <TextView
tools:ignore="RtlSymmetry" android:id="@+id/textView_tags"
tools:text="9.6" /> android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="none"
android:gravity="center_vertical"
android:requiresFadingEdge="horizontal"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/textView_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:drawablePadding="4dp"
android:paddingStart="6dp"
app:drawableEndCompat="@drawable/ic_star"
tools:ignore="RtlSymmetry"
tools:text="9.6" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout> </com.google.android.material.card.MaterialCardView>

@ -4,7 +4,6 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall" android:layout_height="?android:listPreferredItemHeightSmall"
android:background="?android:windowBackground"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">

@ -249,6 +249,10 @@
<string name="available_sources">Доступные источники</string> <string name="available_sources">Доступные источники</string>
<string name="dynamic_theme">Динамическая тема</string> <string name="dynamic_theme">Динамическая тема</string>
<string name="dynamic_theme_summary">Применяет тему приложения, основанную на цветовой палитре обоев на устройстве</string> <string name="dynamic_theme_summary">Применяет тему приложения, основанную на цветовой палитре обоев на устройстве</string>
<string name="screenshots_policy">Разрешить скриншоты</string>
<string name="screenshots_allow">Разрешить</string>
<string name="screenshots_block_nsfw">Запретить для NSFW</string>
<string name="screenshots_block_all">Запретить всегда</string>
<string name="suggestions">Рекомендации</string> <string name="suggestions">Рекомендации</string>
<string name="suggestions_enable">Включить рекомендации</string> <string name="suggestions_enable">Включить рекомендации</string>
<string name="suggestions_summary">Предлагать мангу на основе Ваших предпочтений</string> <string name="suggestions_summary">Предлагать мангу на основе Ваших предпочтений</string>

@ -57,4 +57,69 @@
<string name="details">Detaylar</string> <string name="details">Detaylar</string>
<string name="settings">Ayarlar</string> <string name="settings">Ayarlar</string>
<string name="page_saved">Kaydet</string> <string name="page_saved">Kaydet</string>
<string name="restart">Tekrar başlat</string>
<string name="error_occurred">Bir hata oluştu</string>
<string name="remote_sources">Uzak kaynaklar</string>
<string name="warning">Uyarı</string>
<string name="history_and_cache">Geçmiş ve önbellek</string>
<string name="search_history_cleared">Temizlendi</string>
<string name="_continue">Devam</string>
<string name="not_available">Müsait değil</string>
<string name="favourites_category_empty">Boş kategori</string>
<string name="cache">Önbellek</string>
<string name="application_update">Uygulamanın yeni sürümlerini kontrol edin</string>
<string name="remove_category">Kaldır</string>
<string name="delete">Sil</string>
<string name="chapter_d_of_d">Bölüm %1$d / %2$d</string>
<string name="text_file_not_supported">Bir ZIP veya CBZ dosyası seçin.</string>
<string name="read_mode">Okuma modu</string>
<string name="grid_size">Izgara boyutu</string>
<string name="webtoon">Webtoon</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="reader_settings">Okuyucu ayarları</string>
<string name="volume_buttons">Ses butonları</string>
<string name="dont_ask_again">Bir daha sorma</string>
<string name="cancelling_">İptal ediliyor…</string>
<string name="error">Hata</string>
<string name="clear_thumbs_cache">Küçük resim önbelleğini temizle</string>
<string name="gestures_only">Yalnızca hareketler</string>
<string name="domain">Alan adi</string>
<string name="open_in_browser">Web tarayıcısında aç</string>
<string name="new_chapters">Yeni bölümler</string>
<string name="notifications_settings">Bildirim ayarları</string>
<string name="notification_sound">Bildirim sesi</string>
<string name="light_indicator">LED göstergesi</string>
<string name="vibration">Titreşim</string>
<string name="other_storage">Diğer depolama</string>
<string name="updates">Güncellemeler</string>
<string name="create_shortcut">Kısayol oluştur…</string>
<string name="_import">İçe aktar</string>
<string name="delete_manga">Mangayı sil</string>
<string name="computing_">Bilgi işleniyor…</string>
<string name="sort_order">Sıralama düzeni</string>
<string name="no_description">ıklama yok</string>
<string name="operation_not_supported">Bu işlem desteklenmiyor</string>
<string name="standard">Standart</string>
<string name="clear_pages_cache">Sayfa önbelleğini temizle</string>
<string name="search_on_s">%s üzerinde ara</string>
<string name="internal_storage">Dahili depolama</string>
<string name="notifications">Bildirimler</string>
<string name="switch_pages">Sayfaları değiştir</string>
<string name="network_consumption_warning">Bu çok fazla veri aktarabilir</string>
<string name="save_manga">Kaydet</string>
<string name="download">İndir</string>
<string name="manga_save_location">İndirilenler klasörü</string>
<string name="external_storage">Harici depolama</string>
<string name="read_from_start">Baştan oku</string>
<string name="categories_">Kategoriler…</string>
<string name="app_update_available">Uygulamanın yeni bir sürümü mevcut</string>
<string name="show_notification_app_update">Yeni bir sürüm mevcutsa bildirimini göster</string>
<string name="favourites_categories">Favori kategoriler</string>
<string name="done">Bitti</string>
<string name="read_later">Sonra oku</string>
<string name="pages_animation">Sayfa animasyonu</string>
<string name="cannot_find_available_storage">Kullanılabilir depolama alanı yok</string>
<string name="rename">Yeniden adlandır</string>
<string name="text_delete_local_manga">\"%s\" cihazdan kalıcı olarak silinsin mi\?</string>
<string name="clear_search_history">Arama geçmişini temizle</string>
</resources> </resources>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Base.V23.Kotatsu" parent="Base.Theme.Kotatsu">
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
<style name="Theme.Kotatsu" parent="Base.V23.Kotatsu" />
</resources>

@ -2,7 +2,8 @@
<resources> <resources>
<style name="Base.V27.Kotatsu" parent="Base.Theme.Kotatsu"> <style name="Base.V27.Kotatsu" parent="Base.Theme.Kotatsu">
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@color/navigation_bar_scrim</item>
<item name="android:windowLightNavigationBar">@bool/light_navigation_bar</item> <item name="android:windowLightNavigationBar">@bool/light_navigation_bar</item>
</style> </style>

@ -24,4 +24,9 @@
<item>@string/detailed_list</item> <item>@string/detailed_list</item>
<item>@string/grid</item> <item>@string/grid</item>
</string-array> </string-array>
<string-array name="screenshots_policy">
<item>@string/screenshots_allow</item>
<item>@string/screenshots_block_nsfw</item>
<item>@string/screenshots_block_all</item>
</string-array>
</resources> </resources>

@ -20,4 +20,9 @@
<string-array name="values_track_sources_default" translatable="false"> <string-array name="values_track_sources_default" translatable="false">
<item>favourites</item> <item>favourites</item>
</string-array> </string-array>
<string-array name="values_screenshots_policy" translatable="false">
<item>allow</item>
<item>block_nsfw</item>
<item>block_all</item>
</string-array>
</resources> </resources>

@ -7,14 +7,9 @@
<!-- Navigation --> <!-- Navigation -->
<dimen name="nav_header_logo_size">36dp</dimen> <dimen name="nav_header_logo_size">36dp</dimen>
<dimen name="nav_item_horizontal_padding">24dp</dimen> <dimen name="nav_item_horizontal_padding">24dp</dimen>
<!-- Intrinsic height may vary. Use a large radius to ensure a semicircle. -->
<dimen name="nav_item_background_corner_radius_left">0dp</dimen>
<dimen name="nav_item_background_corner_radius_right">100dp</dimen>
<dimen name="nav_item_background_inset_left">0dp</dimen>
<dimen name="nav_item_background_inset_right">8dp</dimen>>
<dimen name="grid_spacing">4dp</dimen> <dimen name="grid_spacing">8dp</dimen>
<dimen name="list_spacing">4dp</dimen> <dimen name="list_spacing">8dp</dimen>
<dimen name="grid_spacing_outer">2dp</dimen> <dimen name="grid_spacing_outer">2dp</dimen>
<dimen name="manga_list_item_height">86dp</dimen> <dimen name="manga_list_item_height">86dp</dimen>
<dimen name="manga_list_details_item_height">120dp</dimen> <dimen name="manga_list_details_item_height">120dp</dimen>
@ -26,6 +21,8 @@
<dimen name="list_footer_height_outer">48dp</dimen> <dimen name="list_footer_height_outer">48dp</dimen>
<dimen name="screen_padding">16dp</dimen> <dimen name="screen_padding">16dp</dimen>
<dimen name="feed_dividers_offset">72dp</dimen> <dimen name="feed_dividers_offset">72dp</dimen>
<dimen name="manga_badge_offset_horizontal">4dp</dimen>
<dimen name="manga_badge_offset_vertical">2dp</dimen>
<!--Text dimens--> <!--Text dimens-->
<dimen name="text_size_h1">22sp</dimen> <dimen name="text_size_h1">22sp</dimen>

@ -2,5 +2,5 @@
<resources> <resources>
<integer name="search_animation_duration">@android:integer/config_shortAnimTime</integer> <integer name="search_animation_duration">@android:integer/config_shortAnimTime</integer>
<integer name="manga_badge_max_character_count">3</integer>
</resources> </resources>

@ -251,12 +251,16 @@
<string name="dynamic_theme">Dynamic theme</string> <string name="dynamic_theme">Dynamic theme</string>
<string name="dynamic_theme_summary">Applies a theme created on the color scheme of your wallpaper</string> <string name="dynamic_theme_summary">Applies a theme created on the color scheme of your wallpaper</string>
<string name="importing_progress">Importing manga: %1$d of %2$d</string> <string name="importing_progress">Importing manga: %1$d of %2$d</string>
<string name="suggestions">Suggestions</string> <string name="screenshots_policy">Screenshots policy</string>
<string name="suggestions_enable">Enable suggestions</string> <string name="screenshots_allow">Allow</string>
<string name="suggestions_summary">Suggest manga based on your preferences</string> <string name="screenshots_block_nsfw">Block on NSFW</string>
<string name="suggestions_info">All data is analyzed locally on this device. There is no transfer of your personal data to any services</string> <string name="screenshots_block_all">Block always</string>
<string name="text_suggestion_holder">Start reading manga and you will get personalized suggestions</string> <string name="suggestions">Suggestions</string>
<string name="exclude_nsfw_from_suggestions">Do not suggest NSFW manga</string> <string name="suggestions_enable">Enable suggestions</string>
<string name="suggestions_summary">Suggest manga based on your preferences</string>
<string name="suggestions_info">All data is analyzed locally on this device. There is no transfer of your personal data to any services</string>
<string name="text_suggestion_holder">Start reading manga and you will get personalized suggestions</string>
<string name="exclude_nsfw_from_suggestions">Do not suggest NSFW manga</string>
<string name="enabled">Enabled</string> <string name="enabled">Enabled</string>
<string name="disabled">Disabled</string> <string name="disabled">Disabled</string>
</resources> </resources>

@ -137,6 +137,12 @@
<item name="android:textColor">?android:attr/textColorSecondary</item> <item name="android:textColor">?android:attr/textColorSecondary</item>
</style> </style>
<!-- Shapes -->
<style name="ShapeAppearanceOverlay.Kotatsu.Cover" parent="">
<item name="cornerSize">12dp</item>
</style>
<!--Preferences--> <!--Preferences-->
<style name="PreferenceThemeOverlay.Kotatsu"> <style name="PreferenceThemeOverlay.Kotatsu">

@ -44,7 +44,7 @@
<!-- Themes --> <!-- Themes -->
<item name="android:windowLightStatusBar" tools:targetApi="M">@bool/light_status_bar</item> <item name="android:windowLightStatusBar" tools:targetApi="M">@bool/light_status_bar</item>
<item name="android:statusBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@color/surface_amoled</item>
<item name="android:navigationBarColor">@color/surface_amoled</item> <item name="android:navigationBarColor">@color/surface_amoled</item>
<item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">@null</item> <item name="android:navigationBarDividerColor" tools:targetApi="o_mr1">@null</item>
<item name="android:enforceNavigationBarContrast" tools:targetApi="Q">false</item> <item name="android:enforceNavigationBarContrast" tools:targetApi="Q">false</item>
@ -66,7 +66,7 @@
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item> <item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item>
<item name="tabStyle">@style/Widget.Kotatsu.Tabs</item> <item name="tabStyle">@style/Widget.Kotatsu.Tabs</item>
<item name="switchStyle">@style/Widget.Kotatsu.Switch</item> <item name="switchStyle">@style/Widget.Kotatsu.Switch</item>
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Elevated</item> <item name="materialCardViewStyle">@style/Widget.Material3.CardView.Filled</item>
<item name="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</item> <item name="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</item>
<!-- Preference text appearance --> <!-- Preference text appearance -->

@ -38,4 +38,13 @@
android:title="@string/show_pages_numbers" android:title="@string/show_pages_numbers"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<ListPreference
android:entries="@array/screenshots_policy"
android:entryValues="@array/values_screenshots_policy"
android:key="screenshots_policy"
android:title="@string/screenshots_policy"
app:defaultValue="allow"
app:iconSpaceReserved="false"
app:useSimpleSummaryProvider="true" />
</PreferenceScreen> </PreferenceScreen>
Loading…
Cancel
Save