Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import androidx.core.content.edit
import androidx.core.content.res.ResourcesCompat
import androidx.core.text.HtmlCompat
import anki.config.ConfigKey
import anki.notetypes.StockNotetype
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anim.ActivityTransitionAnimation
Expand All @@ -74,6 +75,7 @@ import com.ichi2.anki.noteeditor.FieldState.FieldChangeType
import com.ichi2.anki.noteeditor.Toolbar
import com.ichi2.anki.noteeditor.Toolbar.TextFormatListener
import com.ichi2.anki.noteeditor.Toolbar.TextWrapper
import com.ichi2.anki.pages.ImageOcclusion
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.receiver.SdCardReceiver
import com.ichi2.anki.servicelayer.LanguageHintService
Expand All @@ -95,6 +97,7 @@ import com.ichi2.libanki.exception.ConfirmModSchemaException
import com.ichi2.utils.*
import com.ichi2.widget.WidgetStatus
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
import java.util.*
Expand Down Expand Up @@ -240,6 +243,17 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
}
)

private val requestIOEditorLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

NoteEditorActivityResultCallback { result ->
if (result.resultCode != RESULT_CANCELED) {
ImportUtils.getFileCachedCopy(this@NoteEditor, result.data!!)?.let { path ->
setupImageOcclusionEditor(path)
}
}
}
)

private inner class NoteEditorActivityResultCallback(private val callback: (result: ActivityResult) -> Unit) : ActivityResultCallback<ActivityResult> {
override fun onActivityResult(result: ActivityResult) {
Timber.d("onActivityResult() with result: %s", result.resultCode)
Expand Down Expand Up @@ -447,6 +461,25 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
else -> {}
}

col.backend.addImageOcclusionNotetype()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I assume it's fine not to call this in a background task. The computer version doesn't bother using background task for this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Technically, we probably should be using a background task for the desktop as well. If the user has a large number of notetypes + slow disk, the operation to fetch all the notetypes could potentially block for some time.

For methods that access the collection, standard practice is to write a little wrapper function in the Collection class and call that inside withCol, instead of using the backend directly. We only access the backend directly for operations that don't require/want the collection lock (such as checking progress). One problem this avoids is the collection is guaranteed not to be closed in the middle of an operation running inside withCol.


val imageOcclusionButton: Button = findViewById(R.id.ImageOcclusionButton)
if (addNote) {
imageOcclusionButton.setText(R.string.select_image)
imageOcclusionButton.setOnClickListener {
val i = Intent()
i.type = "image/*"
i.action = Intent.ACTION_GET_CONTENT
i.addCategory(Intent.CATEGORY_OPENABLE)
launchActivityForResultWithAnimation(Intent.createChooser(i, resources.getString(R.string.select_image)), requestIOEditorLauncher, START)
}
} else {
imageOcclusionButton.setText(R.string.edit_occlusions)
imageOcclusionButton.setOnClickListener {
setupImageOcclusionEditor()
}
}

// Note type Selector
mNoteTypeSpinner = findViewById(R.id.note_type_spinner)
mAllModelIds = setupNoteTypeSpinner(this, mNoteTypeSpinner!!, col)
Expand Down Expand Up @@ -1252,6 +1285,15 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
val editLines = mFieldState.loadFieldEditLines(type)
mFieldsLayoutContainer!!.removeAllViews()
mCustomViewIds.clear()
if (currentNotetypeIsImageOcclusion()) {
setImageOcclusionButton()
return
} else {
val imageOcclusionButton: Button = findViewById(R.id.ImageOcclusionButton)
imageOcclusionButton.visibility = View.GONE
mFieldsLayoutContainer?.visibility = View.VISIBLE
}

mEditFields = LinkedList()

var previous: FieldEditLine? = null
Expand Down Expand Up @@ -1926,6 +1968,35 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
val fieldsFromSelectedNote: Array<Array<String>>
get() = mEditorNote!!.items().map { it.requireNoNulls() }.toTypedArray()

private fun currentNotetypeIsImageOcclusion(): Boolean {
println("currentNotetypeIsImageOcclusion: ${currentlySelectedNotetype?.fieldsNames}")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Prefer Timber.d

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(if this is something you feel would be useful moving forward, and not temporary debug info that should be removed)

try {
return currentlySelectedNotetype?.getInt("originalStockKind") == StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION_VALUE
} catch (j: JSONException) {
return false
}
}

private fun setImageOcclusionButton() {
val imageOcclusionButton: Button = findViewById(R.id.ImageOcclusionButton)
imageOcclusionButton.visibility = View.VISIBLE
mFieldsLayoutContainer?.visibility = View.GONE
}

private fun setupImageOcclusionEditor(imagePath: String = "") {
val kind: String
val id: Long
if (addNote) {
kind = "add"
id = 0
} else {
kind = "edit"
id = mEditorNote?.id!!
}
val intent = ImageOcclusion.getIntent(this@NoteEditor, kind, id, imagePath)
startActivity(intent)
}

// ----------------------------------------------------------------------------
// INNER CLASSES
// ----------------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/pages/AnkiServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ open class AnkiServer(
"setWantsAbort" -> CollectionManager.getBackend().setWantsAbortRaw(bytes)
"evaluateWeights" -> withCol { evaluateWeightsRaw(bytes) }
"latestProgress" -> CollectionManager.getBackend().latestProgressRaw(bytes)
"getImageForOcclusion" -> CollectionManager.getBackend().getImageForOcclusionRaw(bytes)
"getImageOcclusionNote" -> CollectionManager.getBackend().getImageOcclusionNoteRaw(bytes)
"getImageForOcclusionFields" -> CollectionManager.getBackend().getImageOcclusionFieldsRaw(bytes)
"addImageOcclusionNote" -> CollectionManager.getBackend().addImageOcclusionNoteRaw(bytes)
"updateImageOcclusionNote" -> CollectionManager.getBackend().updateImageOcclusionNoteRaw(bytes)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These would be better implemented as wrappers inside a withCol block.

else -> { throw Exception("unhandled request: $methodName") }
}
}
Expand Down
70 changes: 70 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2023 Abdo <abdo@abdnh.net>
*
* 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.pages

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.webkit.WebView
import com.ichi2.anki.R
import org.json.JSONObject

