Skip to content

[Compose] feat: Redesign shared deck download#21150

Open
criticalAY wants to merge 5 commits into
ankidroid:mainfrom
criticalAY:sharedeck-compose
Open

[Compose] feat: Redesign shared deck download#21150
criticalAY wants to merge 5 commits into
ankidroid:mainfrom
criticalAY:sharedeck-compose

Conversation

@criticalAY
Copy link
Copy Markdown
Contributor

@criticalAY criticalAY commented May 26, 2026

Purpose / Description

Fixes

NA

Approach

First I extracted logic to viewmodel then I used composables to create fresh views proposed by @ColbyCabrera

How Has This Been Tested?

Pixel 10 and Emulator and tests

Screen_recording_20260526_203244.mp4

Learning (optional, can help others)

NA

Checklist

Please, go through these checks before submitting the PR.

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

Adds the Kotlin Compose Compiler plugin to :AnkiDroid, pulls in Compose
BOM 2026.04.01 with material3, ui-tooling, activity-compose and the
lifecycle-compose pair, and opts in to ExperimentalMaterial3Api at the
module level so screens don't need @OptIn annotations. The Compose
Compiler attaches to every Kotlin compilation in the module, so
testFixtures (which has no @composable code) needs the runtime on its
compileOnly classpath to satisfy the plugin's version check.

Also relaxes ktlint's function-naming rule for @composable functions
so PascalCase composables don't trip the linter.
…package

Creates com.ichi2.anki.shareddeck and moves SharedDecksActivity,
SharedDecksDownloadFragment and the DownloadFile data class into it,
so all shared-decks code lives in one place. External references in
the manifest, IntentHandler, DeckPicker, ActivityList and the two
tests are updated to the new import path.

SharedDecksDownloadViewModel exposes a StateFlow<SharedDecksDownloadUiState>
covering every view-visible field on the XML screen. The fragment
subscribes once in onViewCreated and renders the whole state in
renderState(); progress updates, broadcast results and user actions
are sent as SharedDecksDownloadEvent instances through a single
onEvent(event) entry point rather than a per-mutation method.

System work (DownloadManager, BroadcastReceiver, polling, file ops)
stays in the fragment. No behaviour change.
@criticalAY criticalAY added the Blocked by dependency Currently blocked by some other dependent / related change label May 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Important

Maintainers: This PR contains Strings changes

  1. Sync Translations before merging this PR and wait for the action to complete
  2. Review and merge the auto-generated PR in order to sync all user-submitted translations
  3. Sync Translations again and merge the PR so the huge automated string changes caused by merging this PR are by themselves and easy to review

Co-Authored-By: Colby Cabrera <colbycabrera.wd@gmail.com>
@criticalAY criticalAY force-pushed the sharedeck-compose branch from 8a12ea2 to b5be3b0 Compare May 26, 2026 16:29
Copy link
Copy Markdown
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

If you're going to fixup someone's PR, you need to be very careful to avoid offence.

  • Ideally reach out to the contributor first
    • (Not sure if you did; this one completely slipped by my review queue and a ton of effort went into it, and I should have done better)
  • Use the .patch url to ensure you get the author correct
  • Ideally provide author, and take the co-author, I'm unsure on the impact, but it's a nice show of goodwill

@criticalAY
Copy link
Copy Markdown
Contributor Author

I did notice the co-auth and sure I have no issue keep OP as the auther and I did take permission before proceeding @ColbyCabrera was happy I don't mind taking all this to his PR and closing this one later I am okay here whatever @ColbyCabrera thinks is good since its his work in terms of design and idea

@david-allison
Copy link
Copy Markdown
Member

All good, cheers!

@ColbyCabrera
Copy link
Copy Markdown
Contributor

If you're going to fixup someone's PR, you need to be very careful to avoid offence.

  • Ideally reach out to the contributor first

    • (Not sure if you did; this one completely slipped by my review queue and a ton of effort went into it, and I should have done better)
  • Use the .patch url to ensure you get the author correct

  • Ideally provide author, and take the co-author, I'm unsure on the impact, but it's a nice show of goodwill

No worries at all on it slipping by! (probably a good thing with the compose first announcement timing lol). I have no issues keeping it here and being co-author, he asked beforehand 🫡

Copy link
Copy Markdown
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

Partial review

<code_scheme name="Project" version="173">
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
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.

why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I didn't actually make this change idk how it happened will revert

package com.ichi2.anki

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.shareddeck.DownloadFile
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.

why?

Comment on lines +24 to +27
val downloadedFmt =
remember(downloadedBytes, configuration) {
Formatter.formatFileSize(context, downloadedBytes)
}
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.

Why is this being remembered if it effectively changes per-tick?

import com.ichi2.anki.R

@Composable
internal fun rememberDownloadSizeRange(
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.

Should this be remember? It doesn't return a mutable object:

Jetpack Compose framework development and Library development MUST prefix any @Composable factory function that internally remember {}s and returns a mutable object with the prefix remember.

App development SHOULD follow this same convention.

https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.md#naming-composable-functions-that-remember-the-objects-they-return

supportActionBar?.setDisplayShowHomeEnabled(true)

downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
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.

getSystemService<DownloadManager>

Comment on lines +264 to +266
val typedValue = TypedValue()
theme.resolveAttribute(R.attr.appBarColor, typedValue, true)
binding.webviewToolbar.setBackgroundColor(typedValue.data)
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.

MaterialColors.getColor should work here

binding.webView.webViewClient = webViewClient
onBackPressedDispatcher.addCallback(onBackPressedCallback)

supportFragmentManager.addOnBackStackChangedListener {
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.

nit: brief comment + would recommend moving into a setupX method (as ReviewerFragment does

Comment on lines +153 to +172
private fun onScreenAction(action: SharedDecksDownloadAction) {
when (action) {
SharedDecksDownloadAction.CancelConfirmed -> {
Timber.i("SharedDecksDownloadFragment:: cancelling download deck")
confirmCancelDownload()
}
SharedDecksDownloadAction.ImportClicked -> {
Timber.i("SharedDecksDownloadFragment:: opening downloaded deck")
openDownloadedDeck(requireContext())
}
SharedDecksDownloadAction.TryAgainClicked -> {
Timber.i("SharedDecksDownloadFragment:: retrying download")
retryDownload()
}
SharedDecksDownloadAction.OpenInBrowserClicked -> {
Timber.i("SharedDecksDownloadFragment:: open in browser clicked")
openInBrowser()
}
}
}
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.

🐉 Let's define a naming convention. Here was my attempt:

super.onViewCreated(view, savedInstanceState)

val fileToBeDownloaded = arguments?.getSerializableCompat<DownloadFile>(DOWNLOAD_FILE)!!
fileToBeDownloaded = arguments?.getSerializableCompat<DownloadFile>(DOWNLOAD_FILE)!!
Copy link
Copy Markdown
Member

@david-allison david-allison May 27, 2026

Choose a reason for hiding this comment

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

nit: my preference is:

I think requireSerializableCompat would need to be defined

    private val fileToBeDownloaded
        get() = requireArguments().requireSerializableCompat<DownloadFile>(DOWNLOAD_FILE)

checkDownloadProgress()

// Keep checking download progress at intervals of 1 second.
handler.postDelayed(this, DOWNLOAD_PROGRESS_CHECK_DELAY)
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.

comment felt OK, your call

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Blocked by dependency Currently blocked by some other dependent / related change Needs Review Strings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants