Skip to content

Commit

Permalink
we can now store the file where the user wants to
Browse files Browse the repository at this point in the history
  • Loading branch information
malmstein committed Nov 21, 2023
1 parent 9b89236 commit c5bc026
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.duckduckgo.sync.impl
import android.content.Context
import android.graphics.pdf.PdfDocument
import android.graphics.pdf.PdfDocument.PageInfo.Builder
import android.net.Uri
import android.os.Environment
import android.view.LayoutInflater
import android.view.View
Expand All @@ -28,11 +29,22 @@ import com.duckduckgo.common.utils.checkMainThread
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.sync.impl.databinding.ViewRecoveryCodeBinding
import com.squareup.anvil.annotations.ContributesBinding
import timber.log.Timber
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import javax.inject.*

interface RecoveryCodePDF {

@WorkerThread
fun storeRecoveryCodePDF(
viewContext: Context,
recoveryCodeB64: String,
uri: Uri,
): Uri

@WorkerThread
fun generateAndStoreRecoveryCodePDF(
viewContext: Context,
Expand All @@ -44,6 +56,43 @@ interface RecoveryCodePDF {
class RecoveryCodePDFImpl @Inject constructor(
private val qrEncoder: QREncoder,
) : RecoveryCodePDF {
override fun storeRecoveryCodePDF(
viewContext: Context,
recoveryCodeB64: String,
uri: Uri
): Uri {
checkMainThread()

val bitmapQR = qrEncoder.encodeAsBitmap(recoveryCodeB64, R.dimen.qrSizeLarge, R.dimen.qrSizeLarge)
val pdfDocument = PdfDocument()
val inflater = LayoutInflater.from(viewContext)
val page = pdfDocument.startPage(Builder(a4PageWidth.toPx(), a4PageHeight.toPx(), 1).create())
ViewRecoveryCodeBinding.inflate(inflater, null, false).apply {
this.qrCodeImageView.setImageBitmap(bitmapQR)
this.recoveryCodeText.text = recoveryCodeB64
val measureWidth: Int = View.MeasureSpec.makeMeasureSpec(page.canvas.width, View.MeasureSpec.EXACTLY)
val measuredHeight: Int = View.MeasureSpec.makeMeasureSpec(page.canvas.height, View.MeasureSpec.EXACTLY)
this.root.measure(measureWidth, measuredHeight)
this.root.layout(0, 0, page.canvas.width, page.canvas.height)
this.root.draw(page.canvas)
}
pdfDocument.finishPage(page)

try {
viewContext.contentResolver.openFileDescriptor(uri, "w")?.use { parcelFileDescriptor ->
FileOutputStream(parcelFileDescriptor.fileDescriptor).use { fileOutputStream ->
pdfDocument.writeTo(fileOutputStream)
pdfDocument.close()
}
}
} catch (e: FileNotFoundException) {
Timber.d("Sync: Pdf FileNotFoundException $e")
} catch (e: IOException) {
Timber.d("Sync: Pdf IOException $e")
}

return uri
}

override fun generateAndStoreRecoveryCodePDF(
viewContext: Context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,31 @@ import timber.log.Timber

class ShareAction @Inject constructor(private val appBuildConfig: AppBuildConfig) {

fun shareFileUri(applicationContext: Context, uri: Uri): Boolean {
val intent = createShareUriIntent(applicationContext, uri)
return if (intent != null) startActivity(applicationContext, intent) else false
}

private fun createShareUriIntent(applicationContext: Context, uri: Uri): Intent? {
val intent =
Intent().apply {
setDataAndType(uri, applicationContext.contentResolver?.getType(uri))
action = Intent.ACTION_SEND
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
putExtra(Intent.EXTRA_STREAM, uri)
}
return Intent.createChooser(
intent,
applicationContext.getString(R.string.sync_share_title),
)
.apply {
if (appBuildConfig.sdkInt >= Build.VERSION_CODES.Q) {
// Show a thumbnail preview of the file to be shared on Android Q and above.
clipData = ClipData.newRawUri(uri.toString(), uri)
}
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
}

fun shareFile(applicationContext: Context, file: File): Boolean {
val intent = createShareIntent(applicationContext, file)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ package com.duckduckgo.sync.impl.ui

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.DocumentsContract
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
Expand Down Expand Up @@ -130,9 +128,8 @@ class SyncActivity : DuckDuckGoActivity() {

private val savePDFLauncher = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
Timber.d("Sync: Pdf location chosen")
result.data?.let {
viewModel.onPdfLocationChosen(it.data!!)
viewModel.onPdfLocationChosen(this@SyncActivity, it.data!!)
}
}
}
Expand Down Expand Up @@ -231,7 +228,7 @@ class SyncActivity : DuckDuckGoActivity() {
is AskTurnOffSync -> askTurnOffsync(it.device)
is AskDeleteAccount -> askDeleteAccount()
is RecoveryCodePDFSuccess -> {
shareAction.shareFile(this@SyncActivity, it.recoveryCodePDFFile)
shareAction.shareFileUri(this@SyncActivity, it.recoveryCodePDFUri)
}

is CheckIfUserHasStoragePermission -> {
Expand All @@ -243,11 +240,11 @@ class SyncActivity : DuckDuckGoActivity() {
is AskRemoveDevice -> askRemoveDevice(it.device)
is AskEditDevice -> askEditDevice(it.device)
is ShowTextCode -> startActivity(ShowCodeActivity.intent(this))
is AskPDFLocation -> createFile()
is AskPDFLocation -> askForPdfLocation()
}
}

private fun createFile() {
private fun askForPdfLocation() {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/pdf"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class SyncActivityViewModel @Inject constructor(
data class AskTurnOffSync(val device: ConnectedDevice) : Command()
object AskDeleteAccount : Command()
object CheckIfUserHasStoragePermission : Command()
data class RecoveryCodePDFSuccess(val recoveryCodePDFFile: File) : Command()
data class RecoveryCodePDFSuccess(val recoveryCodePDFUri: Uri) : Command()
object AskPDFLocation : Command()
data class AskRemoveDevice(val device: ConnectedDevice) : Command()
data class AskEditDevice(val device: ConnectedDevice) : Command()
Expand Down Expand Up @@ -229,9 +229,14 @@ class SyncActivityViewModel @Inject constructor(
showAccountDetailsIfNeeded()
}

fun onPdfLocationChosen(fileLocation: Uri) {
fun onPdfLocationChosen(viewContext: Context, fileLocation: Uri) {
Timber.d("Sync: Pdf location chosen $fileLocation")

viewModelScope.launch(dispatchers.io()) {
val recoveryCodeB64 = syncAccountRepository.getRecoveryCode() ?: return@launch
val generateRecoveryCodePDF = recoveryCodePDF.storeRecoveryCodePDF(viewContext, recoveryCodeB64, fileLocation)
// should return a result (error, Uri)
command.send(RecoveryCodePDFSuccess(generateRecoveryCodePDF))
}
}

fun onDeleteAccountClicked() {
Expand Down

0 comments on commit c5bc026

Please sign in to comment.