Merge branch 'devel' into feature/suggestions

This commit is contained in:
Koitharu
2022-02-28 18:37:49 +02:00
56 changed files with 467 additions and 268 deletions

View File

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

View File

@@ -13,6 +13,9 @@ abstract class TracksDao {
@Query("SELECT * FROM tracks WHERE manga_id = :mangaId")
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")
abstract suspend fun clear()

View File

@@ -56,10 +56,10 @@ class AppSettings(context: Context) {
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) }
val appUpdateAuto: Boolean
val isUpdateCheckingEnabled: Boolean
get() = prefs.getBoolean(KEY_APP_UPDATE_AUTO, true)
var appUpdate: Long
var lastUpdateCheckTimestamp: Long
get() = prefs.getLong(KEY_APP_UPDATE, 0L)
set(value) = prefs.edit { putLong(KEY_APP_UPDATE, value) }
@@ -123,6 +123,12 @@ class AppSettings(context: Context) {
val isPagesNumbersEnabled: Boolean
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?
get() = prefs.getString(KEY_LOCAL_STORAGE, null)?.let {
File(it)
@@ -230,6 +236,7 @@ class AppSettings(context: Context) {
const val KEY_REVERSE_CHAPTERS = "reverse_chapters"
const val KEY_HISTORY_EXCLUDE_NSFW = "history_exclude_nsfw"
const val KEY_PAGES_NUMBERS = "pages_numbers"
const val KEY_SCREENSHOTS_POLICY = "screenshots_policy"
const val KEY_SUGGESTIONS = "suggestions"
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -43,7 +43,13 @@ class LocalListViewModel(
when {
error != null -> listOf(error.toErrorState(canRetry = true))
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 {
add(headerModel)
list.toUi(this, mode)

View File

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

View File

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

View File

@@ -94,6 +94,7 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
viewModel.content.observe(this) {
onLoadingStateChanged(viewModel.isLoading.value == true)
}
viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure)
}
private fun onInitReader(mode: ReaderMode) {
@@ -299,6 +300,14 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
}.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) {
if (binding.appbarTop.isVisible != isUiVisible) {
if (isUiVisible) {

View File

@@ -20,6 +20,7 @@ import org.koitharu.kotatsu.core.os.ShortcutsRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
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.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
@@ -69,6 +70,17 @@ class ReaderViewModel(
.onStart { emit(settings.readerAnimation) }
.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>()
init {

View File

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

View File

@@ -11,7 +11,6 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
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.settings.onboard.adapter.SourceLocalesAdapter
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 showWelcome(settings: AppSettings, fm: FragmentManager) {
if (!settings.isSourcesSelected) {
OnboardDialogFragment().withArgs(1) {
putBoolean(ARG_WELCOME, true)
}.show(fm, TAG)
}
fun showWelcome(fm: FragmentManager) {
OnboardDialogFragment().withArgs(1) {
putBoolean(ARG_WELCOME, true)
}.show(fm, TAG)
}
}
}

View File

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

View File

@@ -5,11 +5,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
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.ListMode
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.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.onFirst
@@ -29,7 +30,7 @@ class SuggestionsViewModel(
textPrimary = R.string.nothing_found,
textSecondary = R.string.text_suggestion_holder,
))
else -> mapList(list, mode)
else -> list.toUi(mode)
}
}.onFirst {
isLoading.postValue(false)
@@ -43,15 +44,4 @@ class SuggestionsViewModel(
override fun onRefresh() = 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()
}
}
}

View File

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

View File

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

View File

@@ -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
}

View File

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