Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JS Addons] list addons from addons directory #10985

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

krmanik
Copy link
Member

@krmanik krmanik commented Apr 20, 2022

Pull Request template

Purpose / Description

This is UI for addons listing in recycler view from addons directory. The list in view will contains details, remove and toggle switch button.
Also addons menu added to side bar but it will be hidden till all the PR merged.
The implementation will be used in next PR when addons listed from network for download and install.

Fixes

Fixes #7959

Approach

  1. dist package.json made empty when package.json loaded from local directory.
  2. Popup window class created to show info related the current clicked addons
  3. Added a class to list addons from directory, the validated AddonModel added to adapter
  4. Each view in list have three buttons
    details button - show popup with addons info like name, author, version etc.
    remove button - remove the addons from directory
    toggle switch - used to enable/disable addons, for enabled addons, the content will be added to reviewer or note editor

How Has This Been Tested?

Tested on emulator and devices. (Test will be added in next PR)

  1. Listing of empty folder
  2. Listing when one addon added manually (currently manually, in next PR download and install will be implemented)
  3. Info popup window test
  4. Toggle switch test
  5. Delete test
demo.mp4

Learning (optional, can help others)

Checklist

Please, go through these checks before submitting the PR.

  • You have not changed whitespace unnecessarily (it makes diffs hard to read)
  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • Your code follows the style of the project (e.g. never omit braces in if statements)
  • 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

@krmanik krmanik force-pushed the list-addons-from-dir branch from 00ebeac to 8cf509f Compare April 20, 2022 07:55
Copy link
Member

@mikehardy mikehardy left a comment

Choose a reason for hiding this comment

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

A bunch of unrelated comments :-), I don't see anything really wrong here - a minor structural question, some Kotlin null-typing stuff and some grammar bikeshedding ... Curious for thoughts on all of them

