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 17, 2024
1 parent 1969b28 commit b4aebcc
Show file tree
Hide file tree
Showing 19 changed files with 959 additions and 10 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
2 changes: 2 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ fun getAddonModelFromAddonData(addonData: AddonData): Pair<AddonModel?, List<Str
author = addonData.author!!,
license = addonData.license!!,
homepage = addonData.homepage!!,
// 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!!
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/****************************************************************************************
* Copyright (c) 2022 Mani <[email protected]> *
* *
* *
* 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 identified 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)
}
}
}
18 changes: 17 additions & 1 deletion 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,15 @@
package com.ichi2.anki.jsaddons

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

@Parcelize
@TypeParceler<DistInfo, DistParceler>
data class AddonModel(
val name: String,
val addonTitle: String,
Expand All @@ -33,7 +40,7 @@ data class AddonModel(
val license: String,
val homepage: String,
val dist: DistInfo
) {
) : 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 All @@ -60,3 +67,12 @@ data class AddonModel(
preferences.edit { putStringSet(jsAddonKey, newStrSet) }
}
}

class DistParceler : Parceler<DistInfo> {

override fun DistInfo.write(parcel: Parcel, flags: Int) {
parcel.writeString(tarball)
}

override fun create(parcel: Parcel): DistInfo = DistInfo(parcel.readString() ?: "")
}
77 changes: 77 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,77 @@
/****************************************************************************************
* Copyright (c) 2022 Mani <[email protected]> *
* *
* *
* 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.BackupManager
import com.ichi2.anki.CollectionHelper
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 b4aebcc

Please sign in to comment.