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:
parent
6630ff411d
commit
92f4d1c091
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -213,6 +213,7 @@ interface Compat {
|
||||
fun configureView(
|
||||
activity: Activity,
|
||||
view: View,
|
||||
mimeTypes: Array<String>,
|
||||
options: DropHelper.Options,
|
||||
onReceiveContentListener: OnReceiveContentListener
|
||||
)
|
||||
|
@ -154,6 +154,7 @@ open class CompatV23 : Compat {
|
||||
override fun configureView(
|
||||
activity: Activity,
|
||||
view: View,
|
||||
mimeTypes: Array<String>,
|
||||
options: DropHelper.Options,
|
||||
onReceiveContentListener: OnReceiveContentListener
|
||||
) {
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user