Skip to content

features: tags blacklisting, download queue and smart downloads#92

Open
onlyashd wants to merge 6 commits into
develfrom
feature/tags-blacklisting-and-download-queue
Open

features: tags blacklisting, download queue and smart downloads#92
onlyashd wants to merge 6 commits into
develfrom
feature/tags-blacklisting-and-download-queue

Conversation

@onlyashd

@onlyashd onlyashd commented Jul 1, 2026

Copy link
Copy Markdown
Member

This PR is bigger than planned, sorry.

Features

  • Tag blacklisting:
    Add Tag Filtering in Content #26
    hide items from search and explorer based on their tags. Manage it in Settings > Manga sources > Blacklisted tags.
  • Download queue:
    Download Scheduler - Schedule manga downloads for optimal times #74
    items can be added to a queue that will download them in the specified order.
    Order can be changed by drag-&-drop. Secondary options: "Charging only", "Off-peak" and "Storage quota".
  • Smart downloads:
    Download pages as the manga is read #82
    if enabled, the next chapter will be downloaded and the oldest read one will be deleted while you read. Example: you have 5 chapters downloaded, when you start the 4th, the 6th will be downloaded and the 1st will be deleted.

Fixes:

  • Imported downloaded sources not working #69: Added auto-scan when the app is installed and a rescan option. To ensure the scan succeds, access Explore > Local storage > Menu (three dots) > Directories > Add > Give the permissions.

Screenshots

New download options
Download quota limitation
Smart downloads image
Tags blacklisting

Tip

Logcat filters for tests:

tag:SmartDownloads | tag:DownloadQueue | tag:DownloadWorker | tag:DownloadScheduler | tag:DeleteReadChapters | tag:TagsBlacklist

Note

If the user disables Smart downloads, continues reading and then re-enable it the "skiped" chapters won't be downloaded. The deletion process will work the same, removing the oldest downloaded chapter.

onlyashd added 2 commits July 4, 2026 15:26
…sting-and-download-queue

# Conflicts:
#	app/src/main/kotlin/io/github/landwarderer/futon/core/db/migrations/Migration28To29.kt
@onlyashd onlyashd marked this pull request as ready for review July 5, 2026 00:30
@onlyashd onlyashd requested a review from LandWarderer2772 July 5, 2026 00:30
@onlyashd onlyashd changed the title Feature/tags blacklisting and download queue features: tags blacklisting, download queue and smart downloads Jul 5, 2026
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Debug APK built successfully!

Download artifact: debug-apk

Artifact will be available for 7 days

@LandWarderer2772 LandWarderer2772 mentioned this pull request Jul 5, 2026
1 task
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Debug APK built successfully!

Download artifact: debug-apk

Artifact will be available for 7 days

@LandWarderer2772 LandWarderer2772 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds user-configurable content filtering (tag blacklisting) and significantly expands the download system with a persistent queue, scheduling constraints (charging/off-peak/quota), and “smart downloads” behavior tied to reading progress. It also includes a local-library rescan flow to address imported downloads/local indexing issues.

Changes:

  • Add tag-blacklisting UI + settings key, and apply blacklist filtering across Explore/Search/list pipelines.
  • Introduce a Room-backed download queue with a scheduler worker (WorkManager) supporting charging/Wi‑Fi/off‑peak conditions and queue reordering UI.
  • Add smart-download/cleanup + storage quota enforcement hooks into download/local cleanup and reading/history flows; add supporting DB migration/converters.

Reviewed changes

Copilot reviewed 74 out of 74 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
app/src/test/kotlin/io/github/landwarderer/futon/download/ui/worker/DownloadTaskTest.kt Unit test coverage for DownloadTask serialization defaults.
app/src/test/kotlin/io/github/landwarderer/futon/download/ui/worker/DownloadSchedulerTest.kt Unit tests for off-peak window calculations.
app/src/main/res/xml/pref_sources.xml Adds “Blacklisted tags” preference entry.
app/src/main/res/xml/pref_downloads.xml Adds scheduling/limits preferences (charging, off-peak, quota, smart downloads).
app/src/main/res/values/strings.xml Adds strings for queue/scheduling/quota/rescan/blacklist.
app/src/main/res/values/dimens.xml Adds FAB bottom padding dimen for queue screen.
app/src/main/res/menu/opt_local.xml Adds “Rescan” menu action for local storage.
app/src/main/res/menu/opt_downloads.xml Adds entry point to the download queue screen.
app/src/main/res/menu/opt_download_queue.xml New queue screen actions (queue favorites, clear queue).
app/src/main/res/layout/item_download_queue.xml New RecyclerView item layout for queued downloads.
app/src/main/res/layout/activity_tags_blacklist.xml New activity layout for managing blacklisted tags.
app/src/main/res/layout/activity_downloads.xml Adds quota reached + storage usage cards to Downloads screen.
app/src/main/res/layout/activity_download_queue.xml New queue screen layout with list + start FAB.
app/src/main/kotlin/io/github/landwarderer/futon/tracker/work/TrackWorker.kt Propagates new requiresCharging field into scheduled download tasks.
app/src/main/kotlin/io/github/landwarderer/futon/suggestions/domain/TagsBlacklist.kt Adds logging and refactors blacklist checks.
app/src/main/kotlin/io/github/landwarderer/futon/settings/work/WorkScheduleManager.kt Schedules download scheduler updates on relevant preference changes.
app/src/main/kotlin/io/github/landwarderer/futon/settings/utils/TimePreference.kt New custom Preference for selecting times via MaterialTimePicker.
app/src/main/kotlin/io/github/landwarderer/futon/settings/utils/TagsAutoCompleteProvider.kt Updates tag search query path/limit.
app/src/main/kotlin/io/github/landwarderer/futon/settings/storage/directories/MangaDirectoriesViewModel.kt Adjusts directory accessibility + StatFs exception handling.
app/src/main/kotlin/io/github/landwarderer/futon/settings/storage/directories/DirectoryConfigAD.kt Updates inaccessible-directory message.
app/src/main/kotlin/io/github/landwarderer/futon/settings/sources/TagsBlacklistViewModel.kt New ViewModel to fetch tags + persist blacklist selection.
app/src/main/kotlin/io/github/landwarderer/futon/settings/sources/TagsBlacklistActivity.kt New activity to manage blacklist chips + search.
app/src/main/kotlin/io/github/landwarderer/futon/settings/sources/SourcesSettingsFragment.kt Navigates to TagsBlacklistActivity from preferences.
app/src/main/kotlin/io/github/landwarderer/futon/settings/DownloadsSettingsFragment.kt Validates quota changes; triggers scheduler updates on off-peak changes.
app/src/main/kotlin/io/github/landwarderer/futon/search/ui/multi/SearchViewModel.kt Filters search results by blacklisted tags (with DB tag backfill).
app/src/main/kotlin/io/github/landwarderer/futon/reader/ui/ReaderViewModel.kt Wires queue/smart-download dependencies; saves state on chapter change.
app/src/main/kotlin/io/github/landwarderer/futon/local/ui/LocalStorageCleanupWorker.kt Enforces storage quota during cleanup.
app/src/main/kotlin/io/github/landwarderer/futon/local/ui/LocalListMenuProvider.kt Adds “Rescan” action to trigger index update service.
app/src/main/kotlin/io/github/landwarderer/futon/local/ui/LocalListFragment.kt Updates permission request flow for Android R+.
app/src/main/kotlin/io/github/landwarderer/futon/local/domain/EnforceStorageQuotaUseCase.kt New use case to delete oldest downloaded chapters until under quota.
app/src/main/kotlin/io/github/landwarderer/futon/local/domain/DeleteReadChaptersUseCase.kt Adds favorite-skipping + oldestOnly deletion support + extra logging.
app/src/main/kotlin/io/github/landwarderer/futon/local/data/LocalStorageManager.kt Adds total-download-bytes computation helper.
app/src/main/kotlin/io/github/landwarderer/futon/local/data/LocalMangaRepository.kt Exposes APIs needed for quota enforcement (getRawList/getAllFiles).
app/src/main/kotlin/io/github/landwarderer/futon/local/data/index/LocalMangaIndex.kt Wraps update in runCatchingCancellable; adds scan debug output.
app/src/main/kotlin/io/github/landwarderer/futon/list/ui/MangaListViewModel.kt Filters lists by blacklisted tags.
app/src/main/kotlin/io/github/landwarderer/futon/history/domain/HistoryUpdateUseCase.kt Triggers smart download on chapter change.
app/src/main/kotlin/io/github/landwarderer/futon/explore/ui/ExploreViewModel.kt Filters recommendation list by blacklisted tags.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/worker/DownloadWorker.kt Adds charging constraint propagation, bandwidth limiting, smart cleanup/quota enforcement hooks.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/worker/DownloadTask.kt Adds requiresCharging to task serialization/equality.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/worker/DownloadSchedulerWorker.kt New worker to process the queue respecting conditions and off-peak scheduling.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/list/DownloadsViewModel.kt Adds observable storage usage for downloads screen.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/list/DownloadsMenuProvider.kt Routes to queue screen.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/list/DownloadsActivity.kt Displays storage/quota cards and updates padding for overlays.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/DownloadQueueViewModel.kt New ViewModel to observe queue, mutate items, and trigger scheduler.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/DownloadQueueAdapter.kt New adapter for queue list items + drag handle integration.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/DownloadQueueActivity.kt New activity for viewing/reordering queue and starting processing.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/dialog/DownloadDialogViewModel.kt Adds “queue vs start now” behavior and charging propagation.
app/src/main/kotlin/io/github/landwarderer/futon/download/ui/dialog/ChaptersSelectMacro.kt Changes “WholeManga” macro to return all chapter IDs.
app/src/main/kotlin/io/github/landwarderer/futon/download/domain/usecase/SmartDownloadUseCase.kt New use case for auto-enqueueing next chapter during reading.
app/src/main/kotlin/io/github/landwarderer/futon/download/domain/usecase/QueueAllUnreadFromFavoritesUseCase.kt New use case to enqueue unread chapters for all favorites.
app/src/main/kotlin/io/github/landwarderer/futon/download/domain/usecase/AddUnreadToQueueUseCase.kt New use case to enqueue unread chapters for a manga.
app/src/main/kotlin/io/github/landwarderer/futon/download/data/repository/SmartDownloadRepository.kt New repository for smart-download state persistence.
app/src/main/kotlin/io/github/landwarderer/futon/download/data/repository/DownloadQueueRepository.kt New repository managing queue insert/update/reorder and scheduler triggering.
app/src/main/kotlin/io/github/landwarderer/futon/download/data/entity/SmartDownloadEntity.kt New Room entity for smart-download state.
app/src/main/kotlin/io/github/landwarderer/futon/download/data/entity/DownloadQueueEntity.kt New Room entity for queued downloads.
app/src/main/kotlin/io/github/landwarderer/futon/download/data/dao/SmartDownloadDao.kt New DAO for smart-download entity access.
app/src/main/kotlin/io/github/landwarderer/futon/download/data/dao/DownloadQueueDao.kt New DAO for queue access + priority reorder transaction.
app/src/main/kotlin/io/github/landwarderer/futon/details/ui/pager/ChaptersPagesViewModel.kt Adds queue scheduling path and safer local update handling.
app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsViewModel.kt Wires in queue/scheduler dependencies through base viewmodel.
app/src/main/kotlin/io/github/landwarderer/futon/details/ui/DetailsMenuProvider.kt Fixes delete visibility for local items.
app/src/main/kotlin/io/github/landwarderer/futon/core/util/ext/IO.kt Adds optional bandwidth-limited writing for Okio sinks.
app/src/main/kotlin/io/github/landwarderer/futon/core/util/BandwidthLimiter.kt New bandwidth limiter for download throttling.
app/src/main/kotlin/io/github/landwarderer/futon/core/prefs/AppSettings.kt Adds keys/settings for blacklist, off-peak, quota, smart downloads, charging-only, bandwidth.
app/src/main/kotlin/io/github/landwarderer/futon/core/nav/AppRouter.kt Adds navigation to tags blacklist and download queue screens.
app/src/main/kotlin/io/github/landwarderer/futon/core/db/Tables.kt Adds table constants for queue and smart downloads.
app/src/main/kotlin/io/github/landwarderer/futon/core/db/migrations/Migration29To30.kt New migration creating queue + smart download tables.
app/src/main/kotlin/io/github/landwarderer/futon/core/db/MangaDatabase.kt Bumps DB version, registers entities/DAOs/migration, adds type converters.
app/src/main/kotlin/io/github/landwarderer/futon/core/db/entity/TagEntity.kt Adds index to speed tag title queries.
app/src/main/kotlin/io/github/landwarderer/futon/core/db/dao/TagsDao.kt Adds query to search all tags by title.
app/src/main/kotlin/io/github/landwarderer/futon/core/db/converters/DataConverters.kt New type converters for int/long arrays to text columns.
app/src/main/kotlin/io/github/landwarderer/futon/core/AppModule.kt Provides new DAOs via Hilt (queue + manga DAO).
app/src/main/kotlin/io/github/landwarderer/futon/backups/ui/restore/RestoreService.kt Triggers local index update service after restoring settings.
app/src/main/AndroidManifest.xml Registers TagsBlacklistActivity and DownloadQueueActivity.
app/src/androidTest/kotlin/io/github/landwarderer/futon/download/ui/worker/DownloadWorkerIntegrationTest.kt Adds instrumented test for constraint propagation (currently broken).

