Configure manga tracker for each favourite category

This commit is contained in:
Koitharu
2022-05-05 13:44:26 +03:00
parent ffad6a4ae6
commit 11fc8b6642
50 changed files with 584 additions and 132 deletions

View File

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

View File

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

View File

@@ -5,5 +5,5 @@ import org.koin.dsl.module
val databaseModule
get() = module {
single { MangaDatabase.create(androidContext()) }
single { MangaDatabase(androidContext()) }
}

View File

@@ -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()

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import org.koin.dsl.module
val githubModule
get() = module {
single {
factory {
GithubRepository(get())
}
}

View File

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

View File

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

View File

@@ -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())

View File

@@ -11,4 +11,5 @@ fun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong())
sortKey = sortKey,
order = SortOrder(order, SortOrder.NEWEST),
createdAt = Date(createdAt),
isTrackingEnabled = track,
)

View File

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

View File

@@ -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,
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()

View File

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

View File

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

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
android:clipToPadding="false"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="8dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="8dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:padding="8dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem" />
<TextView
android:id="@android:id/summary"
style="@style/PreferenceSummaryTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:maxLines="10"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorSecondary" />
</LinearLayout>
<LinearLayout
android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end|center_vertical"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="0dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>

View File

@@ -23,4 +23,9 @@
</item>
<item
android:id="@+id/action_tracking"
android:title="@string/check_for_new_chapters"
android:checkable="true" />
</menu>

View File

@@ -101,7 +101,6 @@
<string name="notifications">Паведамленні</string>
<string name="enabled_d_of_d">Уключана %1$d з %2$d</string>
<string name="new_chapters">Новыя главы</string>
<string name="show_notification_new_chapters">Апавяшчаць пра абнаўленні мангі, якую вы чытаеце</string>
<string name="download">Спампаваць</string>
<string name="read_from_start">Чытаць з пачатку</string>
<string name="restart">Перазапусціць</string>

View File

@@ -46,7 +46,6 @@
<string name="updates_feed_cleared">Aktualisierungsfeed gelöscht</string>
<string name="clear_updates_feed">Aktualisierungsfeed löschen</string>
<string name="updates">Aktualisierungen</string>
<string name="show_notification_new_chapters">Über Aktualisierungen von Manga benachrichtigen, die du liest</string>
<string name="show_notification_app_update">Benachrichtigung anzeigen, wenn eine Aktualisierung verfügbar ist</string>
<string name="app_update_available">Anwendungsaktualisierung ist verfügbar</string>
<string name="application_update">Automatisch nach Aktualisierungen suchen</string>

View File

@@ -101,7 +101,6 @@
<string name="notifications">Notificaciones</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Activado %1$d de %2$d</string>
<string name="new_chapters">Nuevos capítulos</string>
<string name="show_notification_new_chapters">Notificar sobre las actualizaciones del manga que estás leyendo</string>
<string name="download">Descargar</string>
<string name="read_from_start">Leer desde el principio</string>
<string name="restart">Reiniciar</string>

View File

@@ -118,7 +118,6 @@
<string name="restart">Käynnistä uudelleen</string>
<string name="read_from_start">Lue alusta</string>
<string name="download">Lataa</string>
<string name="show_notification_new_chapters">Ilmoita lukemastasi mangan päivityksistä</string>
<string name="new_chapters">Uusia lukuja</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Käytössä %1$d / %2$d</string>
<string name="notifications">Ilmoitukset</string>

View File

@@ -112,7 +112,6 @@
<string name="notifications_settings">Paramètres des notifications</string>
<string name="read_from_start">Lire depuis le début</string>
<string name="download">Télécharger</string>
<string name="show_notification_new_chapters">Avertir des mises à jour des mangas que vous lisez</string>
<string name="new_chapters">Nouveaux chapitres</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d de %2$d activé(s)</string>
<string name="notifications">Notifications</string>

View File

@@ -154,7 +154,6 @@
<string name="restart">Riavvia</string>
<string name="read_from_start">Leggi dall\'inizio</string>
<string name="download">Scarica</string>
<string name="show_notification_new_chapters">Notifica gli aggiornamenti dei manga che stai leggendo</string>
<string name="new_chapters">Nuovi capitoli</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Abilitato %1$d di %2$d</string>
<string name="notifications">Notifiche</string>

View File

@@ -143,7 +143,6 @@
<string name="clear_search_history">検索履歴をクリア</string>
<string name="external_storage">外部ストレージ</string>
<string name="app_update_available">Kotatsuの新しい更新が利用可能です</string>
<string name="show_notification_new_chapters">あなたが読んでいる漫画の更新について通知</string>
<string name="text_empty_holder_primary">ここは空っぽです…</string>
<string name="text_categories_holder">カテゴリーを使用してお気に入りを整理できます。 «+»を押してカテゴリーを作成出来ます</string>
<string name="favourites_category_empty">空のカテゴリー</string>

View File

@@ -54,7 +54,6 @@
<string name="category_delete_confirm">Fjern «%s»-kategorien fra favorittene\?
\nAlle mangaer i den vil bli tapt.</string>
<string name="restart">Programomstart</string>
<string name="show_notification_new_chapters">Gi merknad om oppdateringer av det du leser</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d av %2$d påskrudd</string>
<string name="large_manga_save_confirm">Denne mangaen har %s. Lagre hele\?</string>
<string name="open_in_browser">Åpne i nettleser</string>

View File

@@ -232,7 +232,6 @@
<string name="clear_thumbs_cache">Limpar cache de miniaturas</string>
<string name="application_update">Verifique se há novas versões do aplicativo</string>
<string name="favourites_categories">Categorias favoritas</string>
<string name="show_notification_new_chapters">Notificar sobre atualizações de mangá que você está lendo</string>
<string name="category_delete_confirm">Remover a categoria “%s” dos seus favoritos\?
\nTodos os mangás nela serão perdidos.</string>
<string name="no_update_available">Nenhuma atualização disponível</string>

