mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-20 03:52:15 +02:00
Fix 0 bytes file output when exporting collection
To fix the bug this commit introduces a split between saving and sharing the collection exported file.
This commit is contained in:
parent
eb49979e9b
commit
8e13995d7a
@ -16,12 +16,12 @@
|
||||
|
||||
package com.ichi2.anki.dialogs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.ichi2.anki.R
|
||||
import com.ichi2.themes.Themes
|
||||
import java.io.File
|
||||
|
||||
class ExportCompleteDialog(private val listener: ExportCompleteDialogListener) : AsyncDialogFragment() {
|
||||
interface ExportCompleteDialogListener {
|
||||
@ -29,7 +29,7 @@ class ExportCompleteDialog(private val listener: ExportCompleteDialogListener) :
|
||||
fun shareFile(path: String) // path of the file to be shared
|
||||
fun saveExportFile(exportPath: String)
|
||||
}
|
||||
val exportPath
|
||||
private val exportPath
|
||||
get() = requireArguments().getString("exportPath")!!
|
||||
|
||||
fun withArguments(exportPath: String): ExportCompleteDialog {
|
||||
@ -37,23 +37,30 @@ class ExportCompleteDialog(private val listener: ExportCompleteDialogListener) :
|
||||
return this
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): MaterialDialog {
|
||||
super.onCreate(savedInstanceState)
|
||||
val options = listOf(
|
||||
getString(R.string.export_select_save_app),
|
||||
getString(R.string.export_select_share_app),
|
||||
)
|
||||
return MaterialDialog(requireActivity()).show {
|
||||
title(text = notificationTitle)
|
||||
message(text = notificationMessage)
|
||||
icon(Themes.getResFromAttr(context, R.attr.dialogSendIcon))
|
||||
positiveButton(R.string.export_send_button) {
|
||||
listItems(items = options, waitForPositiveButton = false) { _, index, _ ->
|
||||
listener.dismissAllDialogFragments()
|
||||
listener.shareFile(exportPath)
|
||||
when (index) {
|
||||
0 -> listener.saveExportFile(exportPath)
|
||||
1 -> listener.shareFile(exportPath)
|
||||
}
|
||||
}
|
||||
negativeButton(R.string.dialog_cancel) { listener.dismissAllDialogFragments() }
|
||||
}
|
||||
}
|
||||
|
||||
override val notificationTitle: String
|
||||
get() = res().getString(R.string.export_successful_title)
|
||||
get() = res().getString(R.string.export_success_title)
|
||||
|
||||
override val notificationMessage: String
|
||||
get() = res().getString(R.string.export_successful, File(exportPath).name)
|
||||
get() = res().getString(R.string.export_success_message)
|
||||
}
|
||||
|
@ -15,16 +15,14 @@
|
||||
*/
|
||||
package com.ichi2.anki.export
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.content.pm.LabeledIntent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.app.ShareCompat.IntentBuilder
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.ichi2.anki.*
|
||||
@ -118,51 +116,6 @@ class ActivityExportingDelegate(private val activity: AnkiActivity, private val
|
||||
activity.dismissAllDialogFragments()
|
||||
}
|
||||
|
||||
@Suppress("deprecation") // API33 deprecation for pm.queryIntentActivities
|
||||
fun shareFileIntent(exportPath: String, uri: Uri): Intent {
|
||||
val attachment = File(exportPath)
|
||||
val pm: PackageManager = activity.packageManager
|
||||
val sendIntent = Intent(Intent.ACTION_SEND)
|
||||
sendIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
sendIntent.type = "application/apkg"
|
||||
val resInfo = pm.queryIntentActivities(sendIntent, 0)
|
||||
|
||||
// If we make two intents, one for save file and one for share, we get save file as
|
||||
// an option in the share sheet
|
||||
val intentList = arrayListOf(
|
||||
LabeledIntent(
|
||||
saveFileIntent(attachment),
|
||||
BuildConfig.APPLICATION_ID,
|
||||
"", // param "nonLocalizedLabel" isn't actually used
|
||||
0 // param "icon" isn't actually used, this is the constant for "No icon"
|
||||
)
|
||||
)
|
||||
|
||||
for (ri in resInfo) {
|
||||
val packageName = ri.activityInfo.packageName
|
||||
val intent = IntentBuilder(activity)
|
||||
.setType("application/apkg")
|
||||
.setStream(uri)
|
||||
.setSubject(activity.getString(R.string.export_email_subject, attachment.name))
|
||||
.setHtmlText(activity.getString(R.string.export_email_text, activity.getString(R.string.link_manual), activity.getString(R.string.link_distributions)))
|
||||
.intent
|
||||
.setAction(Intent.ACTION_SEND)
|
||||
.setDataAndType(uri, "application/apkg")
|
||||
.setComponent(ComponentName(packageName, ri.activityInfo.name))
|
||||
.setPackage(packageName)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
intentList.add(
|
||||
LabeledIntent(intent, packageName, ri.loadLabel(pm), ri.iconResource)
|
||||
)
|
||||
}
|
||||
|
||||
return Intent.createChooser(
|
||||
intentList.first(),
|
||||
activity.getString(R.string.export_share_title)
|
||||
).apply { putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toTypedArray()) }
|
||||
}
|
||||
|
||||
override fun shareFile(path: String) {
|
||||
// Make sure the file actually exists
|
||||
val attachment = File(path)
|
||||
@ -179,8 +132,25 @@ class ActivityExportingDelegate(private val activity: AnkiActivity, private val
|
||||
showThemedToast(activity, activity.resources.getString(R.string.apk_share_error), false)
|
||||
return
|
||||
}
|
||||
|
||||
val shareFileIntent = shareFileIntent(path, uri)
|
||||
val sendIntent = ShareCompat.IntentBuilder(activity)
|
||||
.setType("application/apkg")
|
||||
.setStream(uri)
|
||||
.setSubject(activity.getString(R.string.export_email_subject, attachment.name))
|
||||
.setHtmlText(
|
||||
activity.getString(
|
||||
R.string.export_email_text,
|
||||
activity.getString(R.string.link_manual),
|
||||
activity.getString(R.string.link_distributions),
|
||||
)
|
||||
)
|
||||
.intent.apply {
|
||||
clipData = ClipData.newRawUri(attachment.name, uri)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
val shareFileIntent = Intent.createChooser(
|
||||
sendIntent,
|
||||
activity.getString(R.string.export_share_title)
|
||||
)
|
||||
if (shareFileIntent.resolveActivity(activity.packageManager) != null) {
|
||||
activity.startActivityWithoutAnimation(shareFileIntent)
|
||||
} else {
|
||||
@ -201,22 +171,19 @@ class ActivityExportingDelegate(private val activity: AnkiActivity, private val
|
||||
|
||||
// Send the user to the standard Android file picker via Intent
|
||||
mExportFileName = exportPath
|
||||
val saveIntent = saveFileIntent(attachment)
|
||||
mSaveFileLauncher.launch(saveIntent)
|
||||
}
|
||||
|
||||
private fun saveFileIntent(file: File): Intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
val saveIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/apkg"
|
||||
putExtra(Intent.EXTRA_TITLE, file.name)
|
||||
putExtra(Intent.EXTRA_TITLE, attachment.name)
|
||||
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
||||
putExtra("android.content.extra.FANCY", true)
|
||||
putExtra("android.content.extra.SHOW_FILESIZE", true)
|
||||
}
|
||||
mSaveFileLauncher.launch(saveIntent)
|
||||
}
|
||||
|
||||
private fun saveFileCallback(result: ActivityResult) {
|
||||
val isSuccessful = exportToProvider(result.data!!, true)
|
||||
val isSuccessful = exportToProvider(result.data!!)
|
||||
|
||||
if (isSuccessful) {
|
||||
activity.showSnackbar(R.string.export_save_apkg_successful, Snackbar.LENGTH_SHORT)
|
||||
@ -225,7 +192,7 @@ class ActivityExportingDelegate(private val activity: AnkiActivity, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportToProvider(intent: Intent, deleteAfterExport: Boolean): Boolean {
|
||||
private fun exportToProvider(intent: Intent, deleteAfterExport: Boolean = true): Boolean {
|
||||
if (intent.data == null) {
|
||||
Timber.e("exportToProvider() provided with insufficient intent data %s", intent)
|
||||
return false
|
||||
|
@ -176,12 +176,11 @@
|
||||
<string name="confirm_apkg_export">Export collection as Anki package?</string>
|
||||
<string name="confirm_apkg_export_deck">Export “%s” as apkg file?</string>
|
||||
<string name="export_in_progress">Exporting Anki package file…</string>
|
||||
<string name="export_successful_title">Send Anki package?</string>
|
||||
<string name="export_send_button">Send</string>
|
||||
<string name="export_success_title">Collection exported successfully</string>
|
||||
<string name="export_send_no_handlers">No applications available to handle apkg. Saving...</string>
|
||||
<string name="export_save_apkg_successful">Successfully saved Anki package</string>
|
||||
<string name="export_save_apkg_unsuccessful">Save Anki package failed</string>
|
||||
<string name="export_successful">File “%s” was exported. Do you want to send it with another app?</string>
|
||||
<string name="export_success_message">You may choose a file location to save it, or share it with another application.</string>
|
||||
<string name="export_share_title">Send Anki package using</string>
|
||||
<string name="export_email_subject">AnkiDroid exported flashcards: %s</string>
|
||||
<string name="export_email_text">
|
||||
@ -195,6 +194,8 @@
|
||||
]]>
|
||||
</string>
|
||||
<string name="export_unsuccessful">Error exporting apkg file</string>
|
||||
<string name="export_select_save_app">Select where to save file</string>
|
||||
<string name="export_select_share_app">Select application for sharing</string>
|
||||
<string name="study_options" comment="Name for a preference category in Filtered deck (aka Cram deck, aka Custom study) options">Options</string>
|
||||
<string name="menu__deck_options">Deck options</string>
|
||||
<string name="menu__study_options" comment="Menu item that opens options for a Filtered deck (aka Cram deck, aka Custom study)">Study options</string>
|
||||
|
Loading…
Reference in New Issue
Block a user