[Compose] feat: Redesign shared deck download#21150
Conversation
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.
|
Important Maintainers: This PR contains Strings changes
|
cbafb4f to
8a12ea2
Compare
Co-Authored-By: Colby Cabrera <colbycabrera.wd@gmail.com>
8a12ea2 to
b5be3b0
Compare
There was a problem hiding this comment.
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
.patchurl to ensure you get the author correct- As it stands, the co-author is incorrect
- sample: https://github.com/ankidroid/Anki-Android/commit/1de48393876fbf764939692e10b07adb691c04ae.patch
- Ideally provide
author, and take theco-author, I'm unsure on the impact, but it's a nice show of goodwill
|
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 |
|
All good, cheers! |
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 🫡 |
| <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"> |
There was a problem hiding this comment.
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 |
| val downloadedFmt = | ||
| remember(downloadedBytes, configuration) { | ||
| Formatter.formatFileSize(context, downloadedBytes) | ||
| } |
There was a problem hiding this comment.
Why is this being remembered if it effectively changes per-tick?
| import com.ichi2.anki.R | ||
|
|
||
| @Composable | ||
| internal fun rememberDownloadSizeRange( |
There was a problem hiding this comment.
Should this be remember? It doesn't return a mutable object:
Jetpack Compose framework development and Library development MUST prefix any
@Composablefactory function that internallyremember {}s and returns a mutable object with the prefixremember.App development SHOULD follow this same convention.
| supportActionBar?.setDisplayShowHomeEnabled(true) | ||
|
|
||
| downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager | ||
| downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager |
There was a problem hiding this comment.
getSystemService<DownloadManager>
| val typedValue = TypedValue() | ||
| theme.resolveAttribute(R.attr.appBarColor, typedValue, true) | ||
| binding.webviewToolbar.setBackgroundColor(typedValue.data) |
There was a problem hiding this comment.
MaterialColors.getColor should work here
| binding.webView.webViewClient = webViewClient | ||
| onBackPressedDispatcher.addCallback(onBackPressedCallback) | ||
|
|
||
| supportFragmentManager.addOnBackStackChangedListener { |
There was a problem hiding this comment.
nit: brief comment + would recommend moving into a setupX method (as ReviewerFragment does
| 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() | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🐉 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)!! |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
comment felt OK, your call
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.