View File

@@ -83,7 +83,6 @@
<string name="save_manga">Salve</string>
<string name="notifications">Notificações</string>
<string name="new_chapters">Novos capítulos</string>
<string name="show_notification_new_chapters">Notifique sobre atualizações do mangá que está lendo</string>
<string name="download">Download</string>
<string name="read_from_start">Ler desde o início</string>
<string name="restart">Reiniciar</string>

View File

@@ -101,7 +101,6 @@
<string name="notifications">Уведомления</string>
<string name="enabled_d_of_d">Включено %1$d из %2$d</string>
<string name="new_chapters">Новые главы</string>
<string name="show_notification_new_chapters">Уведомлять об обновлении манги, которую Вы читаете</string>
<string name="download">Загрузить</string>
<string name="read_from_start">Читать с начала</string>
<string name="restart">Перезапустить</string>
@@ -278,4 +277,8 @@
<string name="chapters_will_removed_background">Главы будут удалены в фоновом режиме. Это может занять какое-то время</string>
<string name="hide">Скрыть</string>
<string name="new_sources_text">Доступны новые источники манги</string>
<string name="check_new_chapters_title">Проверять новые главы и уведомлять о них</string>
<string name="show_notification_new_chapters_on">Вы будете получать уведомления об обновлении манги, которую Вы читаете</string>
<string name="show_notification_new_chapters_off">Вы не будете получать уведомления, но новые главы будут отображаться в списке</string>
<string name="notifications_enable">Включить уведомления</string>
</resources>

View File

@@ -105,7 +105,6 @@
<string name="download">Ladda ned</string>
<string name="notifications_settings">Aviseringsinställningar</string>
<string name="light_indicator">LED-indikator</string>
<string name="show_notification_new_chapters">Avisera om uppdateringar på manga du läser</string>
<string name="read_from_start">Läs från början</string>
<string name="restart">Starta om</string>
<string name="notification_sound">Aviseringsljud</string>

View File

@@ -234,7 +234,6 @@
<string name="system_default">Öntanımlı</string>
<string name="error_empty_name">Bir ad girmelisiniz</string>
<string name="auth_not_supported_by">%s üzerinde oturum açma desteklenmiyor</string>
<string name="show_notification_new_chapters">Okunan manga güncellemeleri hakkında bildirimde bulun</string>
<string name="read_more">Daha fazla oku</string>
<string name="tracker_warning">Bazı aygıtların arka plan görevlerini bozabilecek farklı sistem davranışları vardır.</string>
<string name="screenshots_policy">Ekran görüntüsü politikası</string>

View File

@@ -103,7 +103,6 @@
<string name="notifications">Notifications</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d of %2$d on</string>
<string name="new_chapters">New chapters</string>
<string name="show_notification_new_chapters">Notify about updates of manga you are reading</string>
<string name="download">Download</string>
<string name="read_from_start">Read from start</string>
<string name="restart">Restart</string>
@@ -281,4 +280,8 @@
<string name="chapters_will_removed_background">Chapters will be removed in the background. It can take some time</string>
<string name="hide">Hide</string>
<string name="new_sources_text">New manga sources are available</string>
<string name="check_new_chapters_title">Check for new chapters and notify about it</string>
<string name="show_notification_new_chapters_on">You will receive notifications about updates of manga you are reading</string>
<string name="show_notification_new_chapters_off">You will not receive notifications but new chapters will be highlighted in the lists</string>
<string name="notifications_enable">Enable notifications</string>
</resources>

View File

@@ -1,19 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="tracker_notifications"
android:layout="@layout/preference_toggle_header"
android:title="@string/notifications_enable" />
<Preference
android:dependency="tracker_notifications"
android:key="notifications_sound"
android:title="@string/notification_sound" />
<CheckBoxPreference
android:defaultValue="false"
android:dependency="tracker_notifications"
android:key="notifications_vibrate"
android:title="@string/vibration" />
<CheckBoxPreference
android:defaultValue="true"
android:dependency="tracker_notifications"
android:key="notifications_light"
android:title="@string/light_indicator" />
<org.koitharu.kotatsu.settings.utils.LinksPreference
android:icon="@drawable/ic_info_outline"
android:key="tracker_notifications_info"
android:persistent="false"
android:selectable="false"
android:summary="@string/show_notification_new_chapters_off"
app:allowDividerAbove="true"
app:isPreferenceVisible="false" />
</PreferenceScreen>

View File

@@ -6,7 +6,7 @@
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="suggestions"
android:summary="@string/suggestions_summary"
android:layout="@layout/preference_toggle_header"
android:title="@string/suggestions_enable" />
<org.koitharu.kotatsu.settings.utils.MultiAutoCompleteTextViewPreference

View File

@@ -3,23 +3,28 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="tracker_enabled"
android:layout="@layout/preference_toggle_header"
android:title="@string/check_new_chapters_title" />
<MultiSelectListPreference
android:defaultValue="@array/values_track_sources_default"
android:dependency="tracker_enabled"
android:entries="@array/track_sources"
android:entryValues="@array/values_track_sources"
android:key="track_sources"
android:title="@string/track_sources" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="tracker_notifications"
android:summary="@string/show_notification_new_chapters"
android:title="@string/notifications" />
<Preference
android:key="track_categories"
android:title="@string/favourites_categories" />
<Preference
android:dependency="tracker_notifications"
android:fragment="org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment"
android:key="notifications_settings"
android:dependency="tracker_enabled"
android:title="@string/notifications_settings" />
<org.koitharu.kotatsu.settings.utils.LinksPreference