diff --git a/AnkiDroid/src/main/AndroidManifest.xml b/AnkiDroid/src/main/AndroidManifest.xml index c051cf5a9d90..10ab72e2a15a 100644 --- a/AnkiDroid/src/main/AndroidManifest.xml +++ b/AnkiDroid/src/main/AndroidManifest.xml @@ -463,6 +463,10 @@ android:theme="@style/Theme.AppCompat.NoActionBar" android:configChanges="keyboardHidden|screenSize" /> + + { + 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() diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonData.kt b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonData.kt index f65111d33f24..e262cb0cf39f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonData.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonData.kt @@ -151,6 +151,8 @@ fun getAddonModelFromAddonData(addonData: AddonData): Pair * + * * + * * + * 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 . * + ****************************************************************************************/ + +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(R.id.addon_name).text = addonModel.name + findViewById(R.id.addon_description).text = addonModel.description + findViewById(R.id.addon_type).text = addonModel.addonType + findViewById(R.id.addon_author).text = addonModel.author["name"] + findViewById(R.id.addon_version).text = addonModel.version + findViewById(R.id.addon_js_api_version).text = addonModel.ankidroidJsApi + findViewById(R.id.addon_license).text = addonModel.license + findViewById(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) + } + } +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonModel.kt index 7f6b641a7431..e3360ce595a3 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonModel.kt @@ -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 data class AddonModel( val name: String, val addonTitle: String, @@ -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 @@ -60,3 +67,12 @@ data class AddonModel( preferences.edit { putStringSet(jsAddonKey, newStrSet) } } } + +class DistParceler : Parceler { + + override fun DistInfo.write(parcel: Parcel, flags: Int) { + parcel.writeString(tarball) + } + + override fun create(parcel: Parcel): DistInfo = DistInfo(parcel.readString() ?: "") +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonStorage.kt b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonStorage.kt new file mode 100644 index 000000000000..4d24e1e4eb97 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonStorage.kt @@ -0,0 +1,77 @@ +/**************************************************************************************** + * Copyright (c) 2022 Mani * + * * + * * + * 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 . * + ***************************************************************************************/ + +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) + } +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonsBrowserActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonsBrowserActivity.kt new file mode 100644 index 000000000000..3e900c990d70 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/AddonsBrowserActivity.kt @@ -0,0 +1,214 @@ +/**************************************************************************************** + * Copyright (c) 2022 Mani * + * * + * 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 . * + ***************************************************************************************/ + +package com.ichi2.anki.jsaddons + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.Button +import android.widget.LinearLayout +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.ichi2.anki.AnkiActivity +import com.ichi2.anki.CollectionManager.TR +import com.ichi2.anki.R +import com.ichi2.anki.dialogs.ConfirmationDialog +import com.ichi2.anki.preferences.sharedPrefs +import com.ichi2.anki.showThemedToast +import timber.log.Timber +import java.io.File +import java.io.IOException + +/** + * Shows the addons available in the app along with actions to fetch and remove other addons. + */ +// TODO ===== EXTRACT RELATED STRINGS FOR TRANSLATION BEFORE RELEASE ===== +class AddonsBrowserActivity : AnkiActivity() { + private val addonsAdapter by lazy { + AddonsBrowserAdapter( + context = this@AddonsBrowserActivity, + onToggleAddon = ::handleAddonModelToggled, + onDetailsRequested = { addonModel -> + showDialogFragment(this, AddonDetailsDialog.newInstance(addonModel)) + }, + onDeleteAddon = ::handleAddonDeletion + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + if (showedActivityFailedScreen(savedInstanceState)) { + return + } + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_addons_browser) + + enableToolbar().apply { + // Add a home button to the actionbar + setHomeButtonEnabled(true) + setDisplayHomeAsUpEnabled(true) + title = TR.addonsWindowTitle() + } + + findViewById(R.id.addons_list).apply { + adapter = addonsAdapter + } + findViewById