[+] Drop images

This commit is contained in:
2025-10-26 23:37:54 +08:00
parent 2809dfa040
commit b91de1de56
2 changed files with 105 additions and 47 deletions
+5
View File
@@ -26,6 +26,11 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity> </activity>
</application> </application>
@@ -1,12 +1,15 @@
package aza.instant package aza.instant
import android.Manifest import android.Manifest
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Picture import android.graphics.Picture
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.OpenableColumns
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
@@ -51,20 +54,38 @@ import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private var sharedImageUri by mutableStateOf<Uri?>(null)
private var isUriHandled = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
handleIntent(intent)
setContent { setContent {
ProjectInstantTheme { ProjectInstantTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { Scaffold(modifier = Modifier.fillMaxSize()) {
FileListerScreen(modifier = Modifier.padding(it)) FileListerScreen(modifier = Modifier.padding(it), sharedImageUri = if (isUriHandled) null else sharedImageUri, onSharedImageHandled = { isUriHandled = true })
} }
} }
} }
} }
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
isUriHandled = false
handleIntent(intent)
}
private fun handleIntent(intent: Intent?) {
if (intent?.action == Intent.ACTION_SEND && intent.type?.startsWith("image/") == true) {
sharedImageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
}
}
} }
@Composable @Composable
fun FileListerScreen(modifier: Modifier = Modifier) { fun FileListerScreen(modifier: Modifier = Modifier, sharedImageUri: Uri?, onSharedImageHandled: () -> Unit) {
var feedbackText by remember { mutableStateOf("Click a button to see the output here.") } var feedbackText by remember { mutableStateOf("Click a button to see the output here.") }
var picture by remember { mutableStateOf<Picture?>(null) } var picture by remember { mutableStateOf<Picture?>(null) }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@@ -81,10 +102,66 @@ fun FileListerScreen(modifier: Modifier = Modifier) {
.toTypedArray().ifEmpty { null }?.let { askPerm.launch(it) } .toTypedArray().ifEmpty { null }?.let { askPerm.launch(it) }
} }
Column( val renderAndUpload = { imagePath: String, originalFileName: String ->
modifier = modifier.fillMaxSize(), coroutineScope.launch {
horizontalAlignment = Alignment.CenterHorizontally try {
) { feedbackText = "Rendering..."
val outDir = File("/storage/emulated/0/DCIM/CA_IMAGES_OUT/")
val templateStream = context.resources.openRawResource(R.raw.postcard4)
val template = BufferedReader(InputStreamReader(templateStream)).readText()
val (svg, exif) = genSvg(template, imagePath)
val (pic, bytes) = renderSvgBytes(context, svg, exif) ?: return@launch
picture = pic
// Save picture to file
outDir.mkdirs()
val out = File(outDir, "${File(originalFileName).nameWithoutExtension}-framed.jpg")
.apply { writeBytes(bytes) }
MediaScannerConnection.scanFile(context, arrayOf(out.absolutePath), null, null)
feedbackText = "Rendered ${originalFileName}, now uploading..."
val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(workDataOf(
"originalPhotoPath" to imagePath,
"editedPhotoPath" to out.absolutePath,
"urlName" to exif.urlName
))
.build()
WorkManager.getInstance(context).enqueue(uploadWorkRequest)
// Launch Canon app
context.startActivity(Intent().apply {
setClassName("jp.co.canon.ic.photolayout", "jp.co.canon.ic.photolayout.MainActivity")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
} catch (e: Exception) {
feedbackText = "Error during operation: ${e.message}"
Log.e("MainActivity", "Error during render or upload", e)
}
}
}
LaunchedEffect(sharedImageUri) {
if (sharedImageUri == null) return@LaunchedEffect
feedbackText = "Handling shared image..."
val fileName = getFileName(context, sharedImageUri) ?: "shared_image_${System.currentTimeMillis()}.jpg"
val tempFile = File(context.cacheDir, fileName)
try {
context.contentResolver.openInputStream(sharedImageUri)?.use { input ->
tempFile.outputStream().use { output -> input.copyTo(output) }
}
renderAndUpload(tempFile.absolutePath, fileName)
onSharedImageHandled()
} catch (e: Exception) {
feedbackText = "Failed to handle shared image: ${e.message}"
Log.e("MainActivity", "Error handling shared image", e)
}
}
Column(modifier = modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
Column(modifier = Modifier.weight(1f), horizontalAlignment = Alignment.CenterHorizontally) { Column(modifier = Modifier.weight(1f), horizontalAlignment = Alignment.CenterHorizontally) {
picture?.let { p -> picture?.let { p ->
Canvas(modifier = Modifier.fillMaxWidth().weight(1f).drawWithCache { Canvas(modifier = Modifier.fillMaxWidth().weight(1f).drawWithCache {
@@ -102,61 +179,37 @@ fun FileListerScreen(modifier: Modifier = Modifier) {
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Button(onClick = { Button(onClick = {
coroutineScope.launch { coroutineScope.launch {
feedbackText = "Rendering..."
val imageDir = File("/storage/emulated/0/DCIM/CA_IMAGES/") val imageDir = File("/storage/emulated/0/DCIM/CA_IMAGES/")
val outDir = File("/storage/emulated/0/DCIM/CA_IMAGES_OUT/")
val latestImg = imageDir.listFiles() val latestImg = imageDir.listFiles()
?.filter { !it.name.contains("-framed") and !it.name.contains(".trashed") } ?.filter { !it.name.contains("-framed") && !it.name.contains(".trashed") }
?.maxByOrNull { it.lastModified() } ?.maxByOrNull { it.lastModified() }
if (latestImg == null) { if (latestImg == null) {
feedbackText = "No images found in CA_IMAGES" feedbackText = "No images found in CA_IMAGES"
return@launch return@launch
} }
try { renderAndUpload(latestImg.absolutePath, latestImg.name)
val templateStream = context.resources.openRawResource(R.raw.postcard4)
val template = BufferedReader(InputStreamReader(templateStream)).readText()
val (svg, exif) = genSvg(template, latestImg.absolutePath)
val (pic, bytes) = renderSvgBytes(context, svg, exif) ?: return@launch
picture = pic
// Save picture to file
outDir.mkdirs()
val out = File(outDir, "${latestImg.nameWithoutExtension}-framed.jpg")
.apply { writeBytes(bytes) }
MediaScannerConnection.scanFile(context, arrayOf(out.absolutePath), null, null)
feedbackText = "Rendered ${latestImg.name}, now uploading..."
val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setInputData(workDataOf(
"originalPhotoPath" to latestImg.absolutePath,
"editedPhotoPath" to out.absolutePath,
"urlName" to exif.urlName
))
.build()
WorkManager.getInstance(context).enqueue(uploadWorkRequest)
// Launch Canon app
context.startActivity(Intent().apply {
setClassName("jp.co.canon.ic.photolayout", "jp.co.canon.ic.photolayout.MainActivity")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
})
} catch (e: Exception) {
feedbackText = "Error during operation: ${e.message}"
Log.e("MainActivity", "Error during render or upload", e)
}
} }
}) { Text(text = "Render Latest") } }) { Text(text = "Render Latest") }
} }
} }
} }
private fun getFileName(context: Context, uri: Uri): String? {
context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (nameIndex != -1) return cursor.getString(nameIndex)
}
}
return uri.path?.substringAfterLast('/')
}
private fun listFilesFromDcim(): String { private fun listFilesFromDcim(): String {
val d = File("/storage/emulated/0/DCIM/CA_IMAGES/") val d = File("/storage/emulated/0/DCIM/CA_IMAGES/")
if (!d.exists() || !d.isDirectory) return "Directory not found or is not a directory: ${d.absolutePath}" if (!d.exists() || !d.isDirectory) return "Directory not found or is not a directory: ${d.absolutePath}"
return d.listFiles()?.let { return d.listFiles()?.let { files ->
it.ifEmpty { null }?.sortedBy { it.lastModified() }?.joinToString("\n") { it.name } files.ifEmpty { null }?.sortedBy { it.lastModified() }?.joinToString("\n") { it.name }
?: "No files found in ${d.absolutePath}" ?: "No files found in ${d.absolutePath}"
} ?: "Failed to list files in ${d.absolutePath}. listFiles() returned null." } ?: "Failed to list files in ${d.absolutePath}. listFiles() returned null."
} }
@@ -165,6 +218,6 @@ private fun listFilesFromDcim(): String {
@Composable @Composable
fun FileListerScreenPreview() { fun FileListerScreenPreview() {
ProjectInstantTheme { ProjectInstantTheme {
FileListerScreen() FileListerScreen(sharedImageUri = null, onSharedImageHandled = {})
} }
} }