class ImageOcclusion : PageFragment() {

override val title = R.string.image_occlusion
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can use TR.notetypesImageOcclusionName() to avoid having to add a duplicate string.

override val pageName = "image-occlusion"
override lateinit var webViewClient: PageWebViewClient
override var webChromeClient = PageChromeClient()

override fun onCreate(savedInstanceState: Bundle?) {
val kind = arguments?.getString(ARG_KEY_KIND) ?: throw Exception("missing kind")
val id = arguments?.getLong(ARG_KEY_ID) ?: throw Exception("missing ID")
val path = arguments?.getString(ARG_KEY_PATH) ?: if (kind == "add") throw Exception("missing path") else ""
webViewClient = ImageOcclusionWebViewClient(kind, id, path)
super.onCreate(savedInstanceState)
}

class ImageOcclusionWebViewClient(val kind: String, private val noteOrNotetypeId: Long, private val path: String?) : PageWebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
val options = JSONObject()
options.put("kind", kind)
options.put("imagePath", path)
if (kind == "add") {
options.put("notetypeId", noteOrNotetypeId)
} else {
options.put("noteId", noteOrNotetypeId)
}
view!!.evaluateJavascript("anki.setupImageOcclusion($options);") {
super.onPageFinished(view, url)
}
}
}

companion object {
private const val ARG_KEY_KIND = "kind"
private const val ARG_KEY_ID = "id"
private const val ARG_KEY_PATH = "path"

fun getIntent(context: Context, kind: String, noteOrNotetypeId: Long, imagePath: String?): Intent {
val arguments = Bundle().apply {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

bundleOf exists in Kotlin

putString(ARG_KEY_KIND, kind)
putLong(ARG_KEY_ID, noteOrNotetypeId)
putString(ARG_KEY_PATH, imagePath)
}
return PagesActivity.getIntent(context, ImageOcclusion::class, arguments)
}
}
}
9 changes: 8 additions & 1 deletion AnkiDroid/src/main/res/layout/note_editor.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,14 @@
android:animateLayoutChanges="true"
android:paddingVertical="@dimen/keyline_1"
android:paddingHorizontal="6dip" />
<androidx.appcompat.widget.AppCompatButton

<Button
android:id="@+id/ImageOcclusionButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Select Image" />

<androidx.appcompat.widget.AppCompatButton
android:id="@+id/CardEditorTagButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
Expand Down
4 changes: 4 additions & 0 deletions AnkiDroid/src/main/res/values/02-strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
<string name="CardEditorModel">Type:</string>
<string name="CardEditorTags">Tags: %1$s</string>
<string name="CardEditorCards">Cards: %1$s</string>
<string name="select_image">Select Image</string>
<string name="edit_occlusions">Edit Occlusions</string>
<string name="image_occlusion">Image Occlusion</string>

<string name="tag_name">Tag name</string>
<string name="add_new_filter_tags">Add/filter tags</string>
<string name="add_tag">Add tag</string>
Expand Down