@@ -20,6 +20,8 @@ package com.ichi2.anki
2020
2121import android.annotation.SuppressLint
2222import android.content.BroadcastReceiver
23+ import android.content.ClipData
24+ import android.content.ClipboardManager
2325import android.content.Context
2426import android.content.Intent
2527import android.content.IntentFilter
@@ -49,11 +51,13 @@ import androidx.core.content.edit
4951import androidx.core.content.res.ResourcesCompat
5052import androidx.core.text.HtmlCompat
5153import anki.config.ConfigKey
54+ import anki.notetypes.StockNotetype
5255import com.google.android.material.color.MaterialColors
5356import com.google.android.material.snackbar.Snackbar
5457import com.ichi2.anim.ActivityTransitionAnimation
5558import com.ichi2.anim.ActivityTransitionAnimation.Direction.*
5659import com.ichi2.anki.CollectionManager.TR
60+ import com.ichi2.anki.CollectionManager.withCol
5761import com.ichi2.anki.dialogs.ConfirmationDialog
5862import com.ichi2.anki.dialogs.DeckSelectionDialog.DeckSelectionListener
5963import com.ichi2.anki.dialogs.DeckSelectionDialog.SelectableDeck
@@ -74,6 +78,7 @@ import com.ichi2.anki.noteeditor.FieldState.FieldChangeType
7478import com.ichi2.anki.noteeditor.Toolbar
7579import com.ichi2.anki.noteeditor.Toolbar.TextFormatListener
7680import com.ichi2.anki.noteeditor.Toolbar.TextWrapper
81+ import com.ichi2.anki.pages.ImageOcclusion
7782import com.ichi2.anki.preferences.sharedPrefs
7883import com.ichi2.anki.receiver.SdCardReceiver
7984import com.ichi2.anki.servicelayer.LanguageHintService
@@ -94,6 +99,7 @@ import com.ichi2.libanki.Notetypes.Companion.NOT_FOUND_NOTE_TYPE
9499import com.ichi2.utils.*
95100import com.ichi2.widget.WidgetStatus
96101import org.json.JSONArray
102+ import org.json.JSONException
97103import org.json.JSONObject
98104import timber.log.Timber
99105import java.util.*
@@ -133,6 +139,10 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
133139 private var mCardsButton: AppCompatButton ? = null
134140 private var mNoteTypeSpinner: Spinner ? = null
135141 private var mDeckSpinnerSelection: DeckSpinnerSelection ? = null
142+ private var imageOcclusionButtonsContainer: LinearLayout ? = null
143+ private var selectImageForOcclusionButton: Button ? = null
144+ private var editOcclusionsButton: Button ? = null
145+ private var pasteOcclusionImageButton: Button ? = null
136146
137147 // non-null after onCollectionLoaded
138148 private var mEditorNote: Note ? = null
@@ -169,6 +179,8 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
169179 private var mToggleStickyText: HashMap <Int , String ?> = HashMap ()
170180 private val mOnboarding = Onboarding .NoteEditor (this )
171181
182+ var clipboard: ClipboardManager ? = null
183+
172184 private val requestAddLauncher = registerForActivityResult(
173185 ActivityResultContracts .StartActivityForResult (),
174186 NoteEditorActivityResultCallback {
@@ -239,6 +251,29 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
239251 }
240252 )
241253
254+ private val ioEditorLauncher = registerForActivityResult(
255+ ActivityResultContracts .GetContent ()
256+ ) { uri ->
257+ if (uri != null ) {
258+ ImportUtils .getFileCachedCopy(this @NoteEditor, uri)?.let { path ->
259+ setupImageOcclusionEditor(path)
260+ }
261+ }
262+ }
263+
264+ private val requestIOEditorCloser = registerForActivityResult(
265+ ActivityResultContracts .StartActivityForResult (),
266+ NoteEditorActivityResultCallback { result ->
267+ if (result.resultCode != RESULT_CANCELED ) {
268+ changed = true
269+ if (! addNote) {
270+ mReloadRequired = true
271+ closeNoteEditor(RESULT_UPDATED_IO_NOTE , null )
272+ }
273+ }
274+ }
275+ )
276+
242277 private inner class NoteEditorActivityResultCallback (private val callback : (result: ActivityResult ) -> Unit ) : ActivityResultCallback<ActivityResult> {
243278 override fun onActivityResult (result : ActivityResult ) {
244279 Timber .d(" onActivityResult() with result: %s" , result.resultCode)
@@ -402,6 +437,17 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
402437 Timber .i(" NoteEditor:: Cards button pressed. Opening template editor" )
403438 showCardTemplateEditor()
404439 }
440+ imageOcclusionButtonsContainer = findViewById(R .id.ImageOcclusionButtonsLayout )
441+ editOcclusionsButton = findViewById(R .id.EditOcclusionsButton )
442+ selectImageForOcclusionButton = findViewById(R .id.SelectImageForOcclusionButton )
443+ pasteOcclusionImageButton = findViewById(R .id.PasteImageForOcclusionButton )
444+
445+ try {
446+ clipboard = getSystemService(Context .CLIPBOARD_SERVICE ) as ClipboardManager
447+ } catch (e: Exception ) {
448+ Timber .w(e)
449+ }
450+
405451 aedictIntent = false
406452 mCurrentEditedCard = null
407453 when (caller) {
@@ -446,6 +492,44 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
446492 else -> {}
447493 }
448494
495+ launchCatchingTask {
496+ withCol {
497+ addImageOcclusionNotetype()
498+ }
499+ }
500+
501+ if (addNote) {
502+ editOcclusionsButton?.visibility = View .GONE
503+ selectImageForOcclusionButton?.setOnClickListener {
504+ ioEditorLauncher.launch(" image/*" )
505+ }
506+ pasteOcclusionImageButton?.text = TR .notetypesIoPasteImageFromClipboard()
507+ pasteOcclusionImageButton?.setOnClickListener {
508+ // TODO: Support all extensions
509+ // See https://github.com/ankitects/anki/blob/6f3550464d37aee1b8b784e431cbfce8382d3ce7/rslib/src/image_occlusion/imagedata.rs#L154
510+ if (ClipboardUtil .hasImage(clipboard)) {
511+ val uri = ClipboardUtil .getImageUri(clipboard)
512+ val i = Intent ().apply {
513+ addFlags(Intent .FLAG_GRANT_READ_URI_PERMISSION )
514+ clipData = ClipData .newUri(contentResolver, uri.toString(), uri)
515+ }
516+ ImportUtils .getFileCachedCopy(this , i)?.let { path ->
517+ setupImageOcclusionEditor(path)
518+ }
519+ } else {
520+ showSnackbar(TR .editingNoImageFoundOnClipboard())
521+ }
522+ }
523+ } else {
524+ selectImageForOcclusionButton?.visibility = View .GONE
525+ pasteOcclusionImageButton?.visibility = View .GONE
526+ editOcclusionsButton?.visibility = View .VISIBLE
527+ editOcclusionsButton?.text = resources.getString(R .string.edit_occlusions)
528+ editOcclusionsButton?.setOnClickListener {
529+ setupImageOcclusionEditor()
530+ }
531+ }
532+
449533 // Note type Selector
450534 mNoteTypeSpinner = findViewById(R .id.note_type_spinner)
451535 mAllModelIds = setupNoteTypeSpinner(this , mNoteTypeSpinner!! , col)
@@ -845,19 +929,21 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
845929 mCurrentEditedCard!! .did = deckId
846930 modified = true
847931 }
848- // now load any changes to the fields from the form
849- for (f in mEditFields!! ) {
850- modified = modified or updateField(f)
851- }
852- // added tag?
853- for (t in mSelectedTags!! ) {
854- modified = modified || ! mEditorNote!! .hasTag(t)
855- }
856- // removed tag?
857- modified = modified || mEditorNote!! .tags.size > mSelectedTags!! .size
858- if (modified) {
859- mEditorNote!! .setTagsFromStr(tagsAsString(mSelectedTags!! ))
860- changed = true
932+ if (! currentNotetypeIsImageOcclusion()) {
933+ // now load any changes to the fields from the form
934+ for (f in mEditFields!! ) {
935+ modified = modified or updateField(f)
936+ }
937+ // added tag?
938+ for (t in mSelectedTags!! ) {
939+ modified = modified || ! mEditorNote!! .hasTag(t)
940+ }
941+ // removed tag?
942+ modified = modified || mEditorNote!! .tags.size > mSelectedTags!! .size
943+ if (modified) {
944+ mEditorNote!! .setTagsFromStr(tagsAsString(mSelectedTags!! ))
945+ changed = true
946+ }
861947 }
862948 closeNoteEditor()
863949 }
@@ -1224,6 +1310,14 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
12241310 val editLines = mFieldState.loadFieldEditLines(type)
12251311 mFieldsLayoutContainer!! .removeAllViews()
12261312 mCustomViewIds.clear()
1313+ if (currentNotetypeIsImageOcclusion()) {
1314+ setImageOcclusionButton()
1315+ return
1316+ } else {
1317+ imageOcclusionButtonsContainer?.visibility = View .GONE
1318+ mFieldsLayoutContainer?.visibility = View .VISIBLE
1319+ }
1320+
12271321 mEditFields = LinkedList ()
12281322
12291323 var previous: FieldEditLine ? = null
@@ -1897,6 +1991,33 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
18971991 val fieldsFromSelectedNote: Array <Array <String >>
18981992 get() = mEditorNote!! .items()
18991993
1994+ private fun currentNotetypeIsImageOcclusion (): Boolean {
1995+ try {
1996+ return currentlySelectedNotetype?.getInt(" originalStockKind" ) == StockNotetype .OriginalStockKind .ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION_VALUE
1997+ } catch (j: JSONException ) {
1998+ return false
1999+ }
2000+ }
2001+
2002+ private fun setImageOcclusionButton () {
2003+ imageOcclusionButtonsContainer?.visibility = View .VISIBLE
2004+ mFieldsLayoutContainer?.visibility = View .GONE
2005+ }
2006+
2007+ private fun setupImageOcclusionEditor (imagePath : String = "") {
2008+ val kind: String
2009+ val id: Long
2010+ if (addNote) {
2011+ kind = " add"
2012+ id = 0
2013+ } else {
2014+ kind = " edit"
2015+ id = mEditorNote?.id!!
2016+ }
2017+ val intent = ImageOcclusion .getIntent(this @NoteEditor, kind, id, imagePath)
2018+ requestIOEditorCloser.launch(intent)
2019+ }
2020+
19002021 // ----------------------------------------------------------------------------
19012022 // INNER CLASSES
19022023 // ----------------------------------------------------------------------------
@@ -2163,6 +2284,8 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags
21632284 const val CALLER_NOTEEDITOR = 8
21642285 const val CALLER_NOTEEDITOR_INTENT_ADD = 10
21652286
2287+ const val RESULT_UPDATED_IO_NOTE = 11
2288+
21662289 // preferences keys
21672290 const val PREF_NOTE_EDITOR_SCROLL_TOOLBAR = " noteEditorScrollToolbar"
21682291 private const val PREF_NOTE_EDITOR_SHOW_TOOLBAR = " noteEditorShowToolbar"
0 commit comments