Skip to content

Commit

Permalink
Add initial code for application addons
Browse files Browse the repository at this point in the history
Contains the initial code to fetch and parse addons and also the basic
UI to display the addons.
  • Loading branch information
krmanik authored and lukstbit committed Oct 16, 2024
1 parent 1969b28 commit b82e7e4
Show file tree
Hide file tree
Showing 18 changed files with 947 additions and 7 deletions.
4 changes: 4 additions & 0 deletions AnkiDroid/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,10 @@
android:theme="@style/Theme.AppCompat.NoActionBar"
android:configChanges="keyboardHidden|screenSize" />

<activity android:name=".jsaddons.AddonsBrowserActivity"
android:parentActivityName=".DeckPicker"
android:exported="false" />

<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
Expand Down
12 changes: 12 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ import androidx.drawerlayout.widget.ClosableDrawerLayout
import androidx.drawerlayout.widget.DrawerLayout
import com.google.android.material.color.MaterialColors
import com.google.android.material.navigation.NavigationView
import com.ichi2.anim.ActivityTransitionAnimation
import com.ichi2.anki.dialogs.help.HelpDialog
import com.ichi2.anki.jsaddons.AddonsBrowserActivity
import com.ichi2.anki.preferences.Preferences
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.workarounds.FullDraggableContainerFix
Expand Down Expand Up @@ -129,6 +131,10 @@ abstract class NavigationDrawerActivity :
// Setup toolbar and hamburger
navigationView = drawerLayout.findViewById(R.id.navdrawer_items_container)
navigationView!!.setNavigationItemSelectedListener(this)
// show the addons option as well if the dev setting is enabled
if (sharedPrefs().getBoolean(getString(R.string.new_addons_screen_pref_key), false)) {
navigationView?.menu?.findItem(R.id.nav_addons)?.isVisible = true
}
val toolbar: Toolbar? = mainView.findViewById(R.id.toolbar)
if (toolbar != null) {
setSupportActionBar(toolbar)
Expand Down Expand Up @@ -317,6 +323,12 @@ abstract class NavigationDrawerActivity :
openStatistics()
}

R.id.nav_addons -> {
Timber.i("Navigating to addons")
val intent = Intent(this, AddonsBrowserActivity::class.java)
startActivityWithAnimation(intent, ActivityTransitionAnimation.Direction.START)
}

R.id.nav_settings -> {
Timber.i("Navigating to settings")
openSettings()
Expand Down
7 changes: 5 additions & 2 deletions AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ class AddonData(
val author: Map<String, String>? = null,
val license: String? = null,
val homepage: String? = null,
val dist: DistInfo? = null
val dist: Map<String, String>? = null
)

@Serializable
// Note: used in the original code as a type for the AddonData.dist property
data class DistInfo(val tarball: String)

/**
Expand Down Expand Up @@ -151,7 +152,9 @@ fun getAddonModelFromAddonData(addonData: AddonData): Pair<AddonModel?, List<Str
author = addonData.author!!,
license = addonData.license!!,
homepage = addonData.homepage!!,
dist = addonData.dist!!
// 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
dist = addonData.dist ?: emptyMap()
)

return Pair(addonModel, immutableList)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/****************************************************************************************
* Copyright (c) 2022 Mani <infinyte01@gmail.com> *
* *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/

package com.ichi2.anki.jsaddons

import android.app.Dialog
import android.os.Bundle
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.os.BundleCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import com.ichi2.anki.R
import com.ichi2.utils.cancelable
import com.ichi2.utils.create
import com.ichi2.utils.customView
import com.ichi2.utils.positiveButton

/**
* Shows all available details for the addon identifier by the [AddonModel] passed as an argument.
*/
class AddonDetailsDialog : DialogFragment() {

override fun onStart() {
super.onStart()
dialog?.window?.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val addonModel =
BundleCompat.getParcelable(requireArguments(), KEY_ADDON_MODEL, AddonModel::class.java)
?: error("No addon identifier was provided!")
val contentView =
requireActivity().layoutInflater.inflate(R.layout.dialog_addon_details, null).apply {
findViewById<TextView>(R.id.addon_name).text = addonModel.name
findViewById<TextView>(R.id.addon_description).text = addonModel.description
findViewById<TextView>(R.id.addon_type).text = addonModel.addonType
findViewById<TextView>(R.id.addon_author).text = addonModel.author["name"]
findViewById<TextView>(R.id.addon_version).text = addonModel.version
findViewById<TextView>(R.id.addon_js_api_version).text = addonModel.ankidroidJsApi
findViewById<TextView>(R.id.addon_license).text = addonModel.license
findViewById<TextView>(R.id.addon_homepage).text = addonModel.homepage
}
return AlertDialog.Builder(requireContext()).create {
customView(contentView)
cancelable(true)
positiveButton(R.string.close)
}
}

companion object {
private const val KEY_ADDON_MODEL = "key_addon_model"

fun newInstance(addonModel: AddonModel): AddonDetailsDialog = AddonDetailsDialog().apply {
arguments = bundleOf(KEY_ADDON_MODEL to addonModel)
}
}
}
7 changes: 5 additions & 2 deletions AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
package com.ichi2.anki.jsaddons

import android.content.SharedPreferences
import android.os.Parcelable
import androidx.core.content.edit
import kotlinx.parcelize.Parcelize

@Parcelize
data class AddonModel(
val name: String,
val addonTitle: String,
Expand All @@ -32,8 +35,8 @@ data class AddonModel(
val author: Map<String, String>,
val license: String,
val homepage: String,
val dist: DistInfo
) {
val dist: Map<String, String>
) : Parcelable {
/**
* Update preferences for addons with boolean remove, the preferences will be used to store the information about
* enabled and disabled addon. So, that other method will return content of script to reviewer or note editor
Expand Down
76 changes: 76 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonStorage.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/****************************************************************************************
* Copyright (c) 2022 Mani <infinyte01@gmail.com> *
* *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************************/

package com.ichi2.anki.jsaddons

import android.content.Context
import com.ichi2.anki.*
import com.ichi2.annotations.NeedsTest
import java.io.File

/**
* Implemented functions for getting addons related directory for current profile
*/
@NeedsTest("Addons directory test")
class AddonStorage(val context: Context) {
private val currentAnkiDroidDirectory = CollectionHelper.getCurrentAnkiDroidDirectory(context)
private val addonsHomeDir = File(currentAnkiDroidDirectory, "addons")

/**
* Get addons directory for current profile
* e.g. AnkiDroid/addons/
*/
fun getCurrentProfileAddonDir(): File {
if (!addonsHomeDir.exists()) {
addonsHomeDir.mkdirs()
}
return addonsHomeDir
}

/**
* Get addon's directory which contains packages and index.js files
* e.g. AnkiDroid/addons/some-addon/
*
* @param addonName
* @return some-addon dir e.g. AnkiDroid/addons/some-addon/
*/
fun getSelectedAddonDir(addonName: String): File {
return File(addonsHomeDir, addonName)
}

/**
* Get package.json for selected addons
* e.g. AnkiDroid/addons/some-addon/package/package.json
*
* @param addonName
* @return package.json file
*/
fun getSelectedAddonPackageJson(addonName: String): File {
val addonPath = getSelectedAddonDir(addonName)
return File(addonPath, "package/package.json")
}

/**
* Remove selected addon in list view from addons directory
*
* @param addonName
*/
fun deleteSelectedAddonPackageDir(addonName: String): Boolean {
val dir = getSelectedAddonDir(addonName)
return BackupManager.removeDir(dir)
}
}
Loading

0 comments on commit b82e7e4

Please sign in to comment.