pull/211/head
Koitharu 4 years ago
parent 6e71f20470
commit 905c4b362c
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -3,6 +3,7 @@ package org.koitharu.kotatsu
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.os.StrictMode import android.os.StrictMode
import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.strictmode.FragmentStrictMode import androidx.fragment.app.strictmode.FragmentStrictMode
import androidx.hilt.work.HiltWorkerFactory import androidx.hilt.work.HiltWorkerFactory
@ -10,6 +11,8 @@ import androidx.room.InvalidationTracker
import androidx.work.Configuration import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.acra.ReportField import org.acra.ReportField
import org.acra.config.dialog import org.acra.config.dialog
import org.acra.config.mailSender import org.acra.config.mailSender
@ -20,6 +23,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
@HiltAndroidApp @HiltAndroidApp
class KotatsuApp : Application(), Configuration.Provider { class KotatsuApp : Application(), Configuration.Provider {
@ -46,7 +50,9 @@ class KotatsuApp : Application(), Configuration.Provider {
} }
AppCompatDelegate.setDefaultNightMode(settings.theme) AppCompatDelegate.setDefaultNightMode(settings.theme)
setupActivityLifecycleCallbacks() setupActivityLifecycleCallbacks()
setupDatabaseObservers() processLifecycleScope.launch(Dispatchers.Default) {
setupDatabaseObservers()
}
} }
override fun attachBaseContext(base: Context?) { override fun attachBaseContext(base: Context?) {
@ -86,6 +92,7 @@ class KotatsuApp : Application(), Configuration.Provider {
.build() .build()
} }
@WorkerThread
private fun setupDatabaseObservers() { private fun setupDatabaseObservers() {
val tracker = database.invalidationTracker val tracker = database.invalidationTracker
databaseObservers.forEach { databaseObservers.forEach {

@ -11,8 +11,8 @@ class SpacingItemDecoration(@Px private val spacing: Int) : RecyclerView.ItemDec
outRect: Rect, outRect: Rect,
view: View, view: View,
parent: RecyclerView, parent: RecyclerView,
state: RecyclerView.State state: RecyclerView.State,
) { ) {
outRect.set(spacing, spacing, spacing, spacing) outRect.set(spacing, spacing, spacing, spacing)
} }
} }

@ -12,6 +12,7 @@ import android.view.WindowInsets
import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.StringRes
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes
@ -147,6 +148,14 @@ class BottomSheetHeaderBar @JvmOverloads constructor(
expansionListeners.remove(listener) expansionListeners.remove(listener)
} }
fun setTitle(@StringRes resId: Int) {
binding.toolbar.setTitle(resId)
}
fun setSubtitle(@StringRes resId: Int) {
binding.toolbar.setSubtitle(resId)
}
private fun setBottomSheetBehavior(behavior: BottomSheetBehavior<*>?) { private fun setBottomSheetBehavior(behavior: BottomSheetBehavior<*>?) {
bottomSheetBehavior?.removeBottomSheetCallback(bottomSheetCallback) bottomSheetBehavior?.removeBottomSheetCallback(bottomSheetCallback)
bottomSheetBehavior = behavior bottomSheetBehavior = behavior

@ -2,9 +2,13 @@ package org.koitharu.kotatsu.core.db
import android.content.Context import android.content.Context
import androidx.room.Database import androidx.room.Database
import androidx.room.InvalidationTracker
import androidx.room.Room import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.migration.Migration import androidx.room.migration.Migration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.bookmarks.data.BookmarksDao import org.koitharu.kotatsu.bookmarks.data.BookmarksDao
import org.koitharu.kotatsu.core.db.dao.MangaDao import org.koitharu.kotatsu.core.db.dao.MangaDao
@ -29,6 +33,7 @@ import org.koitharu.kotatsu.suggestions.data.SuggestionEntity
import org.koitharu.kotatsu.tracker.data.TrackEntity import org.koitharu.kotatsu.tracker.data.TrackEntity
import org.koitharu.kotatsu.tracker.data.TrackLogEntity import org.koitharu.kotatsu.tracker.data.TrackLogEntity
import org.koitharu.kotatsu.tracker.data.TracksDao import org.koitharu.kotatsu.tracker.data.TracksDao
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
const val DATABASE_VERSION = 14 const val DATABASE_VERSION = 14
@ -88,3 +93,12 @@ fun MangaDatabase(context: Context): MangaDatabase = Room
.addMigrations(*databaseMigrations) .addMigrations(*databaseMigrations)
.addCallback(DatabasePrePopulateCallback(context.resources)) .addCallback(DatabasePrePopulateCallback(context.resources))
.build() .build()
fun InvalidationTracker.removeObserverAsync(observer: InvalidationTracker.Observer) {
val scope = processLifecycleScope
if (scope.isActive) {
processLifecycleScope.launch(Dispatchers.Default) {
removeObserver(observer)
}
}
}

@ -7,13 +7,11 @@ import android.content.IntentFilter
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.badge.BadgeDrawable import com.google.android.material.badge.BadgeDrawable
@ -209,7 +207,7 @@ class DetailsActivity :
right = insets.right, right = insets.right,
) )
if (insets.bottom > 0) { if (insets.bottom > 0) {
window.setNavigationBarTransparentCompat(this, binding.layoutBottom?.elevation ?: 0f) window.setNavigationBarTransparentCompat(this, binding.layoutBottom?.elevation ?: 0f, 0.9f)
} }
} }

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.download.ui package org.koitharu.kotatsu.download.ui
import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.ImageLoader import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
@ -9,6 +10,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemDownloadBinding import org.koitharu.kotatsu.databinding.ItemDownloadBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.download.domain.DownloadState
import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.*
@ -22,6 +24,19 @@ fun downloadItemAD(
var job: Job? = null var job: Job? = null
val percentPattern = context.resources.getString(R.string.percent_string_pattern) val percentPattern = context.resources.getString(R.string.percent_string_pattern)
val clickListener = View.OnClickListener { v ->
when (v.id) {
R.id.button_cancel -> item.cancel()
R.id.button_resume -> item.resume()
else -> context.startActivity(
DetailsActivity.newIntent(context, item.progressValue.manga),
)
}
}
binding.buttonCancel.setOnClickListener(clickListener)
binding.buttonResume.setOnClickListener(clickListener)
itemView.setOnClickListener(clickListener)
bind { bind {
job?.cancel() job?.cancel()
job = item.progressAsFlow().onFirst { state -> job = item.progressAsFlow().onFirst { state ->

@ -14,7 +14,9 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding
import org.koitharu.kotatsu.download.ui.service.DownloadService import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.utils.bindServiceWithLifecycle import org.koitharu.kotatsu.utils.bindServiceWithLifecycle
@ -30,6 +32,8 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
setContentView(ActivityDownloadsBinding.inflate(layoutInflater)) setContentView(ActivityDownloadsBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
val adapter = DownloadsAdapter(lifecycleScope, coil) val adapter = DownloadsAdapter(lifecycleScope, coil)
val spacing = resources.getDimensionPixelOffset(R.dimen.grid_spacing)
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
bindServiceWithLifecycle( bindServiceWithLifecycle(

@ -139,6 +139,8 @@ class DownloadService : BaseService() {
notification.create(job.progressValue, -1L) notification.create(job.progressValue, -1L)
}, },
) )
jobs.remove(job.progressValue.startId)
jobCount.value = jobs.size
stopSelf(startId) stopSelf(startId)
} }
} }
@ -158,8 +160,7 @@ class DownloadService : BaseService() {
when (intent?.action) { when (intent?.action) {
ACTION_DOWNLOAD_CANCEL -> { ACTION_DOWNLOAD_CANCEL -> {
val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0) val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
jobs.remove(cancelId)?.cancel() jobs[cancelId]?.cancel()
jobCount.value = jobs.size
} }
ACTION_DOWNLOAD_RESUME -> { ACTION_DOWNLOAD_RESUME -> {
val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0) val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)

@ -32,8 +32,8 @@ abstract class FavouriteCategoriesDao {
@Query("UPDATE favourite_categories SET `order` = :order WHERE category_id = :id") @Query("UPDATE favourite_categories SET `order` = :order WHERE category_id = :id")
abstract suspend fun updateOrder(id: Long, order: String) abstract suspend fun updateOrder(id: Long, order: String)
// @Query("UPDATE favourite_categories SET `track` = :isEnabled WHERE category_id = :id") @Query("UPDATE favourite_categories SET `track` = :isEnabled WHERE category_id = :id")
// abstract suspend fun updateTracking(id: Long, isEnabled: Boolean) abstract suspend fun updateTracking(id: Long, isEnabled: Boolean)
@Query("UPDATE favourite_categories SET `show_in_lib` = :isEnabled WHERE category_id = :id") @Query("UPDATE favourite_categories SET `show_in_lib` = :isEnabled WHERE category_id = :id")
abstract suspend fun updateLibVisibility(id: Long, isEnabled: Boolean) abstract suspend fun updateLibVisibility(id: Long, isEnabled: Boolean)

@ -108,6 +108,10 @@ class FavouritesRepository @Inject constructor(
db.favouriteCategoriesDao.updateLibVisibility(id, isVisibleInLibrary) db.favouriteCategoriesDao.updateLibVisibility(id, isVisibleInLibrary)
} }
suspend fun updateCategoryTracking(id: Long, isTrackingEnabled: Boolean) {
db.favouriteCategoriesDao.updateTracking(id, isTrackingEnabled)
}
suspend fun removeCategory(id: Long) { suspend fun removeCategory(id: Long) {
db.withTransaction { db.withTransaction {
db.favouriteCategoriesDao.delete(id) db.favouriteCategoriesDao.delete(id)

@ -130,8 +130,8 @@ class LibraryViewModel @Inject constructor(
if (result.isEmpty()) { if (result.isEmpty()) {
result += EmptyState( result += EmptyState(
icon = R.drawable.ic_empty_history, icon = R.drawable.ic_empty_history,
textPrimary = R.string.text_history_holder_primary, textPrimary = R.string.text_shelf_holder_primary,
textSecondary = R.string.text_history_holder_secondary, textSecondary = R.string.text_shelf_holder_secondary,
actionStringRes = 0, actionStringRes = 0,
) )
} }
@ -168,11 +168,13 @@ class LibraryViewModel @Inject constructor(
favourites: Map<FavouriteCategory, List<Manga>>, favourites: Map<FavouriteCategory, List<Manga>>,
) { ) {
for ((category, list) in favourites) { for ((category, list) in favourites) {
destination += LibrarySectionModel.Favourites( if (list.isNotEmpty()) {
items = list.toUi(ListMode.GRID, this), destination += LibrarySectionModel.Favourites(
category = category, items = list.toUi(ListMode.GRID, this),
showAllButtonText = R.string.show_all, category = category,
) showAllButtonText = R.string.show_all,
)
}
} }
} }

@ -26,6 +26,7 @@ import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.scrobbling.shikimori.ui.ShikimoriSettingsFragment import org.koitharu.kotatsu.scrobbling.shikimori.ui.ShikimoriSettingsFragment
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment
import org.koitharu.kotatsu.utils.ext.isScrolledToTop import org.koitharu.kotatsu.utils.ext.isScrolledToTop
@AndroidEntryPoint @AndroidEntryPoint

@ -6,7 +6,6 @@ import androidx.lifecycle.MutableLiveData
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlin.Comparator
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
@ -41,6 +40,7 @@ class OnboardViewModel @Inject constructor(
if (selectedLocales.isEmpty()) { if (selectedLocales.isEmpty()) {
selectedLocales += "en" selectedLocales += "en"
} }
selectedLocales += null
} }
rebuildList() rebuildList()
} }

@ -1,4 +1,4 @@
package org.koitharu.kotatsu.settings package org.koitharu.kotatsu.settings.tracker
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
@ -15,20 +15,18 @@ import android.view.View
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.text.buildSpannedString import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans import androidx.core.text.inSpans
import androidx.fragment.app.viewModels
import androidx.preference.MultiSelectListPreference import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity import org.koitharu.kotatsu.settings.tracker.categories.TrackerCategoriesConfigSheet
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
private const val KEY_IGNORE_DOZE = "ignore_dose" private const val KEY_IGNORE_DOZE = "ignore_dose"
@ -37,8 +35,7 @@ class TrackerSettingsFragment :
BasePreferenceFragment(R.string.check_for_new_chapters), BasePreferenceFragment(R.string.check_for_new_chapters),
SharedPreferences.OnSharedPreferenceChangeListener { SharedPreferences.OnSharedPreferenceChangeListener {
@Inject private val viewModel by viewModels<TrackerSettingsViewModel>()
lateinit var repository: TrackingRepository
@Inject @Inject
lateinit var channels: TrackerNotificationChannels lateinit var channels: TrackerNotificationChannels
@ -66,13 +63,13 @@ class TrackerSettingsFragment :
findPreference<Preference>(KEY_IGNORE_DOZE)?.run { findPreference<Preference>(KEY_IGNORE_DOZE)?.run {
isVisible = isDozeIgnoreAvailable(context) isVisible = isDozeIgnoreAvailable(context)
} }
updateCategoriesSummary()
updateNotificationsSummary() updateNotificationsSummary()
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
settings.subscribe(this) settings.subscribe(this)
viewModel.categoriesCount.observe(viewLifecycleOwner, ::onCategoriesCountChanged)
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -109,7 +106,7 @@ class TrackerSettingsFragment :
} }
} }
AppSettings.KEY_TRACK_CATEGORIES -> { AppSettings.KEY_TRACK_CATEGORIES -> {
startActivity(FavouriteCategoriesActivity.newIntent(preference.context)) TrackerCategoriesConfigSheet.show(childFragmentManager)
true true
} }
KEY_IGNORE_DOZE -> { KEY_IGNORE_DOZE -> {
@ -136,11 +133,10 @@ class TrackerSettingsFragment :
pref.isEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources pref.isEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources
} }
private fun updateCategoriesSummary() { private fun onCategoriesCountChanged(count: IntArray?) {
val pref = findPreference<Preference>(AppSettings.KEY_TRACK_CATEGORIES) ?: return val pref = findPreference<Preference>(AppSettings.KEY_TRACK_CATEGORIES) ?: return
viewLifecycleScope.launch { pref.summary = count?.let {
val count = repository.getCategoriesCount() getString(R.string.enabled_d_of_d, count[0], count[1])
pref.summary = getString(R.string.enabled_d_of_d, count[0], count[1])
} }
} }

@ -0,0 +1,51 @@
package org.koitharu.kotatsu.settings.tracker
import androidx.lifecycle.MutableLiveData
import androidx.room.InvalidationTracker
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import okio.Closeable
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
import org.koitharu.kotatsu.core.db.removeObserverAsync
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
@HiltViewModel
class TrackerSettingsViewModel @Inject constructor(
private val repository: TrackingRepository,
private val database: MangaDatabase,
) : BaseViewModel() {
val categoriesCount = MutableLiveData<IntArray?>(null)
init {
updateCategoriesCount()
val databaseObserver = DatabaseObserver(this)
addCloseable(databaseObserver)
launchJob(Dispatchers.Default) {
database.invalidationTracker.addObserver(databaseObserver)
}
}
private fun updateCategoriesCount() {
launchJob(Dispatchers.Default) {
categoriesCount.postValue(repository.getCategoriesCount())
}
}
private class DatabaseObserver(private var vm: TrackerSettingsViewModel?) :
InvalidationTracker.Observer(arrayOf(TABLE_FAVOURITE_CATEGORIES)),
Closeable {
override fun onInvalidated(tables: MutableSet<String>) {
vm?.updateCategoriesCount()
}
override fun close() {
(vm ?: return).database.invalidationTracker.removeObserverAsync(this)
vm = null
}
}
}

@ -0,0 +1,32 @@
package org.koitharu.kotatsu.settings.tracker.categories
import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
class TrackerCategoriesConfigAdapter(
listener: OnListItemClickListener<FavouriteCategory>,
) : AsyncListDifferDelegationAdapter<FavouriteCategory>(DiffCallback()) {
init {
delegatesManager.addDelegate(trackerCategoryAD(listener))
}
class DiffCallback : DiffUtil.ItemCallback<FavouriteCategory>() {
override fun areItemsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: FavouriteCategory, newItem: FavouriteCategory): Boolean {
return oldItem.isTrackingEnabled == newItem.isTrackingEnabled && oldItem.title == newItem.title
}
override fun getChangePayload(oldItem: FavouriteCategory, newItem: FavouriteCategory): Any? {
return if (oldItem.isTrackingEnabled == newItem.isTrackingEnabled) {
super.getChangePayload(oldItem, newItem)
} else Unit
}
}
}

@ -0,0 +1,54 @@
package org.koitharu.kotatsu.settings.tracker.categories
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.SheetBaseBinding
@AndroidEntryPoint
class TrackerCategoriesConfigSheet :
BaseBottomSheet<SheetBaseBinding>(),
OnListItemClickListener<FavouriteCategory>,
View.OnClickListener {
private val viewModel by viewModels<TrackerCategoriesConfigViewModel>()
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetBaseBinding {
return SheetBaseBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.headerBar.setTitle(R.string.favourites_categories)
binding.buttonDone.isVisible = true
binding.buttonDone.setOnClickListener(this)
val adapter = TrackerCategoriesConfigAdapter(this)
binding.recyclerView.adapter = adapter
viewModel.content.observe(viewLifecycleOwner) { adapter.items = it }
}
override fun onItemClick(item: FavouriteCategory, view: View) {
viewModel.toggleItem(item)
}
override fun onClick(v: View?) {
dismiss()
}
companion object {
private const val TAG = "TrackerCategoriesConfigSheet"
fun show(fm: FragmentManager) = TrackerCategoriesConfigSheet().show(fm, TAG)
}
}

@ -0,0 +1,30 @@
package org.koitharu.kotatsu.settings.tracker.categories
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.utils.asFlowLiveData
@HiltViewModel
class TrackerCategoriesConfigViewModel @Inject constructor(
private val favouritesRepository: FavouritesRepository,
) : BaseViewModel() {
val content = favouritesRepository.observeCategories()
.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
private var updateJob: Job? = null
fun toggleItem(category: FavouriteCategory) {
val prevJob = updateJob
updateJob = launchJob(Dispatchers.Default) {
prevJob?.join()
favouritesRepository.updateCategoryTracking(category.id, !category.isTrackingEnabled)
}
}
}

@ -0,0 +1,21 @@
package org.koitharu.kotatsu.settings.tracker.categories
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ItemCategoryCheckableMultipleBinding
fun trackerCategoryAD(
listener: OnListItemClickListener<FavouriteCategory>,
) = adapterDelegateViewBinding<FavouriteCategory, FavouriteCategory, ItemCategoryCheckableMultipleBinding>(
{ layoutInflater, parent -> ItemCategoryCheckableMultipleBinding.inflate(layoutInflater, parent, false) },
) {
val eventListener = AdapterDelegateClickListenerAdapter(this, listener)
itemView.setOnClickListener(eventListener)
bind {
binding.root.text = item.title
binding.root.isChecked = item.isTrackingEnabled
}
}

@ -15,14 +15,11 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.Settings import android.provider.Settings
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.ViewPropertyAnimator import android.view.ViewPropertyAnimator
import android.view.Window import android.view.Window
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IntegerRes import androidx.annotation.IntegerRes
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
import androidx.core.view.children
import androidx.core.view.descendants
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
@ -104,7 +101,7 @@ fun SyncResult.onError(error: Throwable) {
error.printStackTraceDebug() error.printStackTraceDebug()
} }
fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float = 0F) { fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float, alphaFactor: Float = 0.7f) {
navigationBarColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && navigationBarColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
!InternalResourceHelper.getBoolean(context, "config_navBarNeedsScrim", true) !InternalResourceHelper.getBoolean(context, "config_navBarNeedsScrim", true)
) { ) {
@ -112,7 +109,7 @@ fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float
} else { } else {
// Set navbar scrim 70% of navigationBarColor // Set navbar scrim 70% of navigationBarColor
ElevationOverlayProvider(context).compositeOverlayIfNeeded( ElevationOverlayProvider(context).compositeOverlayIfNeeded(
context.getResourceColor(android.R.attr.navigationBarColor, 0.7F), context.getThemeColor(android.R.attr.navigationBarColor, alphaFactor),
elevation, elevation,
) )
} }

