diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index 13701c33d..c5ebfae04 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu import android.app.Application import android.content.Context import android.os.StrictMode +import androidx.annotation.WorkerThread import androidx.appcompat.app.AppCompatDelegate import androidx.fragment.app.strictmode.FragmentStrictMode import androidx.hilt.work.HiltWorkerFactory @@ -10,6 +11,8 @@ import androidx.room.InvalidationTracker import androidx.work.Configuration import dagger.hilt.android.HiltAndroidApp import javax.inject.Inject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.acra.ReportField import org.acra.config.dialog 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.domain.LocalMangaRepository import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.utils.ext.processLifecycleScope @HiltAndroidApp class KotatsuApp : Application(), Configuration.Provider { @@ -46,7 +50,9 @@ class KotatsuApp : Application(), Configuration.Provider { } AppCompatDelegate.setDefaultNightMode(settings.theme) setupActivityLifecycleCallbacks() - setupDatabaseObservers() + processLifecycleScope.launch(Dispatchers.Default) { + setupDatabaseObservers() + } } override fun attachBaseContext(base: Context?) { @@ -86,6 +92,7 @@ class KotatsuApp : Application(), Configuration.Provider { .build() } + @WorkerThread private fun setupDatabaseObservers() { val tracker = database.invalidationTracker databaseObservers.forEach { diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt index b86a87dce..5b9fbde29 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/decor/SpacingItemDecoration.kt @@ -11,8 +11,8 @@ class SpacingItemDecoration(@Px private val spacing: Int) : RecyclerView.ItemDec outRect: Rect, view: View, parent: RecyclerView, - state: RecyclerView.State + state: RecyclerView.State, ) { outRect.set(spacing, spacing, spacing, spacing) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/BottomSheetHeaderBar.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/BottomSheetHeaderBar.kt index ff6f05a29..3aaa7270d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/BottomSheetHeaderBar.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/BottomSheetHeaderBar.kt @@ -12,6 +12,7 @@ import android.view.WindowInsets import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.DecelerateInterpolator import androidx.annotation.AttrRes +import androidx.annotation.StringRes import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.withStyledAttributes @@ -147,6 +148,14 @@ class BottomSheetHeaderBar @JvmOverloads constructor( 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<*>?) { bottomSheetBehavior?.removeBottomSheetCallback(bottomSheetCallback) bottomSheetBehavior = behavior diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt index a9649e942..5cbc17385 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -2,9 +2,13 @@ package org.koitharu.kotatsu.core.db import android.content.Context import androidx.room.Database +import androidx.room.InvalidationTracker import androidx.room.Room import androidx.room.RoomDatabase 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.BookmarksDao 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.TrackLogEntity import org.koitharu.kotatsu.tracker.data.TracksDao +import org.koitharu.kotatsu.utils.ext.processLifecycleScope const val DATABASE_VERSION = 14 @@ -88,3 +93,12 @@ fun MangaDatabase(context: Context): MangaDatabase = Room .addMigrations(*databaseMigrations) .addCallback(DatabasePrePopulateCallback(context.resources)) .build() + +fun InvalidationTracker.removeObserverAsync(observer: InvalidationTracker.Observer) { + val scope = processLifecycleScope + if (scope.isActive) { + processLifecycleScope.launch(Dispatchers.Default) { + removeObserver(observer) + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 371287b66..25b43c7c2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -7,13 +7,11 @@ import android.content.IntentFilter import android.os.Bundle import android.view.Menu import android.view.View -import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.widget.PopupMenu import androidx.core.graphics.Insets import androidx.core.view.isGone import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import com.google.android.material.badge.BadgeDrawable @@ -209,7 +207,7 @@ class DetailsActivity : right = insets.right, ) if (insets.bottom > 0) { - window.setNavigationBarTransparentCompat(this, binding.layoutBottom?.elevation ?: 0f) + window.setNavigationBarTransparentCompat(this, binding.layoutBottom?.elevation ?: 0f, 0.9f) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt index 90bd30365..4d24c9104 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.download.ui +import android.view.View import androidx.core.view.isVisible import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding @@ -9,6 +10,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.koitharu.kotatsu.R import org.koitharu.kotatsu.databinding.ItemDownloadBinding +import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.utils.ext.* @@ -22,6 +24,19 @@ fun downloadItemAD( var job: Job? = null 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 { job?.cancel() job = item.progressAsFlow().onFirst { state -> diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt index 61724a6db..f5e1c4996 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt @@ -14,7 +14,9 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import org.koitharu.kotatsu.R 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.download.ui.service.DownloadService import org.koitharu.kotatsu.utils.bindServiceWithLifecycle @@ -30,6 +32,8 @@ class DownloadsActivity : BaseActivity() { setContentView(ActivityDownloadsBinding.inflate(layoutInflater)) supportActionBar?.setDisplayHomeAsUpEnabled(true) val adapter = DownloadsAdapter(lifecycleScope, coil) + val spacing = resources.getDimensionPixelOffset(R.dimen.grid_spacing) + binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing)) binding.recyclerView.setHasFixedSize(true) binding.recyclerView.adapter = adapter bindServiceWithLifecycle( diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt index bf54797be..c564e82e9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt @@ -139,6 +139,8 @@ class DownloadService : BaseService() { notification.create(job.progressValue, -1L) }, ) + jobs.remove(job.progressValue.startId) + jobCount.value = jobs.size stopSelf(startId) } } @@ -158,8 +160,7 @@ class DownloadService : BaseService() { when (intent?.action) { ACTION_DOWNLOAD_CANCEL -> { val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0) - jobs.remove(cancelId)?.cancel() - jobCount.value = jobs.size + jobs[cancelId]?.cancel() } ACTION_DOWNLOAD_RESUME -> { val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt index 83d5ab0d3..88325d1bb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoriesDao.kt @@ -32,8 +32,8 @@ abstract class FavouriteCategoriesDao { @Query("UPDATE favourite_categories SET `order` = :order WHERE category_id = :id") abstract suspend fun updateOrder(id: Long, order: String) - // @Query("UPDATE favourite_categories SET `track` = :isEnabled WHERE category_id = :id") - // abstract suspend fun updateTracking(id: Long, isEnabled: Boolean) + @Query("UPDATE favourite_categories SET `track` = :isEnabled WHERE category_id = :id") + abstract suspend fun updateTracking(id: Long, isEnabled: Boolean) @Query("UPDATE favourite_categories SET `show_in_lib` = :isEnabled WHERE category_id = :id") abstract suspend fun updateLibVisibility(id: Long, isEnabled: Boolean) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index 4cdbf8311..1c391de8a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -108,6 +108,10 @@ class FavouritesRepository @Inject constructor( db.favouriteCategoriesDao.updateLibVisibility(id, isVisibleInLibrary) } + suspend fun updateCategoryTracking(id: Long, isTrackingEnabled: Boolean) { + db.favouriteCategoriesDao.updateTracking(id, isTrackingEnabled) + } + suspend fun removeCategory(id: Long) { db.withTransaction { db.favouriteCategoriesDao.delete(id) diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt index 0d7cafb0d..46e318b1f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt @@ -130,8 +130,8 @@ class LibraryViewModel @Inject constructor( if (result.isEmpty()) { result += EmptyState( icon = R.drawable.ic_empty_history, - textPrimary = R.string.text_history_holder_primary, - textSecondary = R.string.text_history_holder_secondary, + textPrimary = R.string.text_shelf_holder_primary, + textSecondary = R.string.text_shelf_holder_secondary, actionStringRes = 0, ) } @@ -168,11 +168,13 @@ class LibraryViewModel @Inject constructor( favourites: Map>, ) { for ((category, list) in favourites) { - destination += LibrarySectionModel.Favourites( - items = list.toUi(ListMode.GRID, this), - category = category, - showAllButtonText = R.string.show_all, - ) + if (list.isNotEmpty()) { + destination += LibrarySectionModel.Favourites( + items = list.toUi(ListMode.GRID, this), + category = category, + showAllButtonText = R.string.show_all, + ) + } } } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt index d79a93bf8..f00be0080 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt @@ -26,6 +26,7 @@ import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.scrobbling.shikimori.ui.ShikimoriSettingsFragment import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment +import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment import org.koitharu.kotatsu.utils.ext.isScrolledToTop @AndroidEntryPoint diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt index d71fb079f..77ba1aaf5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel import java.util.* import javax.inject.Inject -import kotlin.Comparator import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.prefs.AppSettings @@ -41,6 +40,7 @@ class OnboardViewModel @Inject constructor( if (selectedLocales.isEmpty()) { selectedLocales += "en" } + selectedLocales += null } rebuildList() } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/TrackerSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/TrackerSettingsFragment.kt similarity index 89% rename from app/src/main/java/org/koitharu/kotatsu/settings/TrackerSettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/tracker/TrackerSettingsFragment.kt index 58a0308f9..e3ee6a41d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/TrackerSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/TrackerSettingsFragment.kt @@ -1,4 +1,4 @@ -package org.koitharu.kotatsu.settings +package org.koitharu.kotatsu.settings.tracker import android.annotation.SuppressLint import android.content.ActivityNotFoundException @@ -15,20 +15,18 @@ import android.view.View import androidx.core.net.toUri import androidx.core.text.buildSpannedString import androidx.core.text.inSpans +import androidx.fragment.app.viewModels import androidx.preference.MultiSelectListPreference import androidx.preference.Preference import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject -import kotlinx.coroutines.launch import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment 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.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels -import org.koitharu.kotatsu.utils.ext.viewLifecycleScope private const val KEY_IGNORE_DOZE = "ignore_dose" @@ -37,8 +35,7 @@ class TrackerSettingsFragment : BasePreferenceFragment(R.string.check_for_new_chapters), SharedPreferences.OnSharedPreferenceChangeListener { - @Inject - lateinit var repository: TrackingRepository + private val viewModel by viewModels() @Inject lateinit var channels: TrackerNotificationChannels @@ -66,13 +63,13 @@ class TrackerSettingsFragment : findPreference(KEY_IGNORE_DOZE)?.run { isVisible = isDozeIgnoreAvailable(context) } - updateCategoriesSummary() updateNotificationsSummary() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) settings.subscribe(this) + viewModel.categoriesCount.observe(viewLifecycleOwner, ::onCategoriesCountChanged) } override fun onDestroyView() { @@ -109,7 +106,7 @@ class TrackerSettingsFragment : } } AppSettings.KEY_TRACK_CATEGORIES -> { - startActivity(FavouriteCategoriesActivity.newIntent(preference.context)) + TrackerCategoriesConfigSheet.show(childFragmentManager) true } KEY_IGNORE_DOZE -> { @@ -136,11 +133,10 @@ class TrackerSettingsFragment : pref.isEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources } - private fun updateCategoriesSummary() { + private fun onCategoriesCountChanged(count: IntArray?) { val pref = findPreference(AppSettings.KEY_TRACK_CATEGORIES) ?: return - viewLifecycleScope.launch { - val count = repository.getCategoriesCount() - pref.summary = getString(R.string.enabled_d_of_d, count[0], count[1]) + pref.summary = count?.let { + getString(R.string.enabled_d_of_d, count[0], count[1]) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/tracker/TrackerSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/TrackerSettingsViewModel.kt new file mode 100644 index 000000000..0c32ba5cf --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/TrackerSettingsViewModel.kt @@ -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(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) { + vm?.updateCategoriesCount() + } + + override fun close() { + (vm ?: return).database.invalidationTracker.removeObserverAsync(this) + vm = null + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigAdapter.kt new file mode 100644 index 000000000..d70833062 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigAdapter.kt @@ -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, +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + delegatesManager.addDelegate(trackerCategoryAD(listener)) + } + + class DiffCallback : DiffUtil.ItemCallback() { + + 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 + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigSheet.kt b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigSheet.kt new file mode 100644 index 000000000..a463660aa --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigSheet.kt @@ -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(), + OnListItemClickListener, + View.OnClickListener { + + private val viewModel by viewModels() + + 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) + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigViewModel.kt new file mode 100644 index 000000000..1b9c10f7f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoriesConfigViewModel.kt @@ -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) + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoryAD.kt b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoryAD.kt new file mode 100644 index 000000000..5e3876285 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/settings/tracker/categories/TrackerCategoryAD.kt @@ -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, +) = adapterDelegateViewBinding( + { 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 + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt index 9f4c34953..ba5063fd8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/AndroidExt.kt @@ -15,14 +15,11 @@ import android.net.Uri import android.os.Build import android.provider.Settings import android.view.View -import android.view.ViewGroup import android.view.ViewPropertyAnimator import android.view.Window import androidx.activity.result.ActivityResultLauncher import androidx.annotation.IntegerRes import androidx.core.app.ActivityOptionsCompat -import androidx.core.view.children -import androidx.core.view.descendants import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope import androidx.work.CoroutineWorker @@ -104,7 +101,7 @@ fun SyncResult.onError(error: Throwable) { 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 && !InternalResourceHelper.getBoolean(context, "config_navBarNeedsScrim", true) ) { @@ -112,7 +109,7 @@ fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float } else { // Set navbar scrim 70% of navigationBarColor ElevationOverlayProvider(context).compositeOverlayIfNeeded( - context.getResourceColor(android.R.attr.navigationBarColor, 0.7F), + context.getThemeColor(android.R.attr.navigationBarColor, alphaFactor), elevation, ) } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ResourcesExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ResourcesExt.kt index 8735eb970..4d9d8186a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ResourcesExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ResourcesExt.kt @@ -1,15 +1,7 @@ package org.koitharu.kotatsu.utils.ext -import android.content.Context import android.content.res.Resources -import android.graphics.Color -import androidx.annotation.AttrRes -import androidx.annotation.ColorInt 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 @Px @@ -17,17 +9,3 @@ fun Resources.resolveDp(dp: Int) = (dp * displayMetrics.density).roundToInt() @Px 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 -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThemeExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThemeExt.kt index 85ee5ea39..7896da2e5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThemeExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThemeExt.kt @@ -4,7 +4,9 @@ import android.content.Context import android.graphics.Color import androidx.annotation.AttrRes import androidx.annotation.ColorInt +import androidx.annotation.FloatRange import androidx.core.content.res.use +import androidx.core.graphics.ColorUtils fun Context.getThemeDrawable( @AttrRes resId: Int, @@ -15,13 +17,29 @@ fun Context.getThemeDrawable( @ColorInt fun Context.getThemeColor( @AttrRes resId: Int, - @ColorInt default: Int = Color.TRANSPARENT + @ColorInt fallback: Int = Color.TRANSPARENT, ) = 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( @AttrRes resId: Int, ) = obtainStyledAttributes(intArrayOf(resId)).use { it.getColorStateList(0) -} \ No newline at end of file +} diff --git a/app/src/main/res/layout-w600dp/fragment_details.xml b/app/src/main/res/layout-w600dp/fragment_details.xml index 5365076fe..29aaefc20 100644 --- a/app/src/main/res/layout-w600dp/fragment_details.xml +++ b/app/src/main/res/layout-w600dp/fragment_details.xml @@ -83,7 +83,7 @@ - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 053e88ad1..0f08a45c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -120,6 +120,8 @@ Try to reformulate the query. What you read will be displayed here Find what to read in side menu. + Your manga will be displayed here + Find what to read in the «Explore» section Save something first Save it from online sources or import files. Shelf diff --git a/app/src/main/res/xml/pref_root.xml b/app/src/main/res/xml/pref_root.xml index a7ed841c0..0bd27de2f 100644 --- a/app/src/main/res/xml/pref_root.xml +++ b/app/src/main/res/xml/pref_root.xml @@ -25,7 +25,7 @@ android:title="@string/reader_settings" /> @@ -34,4 +34,4 @@ android:icon="@drawable/ic_info_outline" android:title="@string/about" /> - \ No newline at end of file +