Merge branch 'feature/tracker_categories' into devel
This commit is contained in:
@@ -121,6 +121,7 @@ class BackupRepository(private val db: MangaDatabase) {
|
||||
jo.put("sort_key", sortKey)
|
||||
jo.put("title", title)
|
||||
jo.put("order", order)
|
||||
jo.put("track", track)
|
||||
return jo
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ class RestoreRepository(private val db: MangaDatabase) {
|
||||
sortKey = json.getInt("sort_key"),
|
||||
title = json.getString("title"),
|
||||
order = json.getStringOrNull("order") ?: SortOrder.NEWEST.name,
|
||||
track = json.getBooleanOrDefault("track", true),
|
||||
)
|
||||
|
||||
private fun parseFavourite(json: JSONObject) = FavouriteEntity(
|
||||
|
||||
@@ -5,5 +5,5 @@ import org.koin.dsl.module
|
||||
|
||||
val databaseModule
|
||||
get() = module {
|
||||
single { MangaDatabase.create(androidContext()) }
|
||||
single { MangaDatabase(androidContext()) }
|
||||
}
|
||||
@@ -10,8 +10,8 @@ class DatabasePrePopulateCallback(private val resources: Resources) : RoomDataba
|
||||
|
||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL(
|
||||
"INSERT INTO favourite_categories (created_at, sort_key, title, `order`) VALUES (?,?,?,?)",
|
||||
arrayOf(System.currentTimeMillis(), 1, resources.getString(R.string.read_later), SortOrder.NEWEST.name)
|
||||
"INSERT INTO favourite_categories (created_at, sort_key, title, `order`, track) VALUES (?,?,?,?,?)",
|
||||
arrayOf(System.currentTimeMillis(), 1, resources.getString(R.string.read_later), SortOrder.NEWEST.name, 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import org.koitharu.kotatsu.suggestions.data.SuggestionEntity
|
||||
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class,
|
||||
TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class
|
||||
],
|
||||
version = 9
|
||||
version = 10
|
||||
)
|
||||
abstract class MangaDatabase : RoomDatabase() {
|
||||
|
||||
@@ -43,24 +43,22 @@ abstract class MangaDatabase : RoomDatabase() {
|
||||
abstract val trackLogsDao: TrackLogsDao
|
||||
|
||||
abstract val suggestionDao: SuggestionDao
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(context: Context): MangaDatabase = Room.databaseBuilder(
|
||||
context,
|
||||
MangaDatabase::class.java,
|
||||
"kotatsu-db"
|
||||
).addMigrations(
|
||||
Migration1To2(),
|
||||
Migration2To3(),
|
||||
Migration3To4(),
|
||||
Migration4To5(),
|
||||
Migration5To6(),
|
||||
Migration6To7(),
|
||||
Migration7To8(),
|
||||
Migration8To9(),
|
||||
).addCallback(
|
||||
DatabasePrePopulateCallback(context.resources)
|
||||
).build()
|
||||
}
|
||||
}
|
||||
fun MangaDatabase(context: Context): MangaDatabase = Room.databaseBuilder(
|
||||
context,
|
||||
MangaDatabase::class.java,
|
||||
"kotatsu-db"
|
||||
).addMigrations(
|
||||
Migration1To2(),
|
||||
Migration2To3(),
|
||||
Migration3To4(),
|
||||
Migration4To5(),
|
||||
Migration5To6(),
|
||||
Migration6To7(),
|
||||
Migration7To8(),
|
||||
Migration8To9(),
|
||||
Migration9To10(),
|
||||
).addCallback(
|
||||
DatabasePrePopulateCallback(context.resources)
|
||||
).build()
|
||||
@@ -10,6 +10,9 @@ abstract class TracksDao {
|
||||
@Query("SELECT * FROM tracks")
|
||||
abstract suspend fun findAll(): List<TrackEntity>
|
||||
|
||||
@Query("SELECT * FROM tracks WHERE manga_id IN (:ids)")
|
||||
abstract suspend fun findAll(ids: Collection<Long>): List<TrackEntity>
|
||||
|
||||
@Query("SELECT * FROM tracks WHERE manga_id = :mangaId")
|
||||
abstract suspend fun find(mangaId: Long): TrackEntity?
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.koitharu.kotatsu.core.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration9To10 : Migration(9, 10) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN `track` INTEGER NOT NULL DEFAULT 1")
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import org.koin.dsl.module
|
||||
|
||||
val githubModule
|
||||
get() = module {
|
||||
single {
|
||||
factory {
|
||||
GithubRepository(get())
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.koitharu.kotatsu.core.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import java.util.*
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import java.util.*
|
||||
|
||||
@Parcelize
|
||||
data class FavouriteCategory(
|
||||
@@ -12,4 +12,5 @@ data class FavouriteCategory(
|
||||
val sortKey: Int,
|
||||
val order: SortOrder,
|
||||
val createdAt: Date,
|
||||
val isTrackingEnabled: Boolean,
|
||||
) : Parcelable
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.core.prefs
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.ConnectivityManager
|
||||
@@ -78,7 +79,10 @@ class AppSettings(context: Context) {
|
||||
get() = prefs.getLong(KEY_APP_UPDATE, 0L)
|
||||
set(value) = prefs.edit { putLong(KEY_APP_UPDATE, value) }
|
||||
|
||||
val trackerNotifications: Boolean
|
||||
val isTrackerEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_TRACKER_ENABLED, true)
|
||||
|
||||
val isTrackerNotificationsEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_TRACKER_NOTIFICATIONS, true)
|
||||
|
||||
var notificationSound: Uri
|
||||
@@ -269,13 +273,16 @@ class AppSettings(context: Context) {
|
||||
const val KEY_REMOTE_SOURCES = "remote_sources"
|
||||
const val KEY_LOCAL_STORAGE = "local_storage"
|
||||
const val KEY_READER_SWITCHERS = "reader_switchers"
|
||||
const val KEY_TRACKER_ENABLED = "tracker_enabled"
|
||||
const val KEY_TRACK_SOURCES = "track_sources"
|
||||
const val KEY_TRACK_CATEGORIES = "track_categories"
|
||||
const val KEY_TRACK_WARNING = "track_warning"
|
||||
const val KEY_TRACKER_NOTIFICATIONS = "tracker_notifications"
|
||||
const val KEY_NOTIFICATIONS_SETTINGS = "notifications_settings"
|
||||
const val KEY_NOTIFICATIONS_SOUND = "notifications_sound"
|
||||
const val KEY_NOTIFICATIONS_VIBRATE = "notifications_vibrate"
|
||||
const val KEY_NOTIFICATIONS_LIGHT = "notifications_light"
|
||||
const val KEY_NOTIFICATIONS_INFO = "tracker_notifications_info"
|
||||
const val KEY_READER_ANIMATION = "reader_animation"
|
||||
const val KEY_READER_PREFER_RTL = "reader_prefer_rtl"
|
||||
const val KEY_APP_PASSWORD = "app_password"
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.koitharu.kotatsu.favourites.ui.list.FavouritesListViewModel
|
||||
val favouritesModule
|
||||
get() = module {
|
||||
|
||||
single { FavouritesRepository(get()) }
|
||||
factory { FavouritesRepository(get(), get()) }
|
||||
|
||||
viewModel { categoryId ->
|
||||
FavouritesListViewModel(categoryId.get(), get(), get(), get())
|
||||
|
||||
@@ -11,4 +11,5 @@ fun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong())
|
||||
sortKey = sortKey,
|
||||
order = SortOrder(order, SortOrder.NEWEST),
|
||||
createdAt = Date(createdAt),
|
||||
isTrackingEnabled = track,
|
||||
)
|
||||
@@ -30,6 +30,9 @@ 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 sort_key = :sortKey WHERE category_id = :id")
|
||||
abstract suspend fun updateSortKey(id: Long, sortKey: Int)
|
||||
|
||||
|
||||
@@ -12,4 +12,5 @@ class FavouriteCategoryEntity(
|
||||
@ColumnInfo(name = "sort_key") val sortKey: Int,
|
||||
@ColumnInfo(name = "title") val title: String,
|
||||
@ColumnInfo(name = "order") val order: String,
|
||||
@ColumnInfo(name = "track") val track: Boolean,
|
||||
)
|
||||
@@ -43,6 +43,9 @@ abstract class FavouritesDao {
|
||||
@Query("SELECT * FROM favourites WHERE category_id = :categoryId GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit OFFSET :offset")
|
||||
abstract suspend fun findAll(categoryId: Long, offset: Int, limit: Int): List<FavouriteManga>
|
||||
|
||||
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM favourites WHERE category_id = :categoryId)")
|
||||
abstract suspend fun findAllManga(categoryId: Int): List<MangaEntity>
|
||||
|
||||
@Query("SELECT * FROM manga WHERE manga_id IN (SELECT manga_id FROM favourites)")
|
||||
abstract suspend fun findAllManga(): List<MangaEntity>
|
||||
|
||||
|
||||
@@ -13,9 +13,13 @@ import org.koitharu.kotatsu.favourites.data.FavouriteEntity
|
||||
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
|
||||
import org.koitharu.kotatsu.utils.ext.mapItems
|
||||
|
||||
class FavouritesRepository(private val db: MangaDatabase) {
|
||||
class FavouritesRepository(
|
||||
private val db: MangaDatabase,
|
||||
private val channels: TrackerNotificationChannels,
|
||||
) {
|
||||
|
||||
suspend fun getAllManga(): List<Manga> {
|
||||
val entities = db.favouritesDao.findAll()
|
||||
@@ -65,23 +69,32 @@ class FavouritesRepository(private val db: MangaDatabase) {
|
||||
sortKey = db.favouriteCategoriesDao.getNextSortKey(),
|
||||
categoryId = 0,
|
||||
order = SortOrder.NEWEST.name,
|
||||
track = true,
|
||||
)
|
||||
val id = db.favouriteCategoriesDao.insert(entity)
|
||||
return entity.toFavouriteCategory(id)
|
||||
val category = entity.toFavouriteCategory(id)
|
||||
channels.createChannel(category)
|
||||
return category
|
||||
}
|
||||
|
||||
suspend fun renameCategory(id: Long, title: String) {
|
||||
db.favouriteCategoriesDao.updateTitle(id, title)
|
||||
channels.renameChannel(id, title)
|
||||
}
|
||||
|
||||
suspend fun removeCategory(id: Long) {
|
||||
db.favouriteCategoriesDao.delete(id)
|
||||
channels.deleteChannel(id)
|
||||
}
|
||||
|
||||
suspend fun setCategoryOrder(id: Long, order: SortOrder) {
|
||||
db.favouriteCategoriesDao.updateOrder(id, order.name)
|
||||
}
|
||||
|
||||
suspend fun setCategoryTracking(id: Long, isEnabled: Boolean) {
|
||||
db.favouriteCategoriesDao.updateTracking(id, isEnabled)
|
||||
}
|
||||
|
||||
suspend fun reorderCategories(orderedIds: List<Long>) {
|
||||
val dao = db.favouriteCategoriesDao
|
||||
db.withTransaction {
|
||||
|
||||
@@ -150,6 +150,10 @@ class FavouritesContainerFragment :
|
||||
menuItem.isChecked = item == category.order
|
||||
}
|
||||
submenu.setGroupCheckable(R.id.group_order, true, true)
|
||||
menu.findItem(R.id.action_tracking)?.run {
|
||||
isVisible = viewModel.isFavouritesTrackerEnabled
|
||||
isChecked = category.isTrackingEnabled
|
||||
}
|
||||
}
|
||||
|
||||
private fun TabLayout.setTabsEnabled(enabled: Boolean) {
|
||||
@@ -168,6 +172,7 @@ class FavouritesContainerFragment :
|
||||
R.id.action_remove -> editDelegate.deleteCategory(category)
|
||||
R.id.action_rename -> editDelegate.renameCategory(category)
|
||||
R.id.action_create -> editDelegate.createCategory()
|
||||
R.id.action_tracking -> viewModel.setCategoryTracking(category.id, !category.isTrackingEnabled)
|
||||
R.id.action_order -> return@setOnMenuItemClickListener false
|
||||
else -> {
|
||||
val order = CategoriesActivity.SORT_ORDERS.getOrNull(it.order)
|
||||
|
||||
@@ -30,7 +30,8 @@ class CategoriesActivity :
|
||||
BaseActivity<ActivityCategoriesBinding>(),
|
||||
OnListItemClickListener<FavouriteCategory>,
|
||||
View.OnClickListener,
|
||||
CategoriesEditDelegate.CategoriesEditCallback, AllCategoriesToggleListener {
|
||||
CategoriesEditDelegate.CategoriesEditCallback,
|
||||
AllCategoriesToggleListener {
|
||||
|
||||
private val viewModel by viewModel<FavouritesCategoriesViewModel>()
|
||||
|
||||
@@ -63,11 +64,12 @@ class CategoriesActivity :
|
||||
override fun onItemClick(item: FavouriteCategory, view: View) {
|
||||
val menu = PopupMenu(view.context, view)
|
||||
menu.inflate(R.menu.popup_category)
|
||||
createOrderSubmenu(menu.menu, item)
|
||||
prepareCategoryMenu(menu.menu, item)
|
||||
menu.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_remove -> editDelegate.deleteCategory(item)
|
||||
R.id.action_rename -> editDelegate.renameCategory(item)
|
||||
R.id.action_tracking -> viewModel.setCategoryTracking(item.id, !item.isTrackingEnabled)
|
||||
R.id.action_order -> return@setOnMenuItemClickListener false
|
||||
else -> {
|
||||
val order = SORT_ORDERS.getOrNull(menuItem.order) ?: return@setOnMenuItemClickListener false
|
||||
@@ -124,7 +126,7 @@ class CategoriesActivity :
|
||||
viewModel.createCategory(name)
|
||||
}
|
||||
|
||||
private fun createOrderSubmenu(menu: Menu, category: FavouriteCategory) {
|
||||
private fun prepareCategoryMenu(menu: Menu, category: FavouriteCategory) {
|
||||
val submenu = menu.findItem(R.id.action_order)?.subMenu ?: return
|
||||
for ((i, item) in SORT_ORDERS.withIndex()) {
|
||||
val menuItem = submenu.add(
|
||||
@@ -137,6 +139,10 @@ class CategoriesActivity :
|
||||
menuItem.isChecked = item == category.order
|
||||
}
|
||||
submenu.setGroupCheckable(R.id.group_order, true, true)
|
||||
menu.findItem(R.id.action_tracking)?.run {
|
||||
isVisible = viewModel.isFavouritesTrackerEnabled
|
||||
isChecked = category.isTrackingEnabled
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ReorderHelperCallback : ItemTouchHelper.SimpleCallback(
|
||||
|
||||
@@ -40,7 +40,10 @@ class CategoriesAdapter(
|
||||
newItem: CategoryListModel,
|
||||
): Any? = when {
|
||||
oldItem is CategoryListModel.All && newItem is CategoryListModel.All -> Unit
|
||||
else -> super.getChangePayload(oldItem, newItem)
|
||||
oldItem is CategoryListModel.CategoryItem &&
|
||||
newItem is CategoryListModel.CategoryItem &&
|
||||
oldItem.category.title != newItem.category.title -> null
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
@@ -11,7 +12,6 @@ import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.adapter.CategoryListModel
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import java.util.*
|
||||
|
||||
class FavouritesCategoriesViewModel(
|
||||
private val repository: FavouritesRepository,
|
||||
@@ -34,6 +34,9 @@ class FavouritesCategoriesViewModel(
|
||||
mapCategories(list, showAll, showAll)
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
val isFavouritesTrackerEnabled: Boolean
|
||||
get() = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources
|
||||
|
||||
fun createCategory(name: String) {
|
||||
launchJob {
|
||||
repository.addCategory(name)
|
||||
@@ -58,6 +61,12 @@ class FavouritesCategoriesViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun setCategoryTracking(id: Long, isEnabled: Boolean) {
|
||||
launchJob {
|
||||
repository.setCategoryTracking(id, isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
fun setAllCategoriesVisible(isVisible: Boolean) {
|
||||
settings.isAllFavouritesVisible = isVisible
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ sealed interface CategoryListModel : ListModel {
|
||||
if (category.id != other.category.id) return false
|
||||
if (category.title != other.category.title) return false
|
||||
if (category.order != other.category.order) return false
|
||||
if (category.isTrackingEnabled != other.category.isTrackingEnabled) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -53,6 +54,7 @@ sealed interface CategoryListModel : ListModel {
|
||||
var result = category.id.hashCode()
|
||||
result = 31 * result + category.title.hashCode()
|
||||
result = 31 * result + category.order.hashCode()
|
||||
result = 31 * result + category.isTrackingEnabled.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@ import org.koitharu.kotatsu.history.ui.HistoryListViewModel
|
||||
val historyModule
|
||||
get() = module {
|
||||
|
||||
single { HistoryRepository(get(), get(), get()) }
|
||||
factory { HistoryRepository(get(), get(), get()) }
|
||||
viewModel { HistoryListViewModel(get(), get(), get(), get()) }
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.local.ui.LocalListViewModel
|
||||
val localModule
|
||||
get() = module {
|
||||
|
||||
single { LocalStorageManager(androidContext(), get()) }
|
||||
factory { LocalStorageManager(androidContext(), get()) }
|
||||
single { LocalMangaRepository(get()) }
|
||||
|
||||
factory { DownloadManager.Factory(androidContext(), get(), get(), get(), get(), get()) }
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel
|
||||
val mainModule
|
||||
get() = module {
|
||||
single { AppProtectHelper(get()) }
|
||||
single { ShortcutsRepository(androidContext(), get(), get(), get()) }
|
||||
factory { ShortcutsRepository(androidContext(), get(), get(), get()) }
|
||||
viewModel { MainViewModel(get(), get()) }
|
||||
viewModel { ProtectViewModel(get(), get()) }
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
||||
val readerModule
|
||||
get() = module {
|
||||
|
||||
single { MangaDataRepository(get()) }
|
||||
factory { MangaDataRepository(get()) }
|
||||
single { PagesCache(get()) }
|
||||
|
||||
factory { PageSaveHelper(get(), androidContext()) }
|
||||
|
||||
@@ -13,8 +13,7 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||
val searchModule
|
||||
get() = module {
|
||||
|
||||
single { MangaSearchRepository(get(), get(), androidContext(), get()) }
|
||||
|
||||
factory { MangaSearchRepository(get(), get(), androidContext(), get()) }
|
||||
factory { MangaSuggestionsProvider.createSuggestions(androidContext()) }
|
||||
|
||||
viewModel { params ->
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.media.RingtoneManager
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
@@ -11,7 +12,9 @@ import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.utils.RingtonePickContract
|
||||
|
||||
class NotificationSettingsLegacyFragment : BasePreferenceFragment(R.string.notifications) {
|
||||
class NotificationSettingsLegacyFragment :
|
||||
BasePreferenceFragment(R.string.notifications),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private val ringtonePickContract = registerForActivityResult(
|
||||
RingtonePickContract(get<Context>().getString(R.string.notification_sound))
|
||||
@@ -25,15 +28,28 @@ class NotificationSettingsLegacyFragment : BasePreferenceFragment(R.string.notif
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_notifications)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
findPreference<Preference>(AppSettings.KEY_NOTIFICATIONS_SOUND)?.run {
|
||||
val uri = settings.notificationSound
|
||||
summary = RingtoneManager.getRingtone(context, uri)?.getTitle(context)
|
||||
?: getString(R.string.silent)
|
||||
}
|
||||
updateInfo()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
settings.subscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
settings.unsubscribe(this)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences?, key: String?) {
|
||||
when (key) {
|
||||
AppSettings.KEY_TRACKER_NOTIFICATIONS -> updateInfo()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
@@ -45,4 +61,9 @@ class NotificationSettingsLegacyFragment : BasePreferenceFragment(R.string.notif
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateInfo() {
|
||||
findPreference<Preference>(AppSettings.KEY_NOTIFICATIONS_INFO)
|
||||
?.isVisible = !settings.isTrackerNotificationsEnabled
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ import org.koitharu.kotatsu.settings.sources.SourcesSettingsViewModel
|
||||
val settingsModule
|
||||
get() = module {
|
||||
|
||||
single { BackupRepository(get()) }
|
||||
single { RestoreRepository(get()) }
|
||||
factory { BackupRepository(get()) }
|
||||
factory { RestoreRepository(get()) }
|
||||
single(createdAtStart = true) { AppSettings(androidContext()) }
|
||||
|
||||
viewModel { BackupViewModel(get(), androidContext()) }
|
||||
|
||||
@@ -1,21 +1,34 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.text.style.URLSpan
|
||||
import android.view.View
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.Preference
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
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.CategoriesActivity
|
||||
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
|
||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
|
||||
class TrackerSettingsFragment : BasePreferenceFragment(R.string.check_for_new_chapters) {
|
||||
class TrackerSettingsFragment :
|
||||
BasePreferenceFragment(R.string.check_for_new_chapters),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private val repository by inject<TrackingRepository>()
|
||||
private val channels by inject<TrackerNotificationChannels>()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_tracker)
|
||||
@@ -32,22 +45,81 @@ class TrackerSettingsFragment : BasePreferenceFragment(R.string.check_for_new_ch
|
||||
}
|
||||
}
|
||||
}
|
||||
updateCategoriesEnabled()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
updateCategoriesSummary()
|
||||
updateNotificationsSummary()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
settings.subscribe(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
settings.unsubscribe(this)
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String?) {
|
||||
when (key) {
|
||||
AppSettings.KEY_TRACKER_NOTIFICATIONS -> updateNotificationsSummary()
|
||||
AppSettings.KEY_TRACK_SOURCES,
|
||||
AppSettings.KEY_TRACKER_ENABLED -> updateCategoriesEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
return when (preference.key) {
|
||||
AppSettings.KEY_NOTIFICATIONS_SETTINGS -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
|
||||
AppSettings.KEY_NOTIFICATIONS_SETTINGS -> when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
|
||||
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||
.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName)
|
||||
.putExtra(Settings.EXTRA_CHANNEL_ID, TrackWorker.CHANNEL_ID)
|
||||
startActivity(intent)
|
||||
true
|
||||
} else {
|
||||
}
|
||||
channels.areNotificationsDisabled -> {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
.setData(Uri.fromParts("package", requireContext().packageName, null))
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
AppSettings.KEY_TRACK_CATEGORIES -> {
|
||||
startActivity(CategoriesActivity.newIntent(preference.context))
|
||||
true
|
||||
}
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateNotificationsSummary() {
|
||||
val pref = findPreference<Preference>(AppSettings.KEY_NOTIFICATIONS_SETTINGS) ?: return
|
||||
pref.setSummary(
|
||||
when {
|
||||
channels.areNotificationsDisabled -> R.string.disabled
|
||||
channels.isNotificationGroupEnabled() -> R.string.show_notification_new_chapters_on
|
||||
else -> R.string.show_notification_new_chapters_off
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateCategoriesEnabled() {
|
||||
val pref = findPreference<Preference>(AppSettings.KEY_TRACK_CATEGORIES) ?: return
|
||||
pref.isEnabled = settings.isTrackerEnabled && AppSettings.TRACK_FAVOURITES in settings.trackSources
|
||||
}
|
||||
|
||||
private fun updateCategoriesSummary() {
|
||||
val pref = findPreference<Preference>(AppSettings.KEY_TRACK_CATEGORIES) ?: return
|
||||
viewLifecycleScope.launch {
|
||||
val count = repository.getCategoriesCount()
|
||||
pref.summary = getString(R.string.enabled_d_of_d, count[0], count[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ class AppBackupAgent : BackupAgent() {
|
||||
}
|
||||
|
||||
private fun createBackupFile() = runBlocking {
|
||||
val repository = BackupRepository(MangaDatabase.create(applicationContext))
|
||||
val repository = BackupRepository(MangaDatabase(applicationContext))
|
||||
BackupZipOutput(this@AppBackupAgent).use { backup ->
|
||||
backup.put(repository.createIndex())
|
||||
backup.put(repository.dumpHistory())
|
||||
@@ -63,7 +63,7 @@ class AppBackupAgent : BackupAgent() {
|
||||
}
|
||||
|
||||
private fun restoreBackupFile(fd: FileDescriptor, size: Long) {
|
||||
val repository = RestoreRepository(MangaDatabase.create(applicationContext))
|
||||
val repository = RestoreRepository(MangaDatabase(applicationContext))
|
||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||
FileInputStream(fd).use { input ->
|
||||
tempFile.outputStream().use { output ->
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package org.koitharu.kotatsu.tracker
|
||||
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.tracker.ui.FeedViewModel
|
||||
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
|
||||
|
||||
val trackerModule
|
||||
get() = module {
|
||||
|
||||
single { TrackingRepository(get()) }
|
||||
factory { TrackingRepository(get()) }
|
||||
factory { TrackerNotificationChannels(androidContext(), get()) }
|
||||
|
||||
viewModel { FeedViewModel(get()) }
|
||||
}
|
||||
@@ -2,15 +2,15 @@ package org.koitharu.kotatsu.tracker.domain
|
||||
|
||||
import androidx.room.withTransaction
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.TrackEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.TrackLogEntity
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.core.db.entity.toTrackingLogItem
|
||||
import org.koitharu.kotatsu.core.db.entity.*
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.model.MangaTracking
|
||||
import org.koitharu.kotatsu.core.model.TrackingLogItem
|
||||
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import java.util.*
|
||||
|
||||
class TrackingRepository(
|
||||
@@ -21,16 +21,29 @@ class TrackingRepository(
|
||||
return db.tracksDao.findNewChapters(mangaId) ?: 0
|
||||
}
|
||||
|
||||
suspend fun getAllTracks(useFavourites: Boolean, useHistory: Boolean): List<MangaTracking> {
|
||||
val mangaList = ArrayList<Manga>()
|
||||
if (useFavourites) {
|
||||
db.favouritesDao.findAllManga().mapTo(mangaList) { it.toManga(emptySet()) }
|
||||
suspend fun getHistoryManga(): List<Manga> {
|
||||
return db.historyDao.findAllManga().toMangaList()
|
||||
}
|
||||
|
||||
suspend fun getFavouritesManga(): Map<FavouriteCategory, List<Manga>> {
|
||||
val categories = db.favouriteCategoriesDao.findAll()
|
||||
return categories.associateTo(LinkedHashMap(categories.size)) { categoryEntity ->
|
||||
categoryEntity.toFavouriteCategory() to db.favouritesDao.findAllManga(categoryEntity.categoryId).toMangaList()
|
||||
}
|
||||
if (useHistory) {
|
||||
db.historyDao.findAllManga().mapTo(mangaList) { it.toManga(emptySet()) }
|
||||
}
|
||||
val tracks = db.tracksDao.findAll().groupBy { it.mangaId }
|
||||
return mangaList
|
||||
}
|
||||
|
||||
suspend fun getCategoriesCount(): IntArray {
|
||||
val categories = db.favouriteCategoriesDao.findAll()
|
||||
return intArrayOf(
|
||||
categories.count { it.track },
|
||||
categories.size,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getTracks(mangaList: Collection<Manga>): List<MangaTracking> {
|
||||
val ids = mangaList.mapToSet { it.id }
|
||||
val tracks = db.tracksDao.findAll(ids).groupBy { it.mangaId }
|
||||
return mangaList // TODO optimize
|
||||
.filterNot { it.source == MangaSource.LOCAL }
|
||||
.distinctBy { it.id }
|
||||
.map { manga ->
|
||||
@@ -103,4 +116,6 @@ class TrackingRepository(
|
||||
)
|
||||
db.tracksDao.upsert(entity)
|
||||
}
|
||||
|
||||
private fun Collection<MangaEntity>.toMangaList() = map { it.toManga(emptySet()) }
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
@@ -41,26 +40,22 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
private val coil by inject<ImageLoader>()
|
||||
private val repository by inject<TrackingRepository>()
|
||||
private val settings by inject<AppSettings>()
|
||||
private val channels by inject<TrackerNotificationChannels>()
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val trackSources = settings.trackSources
|
||||
if (trackSources.isEmpty()) {
|
||||
return Result.success()
|
||||
}
|
||||
val tracks = repository.getAllTracks(
|
||||
useFavourites = AppSettings.TRACK_FAVOURITES in trackSources,
|
||||
useHistory = AppSettings.TRACK_HISTORY in trackSources
|
||||
)
|
||||
if (tracks.isEmpty()) {
|
||||
if (!settings.isTrackerEnabled) {
|
||||
return Result.success()
|
||||
}
|
||||
if (TAG in tags) { // not expedited
|
||||
trySetForeground()
|
||||
}
|
||||
val tracks = getAllTracks()
|
||||
|
||||
var success = 0
|
||||
val workData = Data.Builder()
|
||||
.putInt(DATA_TOTAL, tracks.size)
|
||||
for ((index, track) in tracks.withIndex()) {
|
||||
for ((index, item) in tracks.withIndex()) {
|
||||
val (track, channelId) = item
|
||||
val details = runCatching {
|
||||
MangaRepository(track.manga.source).getDetails(track.manga)
|
||||
}.getOrNull()
|
||||
@@ -80,12 +75,12 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
track.knownChaptersCount == 0 && track.lastChapterId == 0L -> { // manga was empty on last check
|
||||
repository.storeTrackResult(
|
||||
mangaId = track.manga.id,
|
||||
knownChaptersCount = track.knownChaptersCount,
|
||||
knownChaptersCount = 0,
|
||||
lastChapterId = 0L,
|
||||
previousTrackChapterId = track.lastNotifiedChapterId,
|
||||
newChapters = chapters
|
||||
)
|
||||
showNotification(details, chapters)
|
||||
showNotification(details, channelId, chapters)
|
||||
}
|
||||
chapters.size == track.knownChaptersCount -> {
|
||||
if (chapters.lastOrNull()?.id == track.lastChapterId) {
|
||||
@@ -114,7 +109,8 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
)
|
||||
showNotification(
|
||||
details,
|
||||
newChapters.takeLastWhile { x -> x.id != track.lastNotifiedChapterId }
|
||||
channelId,
|
||||
newChapters.takeLastWhile { x -> x.id != track.lastNotifiedChapterId },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -126,11 +122,12 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
knownChaptersCount = track.knownChaptersCount,
|
||||
lastChapterId = track.lastChapterId,
|
||||
previousTrackChapterId = track.lastNotifiedChapterId,
|
||||
newChapters = newChapters
|
||||
newChapters = newChapters,
|
||||
)
|
||||
showNotification(
|
||||
track.manga,
|
||||
newChapters.takeLastWhile { x -> x.id != track.lastNotifiedChapterId }
|
||||
manga = track.manga,
|
||||
channelId = channelId,
|
||||
newChapters = newChapters.takeLastWhile { x -> x.id != track.lastNotifiedChapterId },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -144,13 +141,60 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun showNotification(manga: Manga, newChapters: List<MangaChapter>) {
|
||||
if (newChapters.isEmpty() || !settings.trackerNotifications) {
|
||||
private suspend fun getAllTracks(): List<TrackingItem> {
|
||||
val sources = settings.trackSources
|
||||
if (sources.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val knownIds = HashSet<Manga>()
|
||||
val result = ArrayList<TrackingItem>()
|
||||
// Favourites
|
||||
if (AppSettings.TRACK_FAVOURITES in sources) {
|
||||
val favourites = repository.getFavouritesManga()
|
||||
channels.updateChannels(favourites.keys)
|
||||
for ((category, mangaList) in favourites) {
|
||||
if (!category.isTrackingEnabled || mangaList.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
val categoryTracks = repository.getTracks(mangaList)
|
||||
val channelId = if (channels.isFavouriteNotificationsEnabled(category)) {
|
||||
channels.getFavouritesChannelId(category.id)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
for (track in categoryTracks) {
|
||||
if (knownIds.add(track.manga)) {
|
||||
result.add(TrackingItem(track, channelId))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// History
|
||||
if (AppSettings.TRACK_HISTORY in sources) {
|
||||
val history = repository.getHistoryManga()
|
||||
val historyTracks = repository.getTracks(history)
|
||||
val channelId = if (channels.isHistoryNotificationsEnabled()) {
|
||||
channels.getHistoryChannelId()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
for (track in historyTracks) {
|
||||
if (knownIds.add(track.manga)) {
|
||||
result.add(TrackingItem(track, channelId))
|
||||
}
|
||||
}
|
||||
}
|
||||
result.trimToSize()
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun showNotification(manga: Manga, channelId: String?, newChapters: List<MangaChapter>) {
|
||||
if (newChapters.isEmpty() || channelId == null) {
|
||||
return
|
||||
}
|
||||
val id = manga.url.hashCode()
|
||||
val colorPrimary = ContextCompat.getColor(applicationContext, R.color.blue_primary)
|
||||
val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||
val builder = NotificationCompat.Builder(applicationContext, channelId)
|
||||
val summary = applicationContext.resources.getQuantityString(
|
||||
R.plurals.new_chapters,
|
||||
newChapters.size, newChapters.size
|
||||
@@ -236,7 +280,6 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
|
||||
companion object {
|
||||
|
||||
const val CHANNEL_ID = "tracking"
|
||||
private const val WORKER_CHANNEL_ID = "track_worker"
|
||||
private const val WORKER_NOTIFICATION_ID = 35
|
||||
private const val DATA_PROGRESS = "progress"
|
||||
@@ -244,27 +287,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
|
||||
private const val TAG = "tracking"
|
||||
private const val TAG_ONESHOT = "tracking_oneshot"
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel(context: Context) {
|
||||
val manager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (manager.getNotificationChannel(CHANNEL_ID) == null) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
context.getString(R.string.new_chapters),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
channel.setShowBadge(true)
|
||||
channel.lightColor = ContextCompat.getColor(context, R.color.blue_primary_dark)
|
||||
channel.enableLights(true)
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
fun setup(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel(context)
|
||||
}
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package org.koitharu.kotatsu.tracker.work
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationChannelGroup
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
class TrackerNotificationChannels(
|
||||
private val context: Context,
|
||||
private val settings: AppSettings,
|
||||
) {
|
||||
|
||||
private val manager = NotificationManagerCompat.from(context)
|
||||
|
||||
val areNotificationsDisabled: Boolean
|
||||
get() = !manager.areNotificationsEnabled()
|
||||
|
||||
fun updateChannels(categories: Collection<FavouriteCategory>) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return
|
||||
}
|
||||
manager.deleteNotificationChannel(OLD_CHANNEL_ID)
|
||||
val group = createGroup()
|
||||
val existingChannels = group.channels.associateByTo(HashMap()) { it.id }
|
||||
for (category in categories) {
|
||||
val id = getFavouritesChannelId(category.id)
|
||||
if (existingChannels.remove(id)?.name == category.title) {
|
||||
continue
|
||||
}
|
||||
val channel = NotificationChannel(id, category.title, NotificationManager.IMPORTANCE_DEFAULT)
|
||||
channel.group = GROUP_ID
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
existingChannels.remove(CHANNEL_ID_HISTORY)
|
||||
createHistoryChannel()
|
||||
for (id in existingChannels.keys) {
|
||||
manager.deleteNotificationChannel(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun createChannel(category: FavouriteCategory) {
|
||||
renameChannel(category.id, category.title)
|
||||
}
|
||||
|
||||
fun renameChannel(categoryId: Long, name: String) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return
|
||||
}
|
||||
val id = getFavouritesChannelId(categoryId)
|
||||
val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_DEFAULT)
|
||||
channel.group = createGroup().id
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
fun deleteChannel(categoryId: Long) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return
|
||||
}
|
||||
manager.deleteNotificationChannel(getFavouritesChannelId(categoryId))
|
||||
}
|
||||
|
||||
fun isFavouriteNotificationsEnabled(category: FavouriteCategory): Boolean {
|
||||
if (!manager.areNotificationsEnabled()) {
|
||||
return false
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = manager.getNotificationChannel(getFavouritesChannelId(category.id))
|
||||
channel != null && channel.importance != NotificationManager.IMPORTANCE_NONE
|
||||
} else {
|
||||
// fallback
|
||||
settings.isTrackerNotificationsEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun isHistoryNotificationsEnabled(): Boolean {
|
||||
if (!manager.areNotificationsEnabled()) {
|
||||
return false
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = manager.getNotificationChannel(getHistoryChannelId())
|
||||
channel != null && channel.importance != NotificationManager.IMPORTANCE_NONE
|
||||
} else {
|
||||
// fallback
|
||||
settings.isTrackerNotificationsEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun isNotificationGroupEnabled(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return settings.isTrackerNotificationsEnabled
|
||||
}
|
||||
val group = manager.getNotificationChannelGroup(GROUP_ID) ?: return true
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && group.isBlocked) {
|
||||
return false
|
||||
}
|
||||
return group.channels.any { it.importance != NotificationManagerCompat.IMPORTANCE_NONE }
|
||||
}
|
||||
|
||||
fun getFavouritesChannelId(categoryId: Long): String {
|
||||
return CHANNEL_ID_PREFIX + categoryId
|
||||
}
|
||||
|
||||
fun getHistoryChannelId(): String {
|
||||
return CHANNEL_ID_HISTORY
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createGroup(): NotificationChannelGroup {
|
||||
manager.getNotificationChannelGroup(GROUP_ID)?.let {
|
||||
return it
|
||||
}
|
||||
val group = NotificationChannelGroup(GROUP_ID, context.getString(R.string.new_chapters))
|
||||
manager.createNotificationChannelGroup(group)
|
||||
return group
|
||||
}
|
||||
|
||||
private fun createHistoryChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return
|
||||
}
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID_HISTORY,
|
||||
context.getString(R.string.history),
|
||||
NotificationManager.IMPORTANCE_DEFAULT,
|
||||
)
|
||||
channel.group = GROUP_ID
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val GROUP_ID = "trackers"
|
||||
private const val CHANNEL_ID_PREFIX = "track_fav_"
|
||||
private const val CHANNEL_ID_HISTORY = "track_history"
|
||||
private const val OLD_CHANNEL_ID = "tracking"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.koitharu.kotatsu.tracker.work
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaTracking
|
||||
|
||||
class TrackingItem(
|
||||
val tracking: MangaTracking,
|
||||
val channelId: String?,
|
||||
) {
|
||||
|
||||
operator fun component1() = tracking
|
||||
|
||||
operator fun component2() = channelId
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as TrackingItem
|
||||
|
||||
if (tracking != other.tracking) return false
|
||||
if (channelId != other.channelId) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = tracking.hashCode()
|
||||
result = 31 * result + channelId.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user