Skip to content

Commit

Permalink
Support sharing media attachment with paste
Browse files Browse the repository at this point in the history
Any file with mimetype application/*, audio/*, model/*, video/* is supported.
Note: We do not yet do any size checking. But we do report attach file size
  • Loading branch information
nain-F49FF806 committed Jul 26, 2024
1 parent 058aacf commit cd487f0
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 5 deletions.
2 changes: 1 addition & 1 deletion android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ android {
applicationId = "alt.nainapps.sharepaste"
minSdk = 26
targetSdk = 34
versionCode = 1721991000
versionCode = 1722012000
versionName = "2024.07.26"
setProperty("archivesBaseName", "sharepaste.oo")

Expand Down
15 changes: 15 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@
<data android:mimeType="text/*" />
</intent-filter>
</activity>
<activity
android:name=".intents.ShareAttachActivity"
android:exported="true"
android:label="@string/title_activity_share_text"
android:theme="@style/Theme.SharePastePrivatebin">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/*" />
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
<data android:mimeType="model/*" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
<activity
android:name=".launcher.MainActivity"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ import uniffi.pbcli.PasteFormat
fun EncryptAndShareUI(
text: String = "",
textFormat: PasteFormat? = null,
attach: String? = null,
attachName: String? = null,
customPrivatebinHost: String? = null
) {
var textToEncrypt by rememberSaveable { mutableStateOf(text) }
val textFormat by rememberSaveable { mutableStateOf(textFormat) }
val attach by rememberSaveable { mutableStateOf(attach) }
val attachName by rememberSaveable { mutableStateOf(attachName) }
var expiry by rememberSaveable { mutableStateOf("1day") }
var burnOnRead by rememberSaveable { mutableStateOf(false) }
val customPrivatebinHost by rememberSaveable { mutableStateOf(customPrivatebinHost) }
Expand Down Expand Up @@ -90,7 +94,7 @@ fun EncryptAndShareUI(
coroutineScope.launch(Dispatchers.IO) {
val pb = PrivateBinRs(defaultBaseUrl = customPrivatebinHost)
val opts = pb.getOpts(format = textFormat, expire = expiry, burn = burnOnRead)
val pbResponse = pb.send(textToEncrypt, opts)
val pbResponse = pb.send(textToEncrypt, opts, attach, attachName)
shareLink = pbResponse.toPasteUrl()
deleteLink = pbResponse.toDeleteUrl()
isLoading = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package alt.nainapps.sharepaste.intents

import alt.nainapps.sharepaste.common.EncryptAndShareUI
import alt.nainapps.sharepaste.intents.ui.theme.SharePasteO2Theme
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.provider.OpenableColumns
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.preference.PreferenceManager
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

const val TAG = "ShareAttachActivity"

class ShareAttachActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
assert(intent?.action == Intent.ACTION_SEND) {
"This activity is expected to be intent filtered to Intent.ACTION_SEND"
}
var attach: String? = null
var attachName: String? = null
var attachSize: String? = "unknown size"
val customPrivatebinHost = PreferenceManager.getDefaultSharedPreferences(
this
).getString("privatebin_host_url", null)

// If there is some extra text, receive it.
val text = intent.getStringExtra(Intent.EXTRA_TEXT).orEmpty()

// If there's a file attached, we should read it and covert it's contents to dataUri.
val contentUri: Uri? = intent.getParcelableExtra<Parcelable>(
Intent.EXTRA_STREAM
) as? Uri

contentUri?.let { contentUri ->
try {
val mimeType = contentResolver.getType(contentUri).also {
Log.i(TAG, "Attach file type: $it")
} ?: ""

/*
* Get the file's content URI from the incoming Intent,
* then query the server app to get the file's display name
* and size.
*/
contentResolver.query(contentUri, null, null, null, null)?.use { cursor ->
/*
* Get the column indexes of the data in the Cursor,
* move to the first row in the Cursor, get the data,
* and display it.
*/
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.moveToFirst()
attachName = cursor.getString(nameIndex)
attachSize = bytesToHumanReadableSize(cursor.getLong(sizeIndex))
}

contentResolver.openInputStream(contentUri)?.use { inputStream ->
// inputStream is guaranteed to be non-null here
// Process the input stream
Log.i(TAG, "Processing input stream...")
// Convert InputStream to Base64 String
inputStreamToBase64String(inputStream).let {
// Construct the Data URI
attach = "data:$mimeType;base64,$it"
}
}
} catch (e: FileNotFoundException) {
// Handle the case where the file was not found
Log.e(TAG, "File not found: ${e.message}")
} catch (e: IOException) {
// Handle general IO errors, including permission issues
Log.e(TAG, "IO Error: ${e.message}")
}
}

setContent {
SharePasteO2Theme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Spacer(modifier = Modifier.height(16.dp))
Text(
textAlign = TextAlign.Center,
text = "Attachment: $attachName ($attachSize)"
)
// Note: TODO(" We should maybe warn when too big file size above ")
EncryptAndShareUI(
text = text,
attach = attach,
attachName = attachName,
customPrivatebinHost = customPrivatebinHost
)
}
}
}
}
}
}

// https://stackoverflow.com/a/68822715
fun bytesToHumanReadableSize(bytes: Long) = when {
bytes >= 1 shl 30 -> "%.1f GB".format(bytes.toDouble() / (1 shl 30))
bytes >= 1 shl 20 -> "%.1f MB".format(bytes.toDouble() / (1 shl 20))
bytes >= 1 shl 10 -> "%.0f kB".format(bytes.toDouble() / (1 shl 10))
else -> "$bytes bytes"
}

@OptIn(ExperimentalEncodingApi::class)
fun inputStreamToBase64String(inputStream: InputStream): String? = try {
Base64.encode(inputStream.readBytes())
} catch (e: Exception) {
Log.e(TAG, "Encoding Base64 Error: ${e.message}")
null // Handle exception appropriately
}

@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}

@Preview(showBackground = true)
@Composable
private fun GreetingPreview() {
SharePasteO2Theme {
Greeting("Android")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class ShareTextActivity : ComponentActivity() {
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
private fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
<string name="title_activity_share_text">Encrypt&amp;Share</string>
<string name="title_activity_settings">Settings</string>
<string name="default_privatebin_host_url" translatable="false">https://privatebin.net</string>
<string name="attachment_name_title">Attachment</string>
<string name="attachment_size_title">Size</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ class PrivateBinRs(private val defaultBaseUrl: String? = null) {
burn = burn ?: false
)

fun send(text: String, opts: Opts = defaultOpts): PostPasteResponse {
val decryptedPaste = DecryptedPaste(text, null, null)
fun send(
text: String,
opts: Opts = defaultOpts,
attachment: String? = null,
attachmentName: String? = null
): PostPasteResponse {
val decryptedPaste = DecryptedPaste(text, attachment, attachmentName)
val api = Api(opts.url ?: defaultOpts.url!!, opts)
val postPasteResponse = api.postPaste(decryptedPaste, opts.password ?: "", opts)
// postPasteResponse.toUrl(api.base())
Expand Down

0 comments on commit cd487f0

Please sign in to comment.