0
0
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:
lukstbit 2022-10-03 14:03:05 +03:00 committed by Mike Hardy
parent eb49979e9b
commit 8e13995d7a
3 changed files with 46 additions and 71 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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>