@ -1,15 +1,7 @@
package org.koitharu.kotatsu.utils.ext package org.koitharu.kotatsu.utils.ext
import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Color
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.Px import androidx.annotation.Px
import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Px @Px
@ -17,17 +9,3 @@ fun Resources.resolveDp(dp: Int) = (dp * displayMetrics.density).roundToInt()
@Px @Px
fun Resources.resolveDp(dp: Float) = dp * displayMetrics.density fun Resources.resolveDp(dp: Float) = dp * displayMetrics.density
@ColorInt
fun Context.getResourceColor(@AttrRes resource: Int, alphaFactor: Float = 1f): Int {
val typedArray = obtainStyledAttributes(intArrayOf(resource))
val color = typedArray.getColor(0, 0)
typedArray.recycle()
if (alphaFactor < 1f) {
val alpha = (color.alpha * alphaFactor).roundToInt()
return Color.argb(alpha, color.red, color.green, color.blue)
}
return color
}

@ -4,7 +4,9 @@ import android.content.Context
import android.graphics.Color import android.graphics.Color
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import androidx.core.content.res.use import androidx.core.content.res.use
import androidx.core.graphics.ColorUtils
fun Context.getThemeDrawable( fun Context.getThemeDrawable(
@AttrRes resId: Int, @AttrRes resId: Int,
@ -15,13 +17,29 @@ fun Context.getThemeDrawable(
@ColorInt @ColorInt
fun Context.getThemeColor( fun Context.getThemeColor(
@AttrRes resId: Int, @AttrRes resId: Int,
@ColorInt default: Int = Color.TRANSPARENT @ColorInt fallback: Int = Color.TRANSPARENT,
) = obtainStyledAttributes(intArrayOf(resId)).use { ) = obtainStyledAttributes(intArrayOf(resId)).use {
it.getColor(0, default) it.getColor(0, fallback)
}
@ColorInt
fun Context.getThemeColor(
@AttrRes resId: Int,
@FloatRange(from = 0.0, to = 1.0) alphaFactor: Float,
@ColorInt fallback: Int = Color.TRANSPARENT,
): Int {
if (alphaFactor <= 0f) {
return Color.TRANSPARENT
}
val color = getThemeColor(resId, fallback)
if (alphaFactor >= 1f) {
return color
}
return ColorUtils.setAlphaComponent(color, (0xFF * alphaFactor).toInt())
} }
fun Context.getThemeColorStateList( fun Context.getThemeColorStateList(
@AttrRes resId: Int, @AttrRes resId: Int,
) = obtainStyledAttributes(intArrayOf(resId)).use { ) = obtainStyledAttributes(intArrayOf(resId)).use {
it.getColorStateList(0) it.getColorStateList(0)
} }

@ -83,7 +83,7 @@
<RatingBar <RatingBar
android:id="@+id/rating_bar" android:id="@+id/rating_bar"
style="@style/Widget.AppCompat.RatingBar.Small" style="?ratingBarStyleSmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:isIndicator="true" android:isIndicator="true"

@ -91,7 +91,7 @@
<RatingBar <RatingBar
android:id="@+id/rating_bar" android:id="@+id/rating_bar"
style="@style/Widget.AppCompat.RatingBar.Small" style="?ratingBarStyleSmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"

@ -41,7 +41,7 @@
<RatingBar <RatingBar
android:id="@+id/ratingBar" android:id="@+id/ratingBar"
style="@style/Widget.AppCompat.RatingBar.Small" style="?ratingBarStyleSmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/textView_title" android:layout_below="@id/textView_title"

@ -9,6 +9,7 @@
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp" android:layout_marginVertical="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
app:cardBackgroundColor="?colorOnPrimary"
app:cardCornerRadius="24dp"> app:cardCornerRadius="24dp">
<LinearLayout <LinearLayout
@ -58,4 +59,4 @@
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

@ -120,6 +120,8 @@
<string name="text_search_holder_secondary">Try to reformulate the query.</string> <string name="text_search_holder_secondary">Try to reformulate the query.</string>
<string name="text_history_holder_primary">What you read will be displayed here</string> <string name="text_history_holder_primary">What you read will be displayed here</string>
<string name="text_history_holder_secondary">Find what to read in side menu.</string> <string name="text_history_holder_secondary">Find what to read in side menu.</string>
<string name="text_shelf_holder_primary">Your manga will be displayed here</string>
<string name="text_shelf_holder_secondary">Find what to read in the «Explore» section</string>
<string name="text_local_holder_primary">Save something first</string> <string name="text_local_holder_primary">Save something first</string>
<string name="text_local_holder_secondary">Save it from online sources or import files.</string> <string name="text_local_holder_secondary">Save it from online sources or import files.</string>
<string name="manga_shelf">Shelf</string> <string name="manga_shelf">Shelf</string>

@ -25,7 +25,7 @@
android:title="@string/reader_settings" /> android:title="@string/reader_settings" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.TrackerSettingsFragment" android:fragment="org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment"
android:icon="@drawable/ic_feed" android:icon="@drawable/ic_feed"
android:title="@string/check_for_new_chapters" /> android:title="@string/check_for_new_chapters" />
@ -34,4 +34,4 @@
android:icon="@drawable/ic_info_outline" android:icon="@drawable/ic_info_outline"
android:title="@string/about" /> android:title="@string/about" />
</PreferenceScreen> </PreferenceScreen>

Loading…
Cancel
Save