0
0
mirror of https://github.com/ankidroid/Anki-Android.git synced 2024-09-19 19:42:17 +02:00

feat: deckpicker drag & drop

This commit is contained in:
SanjaySargam 2024-08-16 14:06:32 +05:30 committed by Arthur Milchior
parent 6630ff411d
commit 92f4d1c091
7 changed files with 75 additions and 19 deletions

View File

@ -68,8 +68,12 @@ import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.os.bundleOf
import androidx.core.text.parseAsHtml
import androidx.core.util.component1
import androidx.core.util.component2
import androidx.core.view.MenuItemCompat
import androidx.core.view.OnReceiveContentListener
import androidx.core.view.isVisible
import androidx.draganddrop.DropHelper
import androidx.fragment.app.commit
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
@ -163,6 +167,7 @@ import com.ichi2.anki.worker.UniqueWorkNames
import com.ichi2.annotations.NeedsTest
import com.ichi2.async.deleteMedia
import com.ichi2.async.sendNotificationForAsyncOperation
import com.ichi2.compat.CompatHelper
import com.ichi2.compat.CompatHelper.Companion.getSerializableCompat
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.compat.CompatHelper.Companion.sdkVersion
@ -177,8 +182,11 @@ import com.ichi2.libanki.undoableOp
import com.ichi2.libanki.utils.TimeManager
import com.ichi2.ui.BadgeDrawableBuilder
import com.ichi2.utils.AdaptionUtil
import com.ichi2.utils.ClipboardUtil.IMPORT_MIME_TYPES
import com.ichi2.utils.FragmentFactoryUtils
import com.ichi2.utils.HandlerUtils
import com.ichi2.utils.ImportUtils
import com.ichi2.utils.ImportUtils.ImportResult
import com.ichi2.utils.KotlinCleanup
import com.ichi2.utils.NetworkUtils
import com.ichi2.utils.NetworkUtils.isActiveNetworkMetered
@ -600,6 +608,40 @@ open class DeckPicker :
else -> error("Unexpected fragment result key! Did you forget to update DeckPicker?")
}
}
CompatHelper.compat.configureView(
this,
pullToSyncWrapper,
IMPORT_MIME_TYPES,
DropHelper.Options.Builder()
.setHighlightColor(R.color.material_lime_green_A700)
.setHighlightCornerRadiusPx(0)
.build(),
onReceiveContentListener
)
}
private val onReceiveContentListener = OnReceiveContentListener { _, payload ->
val (uriContent, remaining) = payload.partition { item -> item.uri != null }
val clip = uriContent?.clip ?: return@OnReceiveContentListener remaining
val uri = clip.getItemAt(0).uri
if (!ImportUtils.FileImporter().isValidImportType(this, uri)) {
ImportResult.fromErrorString(getString(R.string.import_log_no_apkg))
return@OnReceiveContentListener remaining
}
try {
// Intent is nullable because `clip.getItemAt(0).intent` always returns null
ImportUtils.FileImporter().handleContentProviderFile(this, uri)
onResume()
} catch (e: Exception) {
Timber.w(e)
CrashReportService.sendExceptionReport(e, "DeckPicker::onReceiveContent")
return@OnReceiveContentListener remaining
}
return@OnReceiveContentListener remaining
}
private fun handleContextMenuSelection(

View File

@ -150,6 +150,7 @@ import com.ichi2.libanki.Notetypes.Companion.NOT_FOUND_NOTE_TYPE
import com.ichi2.libanki.Utils
import com.ichi2.libanki.undoableOp
import com.ichi2.utils.ClipboardUtil
import com.ichi2.utils.ClipboardUtil.MEDIA_MIME_TYPES
import com.ichi2.utils.ClipboardUtil.hasMedia
import com.ichi2.utils.ClipboardUtil.items
import com.ichi2.utils.HashUtil
@ -1647,6 +1648,7 @@ class NoteEditor : AnkiFragment(R.layout.note_editor), DeckSelectionListener, Su
CompatHelper.compat.configureView(
requireActivity(),
editLineView,
MEDIA_MIME_TYPES,
DropHelper.Options.Builder()
.setHighlightColor(R.color.material_lime_green_A700)
.setHighlightCornerRadiusPx(0)

View File

@ -213,6 +213,7 @@ interface Compat {
fun configureView(
activity: Activity,
view: View,
mimeTypes: Array<String>,
options: DropHelper.Options,
onReceiveContentListener: OnReceiveContentListener
)

View File

@ -154,6 +154,7 @@ open class CompatV23 : Compat {
override fun configureView(
activity: Activity,
view: View,
mimeTypes: Array<String>,
options: DropHelper.Options,
onReceiveContentListener: OnReceiveContentListener
) {

View File

@ -24,7 +24,6 @@ import android.view.View
import androidx.core.view.OnReceiveContentListener
import androidx.draganddrop.DropHelper
import com.ichi2.anki.common.utils.android.isRobolectric
import com.ichi2.utils.ClipboardUtil.MEDIA_MIME_TYPES
import timber.log.Timber
import java.util.Locale
@ -48,13 +47,14 @@ open class CompatV24 : CompatV23(), Compat {
override fun configureView(
activity: Activity,
view: View,
mimeTypes: Array<String>,
options: DropHelper.Options,
onReceiveContentListener: OnReceiveContentListener
) {
DropHelper.configureView(
activity,
view,
MEDIA_MIME_TYPES,
mimeTypes,
options,
onReceiveContentListener
)

View File

@ -34,6 +34,7 @@ object ClipboardUtil {
val IMAGE_MIME_TYPES = arrayOf("image/*")
val AUDIO_MIME_TYPES = arrayOf("audio/*")
val VIDEO_MIME_TYPES = arrayOf("video/*")
val IMPORT_MIME_TYPES = arrayOf("application/*", "text/*")
val MEDIA_MIME_TYPES = arrayOf(*IMAGE_MIME_TYPES, *AUDIO_MIME_TYPES, *VIDEO_MIME_TYPES)
fun hasImage(clipboard: ClipboardManager?): Boolean {

View File

@ -125,7 +125,7 @@ object ImportUtils {
private fun handleFileImportInternal(context: Context, intent: Intent): ImportResult {
val importPathUri = getDataUri(intent)
return if (importPathUri != null) {
handleContentProviderFile(context, intent, importPathUri)
handleContentProviderFile(context, importPathUri, intent)
} else {
ImportResult.fromErrorString(context.getString(R.string.import_error_handle_exception))
}
@ -150,10 +150,10 @@ object ImportUtils {
return getFileCachedCopy(context, uri)
}
private fun handleContentProviderFile(
fun handleContentProviderFile(
context: Context,
intent: Intent,
importPathUri: Uri
importPathUri: Uri,
intent: Intent? = null
): ImportResult {
// Note: intent.getData() can be null. Use data instead.
if (!isValidImportType(context, importPathUri)) {
@ -163,18 +163,27 @@ object ImportUtils {
var filename = getFileNameFromContentProvider(context, importPathUri)
// Hack to fix bug where ContentResolver not returning filename correctly
if (filename == null) {
if (intent.type == "application/apkg" || intent.type == "application/zip") {
// Set a dummy filename if MIME type provided or is a valid zip file
filename = "unknown_filename.apkg"
Timber.w("Could not retrieve filename from ContentProvider, but was valid zip file so we try to continue")
} else {
Timber.e("Could not retrieve filename from ContentProvider")
CrashReportService.sendExceptionReport(
RuntimeException("Could not import apkg from ContentProvider"),
"IntentHandler.java",
"apkg import failed; mime type ${intent.type}"
)
return ImportResult.fromErrorString(AnkiDroidApp.appResources.getString(R.string.import_error_content_provider, AnkiDroidApp.manualUrl + "#importing"))
when (intent?.type) {
"application/apkg", "application/zip" -> {
// Set a dummy filename if MIME type provided or is a valid zip file
filename = "unknown_filename.apkg"
Timber.w("Could not retrieve filename from ContentProvider, but was valid zip file so we try to continue")
}
else -> {
Timber.e("Could not retrieve filename from ContentProvider")
CrashReportService.sendExceptionReport(
RuntimeException("Could not import apkg from ContentProvider"),
"IntentHandler.java",
"apkg import failed; mime type ${intent?.type}"
)
return ImportResult.fromErrorString(
AnkiDroidApp.appResources.getString(
R.string.import_error_content_provider,
AnkiDroidApp.manualUrl + "#importing"
)
)
}
}
}
val tempOutDir: String
@ -210,7 +219,7 @@ object ImportUtils {
return ImportResult.fromSuccess()
}
private fun isValidImportType(context: Context, importPathUri: Uri): Boolean {
fun isValidImportType(context: Context, importPathUri: Uri): Boolean {
val fileName = getFileNameFromContentProvider(context, importPathUri)
return when {
isDeckPackage(fileName) -> true