AnkiDroid/src/main/res/values/02-strings.xml Outdated Show resolved Hide resolved
AnkiDroid/src/main/res/values/02-strings.xml Outdated Show resolved Hide resolved
AnkiDroid/src/main/res/values/02-strings.xml Outdated Show resolved Hide resolved
AnkiDroid/src/main/res/values/02-strings.xml Outdated Show resolved Hide resolved
AnkiDroid/src/main/res/values/02-strings.xml Outdated Show resolved Hide resolved
Comment on lines 101 to 108
private fun deleteSelectedAddonPackageDir(addonModel: AddonModel) {
// remove the js addon folder
val currentAnkiDroidDirectory = CollectionHelper.getCurrentAnkiDroidDirectory(context)
val addonsHomeDir = File(currentAnkiDroidDirectory, "addons")
val dir = File(addonsHomeDir, addonModel.name)

val deleted = BackupManager.removeDir(dir)

Copy link
Member

Choose a reason for hiding this comment

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

I don't want to over-engineer it, but I feel like having the filesystem code mixed in with the UI code will make it harder to test etc - you have AddonModel, Is there an AddonStorage perhaps, where the "get me the add on directory / get me the list of addons in directory / delete the addon" code could go? Then it would be really easy to have a test just for that stuff

Copy link
Member Author

Choose a reason for hiding this comment

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

I have created AddonStorage class. It is more cleaner and better implementation. Thanks

*
* @param addonModel
*/
private fun deleteSelectedAddonPackageDir(addonModel: AddonModel) {
Copy link
Member

Choose a reason for hiding this comment

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

I think delete can go in the new extracted AddOnStorage object now

Copy link
Member

@mikehardy mikehardy left a comment

Choose a reason for hiding this comment

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

I think it's really close now - can put more into the storage class and use it by reference, and should probably do a second refactor as a follow-on to have code from previous PRs use the storage class as well and centralize all the directory / file handling (from the download/install/unpack PRs)

@mikehardy mikehardy added the Needs Author Reply Waiting for a reply from the original author label Apr 24, 2022
@krmanik
Copy link
Member Author

krmanik commented Apr 24, 2022

I think this is good idea to handle the addons related file/folder related from a centralized class. I will do refactor in next PR. (Added to TODO)

@krmanik krmanik added the Blocked by dependency Currently blocked by some other dependent / related change label Apr 25, 2022
@mikehardy
Copy link
Member

I'm going to move this to draft just as a strategy to manage the PR review queue, and because I know you are focused on exams right now (as you should be!). Cheers

@mikehardy mikehardy marked this pull request as draft May 10, 2022 15:40
@krmanik
Copy link
Member Author

krmanik commented May 11, 2022

I will update the PR later.

@github-actions
Copy link
Contributor

Hello 👋, this PR has been opened for more than 2 months with no activity on it. If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 7 days until this gets closed automatically

@github-actions github-actions bot added the Stale label Jul 10, 2022
@krmanik krmanik removed the Stale label Jul 10, 2022
@github-actions
Copy link
Contributor

Hello 👋, this PR has been opened for more than 2 months with no activity on it. If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 7 days until this gets closed automatically

@github-actions github-actions bot added the Stale label Dec 12, 2022
@mikehardy
Copy link
Member

Not stale - dormant because of too much work on my end and prioritizing scoped storage but this will all happen. JS addons are the future and this is the infrastructure...

@github-actions github-actions bot removed the Stale label Dec 18, 2022
@github-actions
Copy link
Contributor

Hello 👋, this PR has been opened for more than 2 months with no activity on it. If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 7 days until this gets closed automatically

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2023

Hello 👋, this PR has had no activity for more than 2 weeks and needs a reply from the author. If you think this is a mistake please comment and ping a maintainer to get this merged ASAP! Thanks for contributing! You have 7 days until this gets closed automatically

@github-actions github-actions bot added the Stale label Mar 5, 2023
@mikehardy mikehardy added Keep Open avoids the stale bot and removed Stale labels Mar 5, 2023
@mikehardy
Copy link
Member

The JS AddOn series needs to stay open. It is perfect for 2.17 once storage is finally done

@krmanik
Copy link
Member Author

krmanik commented Mar 6, 2023

The JS AddOn series needs to stay open. It is perfect for 2.17 once storage is finally done

I will start implementing this after scope storage.

@lukstbit
Copy link
Member

lukstbit commented Oct 4, 2024

@krmanik If you want I can look into fixing and updating the code in this PR to get it merged. Maybe the other related PR as well.

@mikehardy
Copy link
Member

Pains me greatly that these aren't merged yet and I feel a lot of responsibility there - not sure how @krmanik feels about it but I would owe you or anyone a debt of gratitude if the work here was finally shepherded in. It's good stuff IMHO and would be a big step forward I think

@david-allison
Copy link
Member

@BrayanDSO I believe you had the last conversation regarding addons in the forums. Pinging for your understanding & thoughts

@BrayanDSO
Copy link
Member

Reviewer add-ons won't happen in the desktop version if they aren't 99,99% secure, and Damien apparently doesn't want it even as a per-notetype opt-in setting with a big warning or just stopped replying because he's busy with something else.

To avoid duplicating the bulletproofing job in the mobile versions, the reviewer should ideally be moved to Svelte first.

I do think that there should be a JS API and addons infrastructure as soon as possible so we can take advantage of the work of other developers. If we wait until all of those desktop issues to be solved (probably at least 3 years from now if I'm optimistic with the current development pace), we will lose a lot of work that could be done in the addons ecosystem in that meantime.

There are a lot of developers, including myself, that may have some time and motivation right now, but not in the future. I could have implemented the whole infrastructure when I created the forums post, but I lost two weeks just in discussion, and stopped getting replied. Now I don't have time for it, nor I want to do it anymore in the future.

So I strongly suggest to continue the JS addons work here in AnkiDroid. To avoid issues in the future, like when Anki implemented a different TTS syntax, I suggest consulting Damien and making him compromise with the API, with whatever is saved in the collection, and with the way that addons are distributed.
That way, things won't break if he wants to distribute things in GitHub with zip files instead of npm with tar.gz, or save the addons list ordered by creation date instead of ID (just an invented example, idk what is planned).

@lukstbit
Copy link
Member

Ok, I'll be implementing the code in this PR for now.

@lukstbit lukstbit self-assigned this Oct 13, 2024
@lukstbit lukstbit force-pushed the list-addons-from-dir branch 3 times, most recently from 6fcb827 to b4aebcc Compare October 17, 2024 09:13
@lukstbit lukstbit marked this pull request as ready for review October 17, 2024 09:33
@lukstbit lukstbit added Priority-Low and removed Needs Author Reply Waiting for a reply from the original author Blocked by dependency Currently blocked by some other dependent / related change Has Conflicts labels Oct 17, 2024
@lukstbit lukstbit added this to the 2.20 Release milestone Oct 17, 2024
@lukstbit
Copy link
Member

I've made changes to the PR to fix the conflicts and update the UI. The feature sits behind a dev setting.
I've left the strings hardcoded, there's no need to ask for translations if this isn't near release.


@krmanik
Copy link
Member Author

krmanik commented Oct 18, 2024

Thanks @lukstbit for continuing this PR. This is good features we can support for AnkiDroid. I still busy at the moment but willing to review if I get chance.

Copy link
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.

I think we all want this to move forward. Let's do it

Given the nature of this feature, I'd like to see a little documentation of folder structures on the disk, and how profiles will interact with this feature when they're added.

In Anki Desktop, addons are global across profiles. This change doesn't allow for this.

I'd like to think about deep links to addons, and how/whether we'd want to accomplish this

@@ -463,6 +463,10 @@
android:theme="@style/Theme.AppCompat.NoActionBar"
android:configChanges="keyboardHidden|screenSize" />

<activity android:name=".jsaddons.AddonsBrowserActivity"
Copy link
Member

Choose a reason for hiding this comment

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

Don't use jsaddons, we can't change the package name later, and this ties us to a technology

@@ -463,6 +463,10 @@
android:theme="@style/Theme.AppCompat.NoActionBar"
android:configChanges="keyboardHidden|screenSize" />

<activity android:name=".jsaddons.AddonsBrowserActivity"
android:parentActivityName=".DeckPicker"
Copy link
Member

@david-allison david-allison Dec 10, 2024

Choose a reason for hiding this comment

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

Is this correct? I suspect we're starting this from Preferences in the initial release

Comment on lines +154 to +167
// package.json in tgz file does not contains dist but it is available when the file loaded
// from network, the dist contains .tgz url which will be used to download the file
Copy link
Member

Choose a reason for hiding this comment

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

Move to KDoc for the class, or move to a constructor which can handle fromNetwork/fromFile


override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val addonModel =
BundleCompat.getParcelable(requireArguments(), KEY_ADDON_MODEL, AddonModel::class.java)
Copy link
Member

Choose a reason for hiding this comment

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

can this be extracted to a val

companion object {
private const val KEY_ADDON_MODEL = "key_addon_model"

fun newInstance(addonModel: AddonModel): AddonDetailsDialog = AddonDetailsDialog().apply {
Copy link
Member

Choose a reason for hiding this comment

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

@CheckResult

* @return some-addon dir e.g. AnkiDroid/addons/some-addon/
*/
fun getSelectedAddonDir(addonName: String): File {
return File(addonsHomeDir, addonName)
Copy link
Member

Choose a reason for hiding this comment

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

⚠️ Path traversal vulnerability

*/
fun deleteSelectedAddonPackageDir(addonName: String): Boolean {
val dir = getSelectedAddonDir(addonName)
return BackupManager.removeDir(dir)
Copy link
Member

Choose a reason for hiding this comment

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

use a better named API

*/
// TODO ===== EXTRACT RELATED STRINGS FOR TRANSLATION BEFORE RELEASE =====
class AddonsBrowserActivity : AnkiActivity() {
private val addonsAdapter by lazy {
Copy link
Member

Choose a reason for hiding this comment

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

Can we move to a ViewModel here?

Comment on lines +91 to +101
return oldItem.modelRef.name == newItem.modelRef.name &&
oldItem.modelRef.addonType == newItem.modelRef.addonType &&
oldItem.modelRef.addonTitle == newItem.modelRef.addonTitle &&
oldItem.modelRef.author["name"] == oldItem.modelRef.author["name"] &&
oldItem.modelRef.author["url"] == oldItem.modelRef.author["url"] &&
oldItem.modelRef.version == newItem.modelRef.version &&
oldItem.modelRef.ankidroidJsApi == newItem.modelRef.ankidroidJsApi &&
oldItem.modelRef.description == newItem.modelRef.description &&
oldItem.modelRef.homepage == newItem.modelRef.homepage &&
oldItem.modelRef.license == newItem.modelRef.license &&
oldItem.modelRef.dist.tarball == newItem.modelRef.dist.tarball
Copy link
Member

Choose a reason for hiding this comment

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

move to the class

android:src="@drawable/ic_extension"
app:tint="@color/material_blue_grey_100" />

<TextView
Copy link
Member

Choose a reason for hiding this comment

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

FIxedTextVIew

@mikehardy
Copy link
Member

Given the nature of this feature, I'd like to see a little documentation of folder structures on the disk, and how profiles will interact with this feature when they're added.

In Anki Desktop, addons are global across profiles. This change doesn't allow for this.

Interesting. With per-profile add-ons you can have the same add-on for everyone but you can also have "AnkiGamify" and "Ank-2-button" (I'm making those up) just for your toddler's profile as they're learning phonics without it affecting yours.

That is, per-profile is the most powerful style IMHO.

If it was one or the other, I'd say per-profile.
If global was important, then I'd say both, and it is similar to when you install software and it asks "do you want to install this for all users of the system or just yourself?"

Deep links? Perhaps we should just get it in first 😆

@krmanik
Copy link
Member Author

krmanik commented Dec 11, 2024

@david-allison @mikehardy
Thanks for the review and comments, I will start updating this features in coming few days.

Contains the initial code to fetch and parse addons and also the basic
UI to display the addons.
@krmanik krmanik force-pushed the list-addons-from-dir branch from b4aebcc to 61feaa0 Compare December 16, 2024 08:49
@krmanik
Copy link
Member Author

krmanik commented Dec 16, 2024

Android Studio interface is changed, rebased the code, now add changes to the code.

@david-allison

This comment was marked as resolved.

@krmanik
Copy link
Member Author

krmanik commented Dec 18, 2024

@david-allison Thanks for the changes, I have added it to local codebase. I have started implementing the suggested changes, will take some days to implement the features.

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

Successfully merging this pull request may close these issues.

[Feature] JavaScript Addons support for AnkiDroid
5 participants