Comment on lines +58 to +75
if (wifiOnly != other.wifiOnly) return false
if (charging_only != other.charging_only) return false
if (offPeakOnly != other.offPeakOnly) return false

return true
}

override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + mangaId.hashCode()
result = 31 * result + chaptersIds.contentHashCode()
result = 31 * result + priority
result = 31 * result + createdAt.hashCode()
result = 31 * result + wifiOnly.hashCode()
result = 31 * result + charging_only.hashCode()
result = 31 * result + offPeakOnly.hashCode()
return result
}
Comment on lines +44 to +62
private fun fetchTags() {
launchJob(Dispatchers.IO) {
try {
val request = Request.Builder()
.url("https://raw.githubusercontent.com/AppFuton/filters/refs/heads/main/data/tags.json")
.build()
okHttpClient.newCall(request).execute().use { response ->
if (response.isSuccessful) {
val tags = response.body.byteStream().use { stream ->
Json.decodeFromStream<List<String>>(stream)
}
_allTags.value = tags.sorted()
}
}
} finally {
loadingCounter.decrement()
}
}
}
Comment on lines +97 to +102
@WorkerThread
fun getTotalBytesUsedByDownloads(): Long {
return runBlocking {
getConfiguredStorageDirs().sumOf { it.computeSize() }
}
}
Comment on lines +28 to +45
fun bind(item: DownloadQueueItem) {
binding.textViewTitle.text = item.manga?.title ?: "Unknown"
binding.textViewDetails.text = "${item.entity.chaptersIds.size} chapters"

binding.imageViewCover.setImageAsync(item.manga?.coverUrl, item.manga)

binding.buttonRemove.setOnClickListener { onRemove(item) }
binding.buttonPause.setOnClickListener { onPauseToggle(item) }
binding.buttonPause.setImageResource(
if (item.entity.isPaused) R.drawable.ic_action_resume else R.drawable.ic_action_pause
)
binding.imageViewHandle.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
onStartDrag(this)
}
false
}
}
Comment on lines +214 to +216
fun openDownloadQueue() {
startActivity(Intent(contextOrNull() ?: return, Class.forName("io.github.landwarderer.futon.download.ui.DownloadQueueActivity")))
}
Comment on lines +408 to +416
var tagsBlacklist: Set<String>
get() {
val string = prefs.getString(KEY_TAGS_BLACKLIST, null)?.trimEnd(' ', ',')
if (string.isNullOrEmpty()) {
return emptySet()
}
return string.split(',').mapToSet { it.trim() }
}
set(value) = prefs.edit { putString(KEY_TAGS_BLACKLIST, value.joinToString(", ")) }
Comment on lines +45 to +49
if (activeWorks.any { it.state == WorkInfo.State.RUNNING }) {
Log.d("DownloadScheduler", "A download is already running, scheduling check in 30s")
scheduleNextCheck(30, TimeUnit.SECONDS)
return@runCatchingCancellable Result.success()
}
Comment on lines +83 to +87
val constructor = schedulerClass.declaredConstructors[0]
constructor.isAccessible = true
val args = arrayOfNulls<Any>(constructor.parameterCount)
val schedulerInstance = constructor.newInstance(*args)

Comment on lines +44 to +51
if (delayMs > 0) {
runBlocking {
kotlinx.coroutines.delay(delayMs)
}
synchronized(this) {
lastTime = SystemClock.elapsedRealtime()
}
}
Comment on lines +44 to +48
println("LocalMangaIndex: Starting update")
runCatchingCancellable {
db.withTransaction {
val dao = db.getLocalMangaIndexDao()
dao.clear()
@LandWarderer2772

Copy link
Copy Markdown
Member

@onlyashd are any of the things the AI listed something we should worry about?

@LandWarderer2772 LandWarderer2772 linked an issue Jul 8, 2026 that may be closed by this pull request
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

3 participants