Mercurial > cgi-bin > hgweb.cgi > SimpleResizer
comparison app/src/main/java/com/bartsent/simpleresizer/EditImage.kt @ 42:45e4df5226c0
Shares, but creates multiple versions of cruft file.
author | David Barts <n5jrn@me.com> |
---|---|
date | Sat, 10 Apr 2021 09:02:44 -0700 |
parents | cfb19d4ccf78 |
children | 9cb9bb5da247 |
comparison
equal
deleted
inserted
replaced
41:9231f1a41a59 | 42:45e4df5226c0 |
---|---|
27 import androidx.lifecycle.ViewModelProvider | 27 import androidx.lifecycle.ViewModelProvider |
28 import androidx.preference.PreferenceManager | 28 import androidx.preference.PreferenceManager |
29 import com.bartsent.simpleresizer.databinding.ActivityEditImageBinding | 29 import com.bartsent.simpleresizer.databinding.ActivityEditImageBinding |
30 import com.bartsent.simpleresizer.lib.ThreadPools | 30 import com.bartsent.simpleresizer.lib.ThreadPools |
31 import com.bartsent.simpleresizer.lib.getScaledInstance | 31 import com.bartsent.simpleresizer.lib.getScaledInstance |
32 import com.google.android.material.floatingactionbutton.FloatingActionButton | |
32 import java.io.File | 33 import java.io.File |
33 import java.io.IOException | 34 import java.io.IOException |
34 import java.util.concurrent.Callable | 35 import java.util.concurrent.Callable |
35 import java.util.concurrent.Future | 36 import java.util.concurrent.Future |
36 | 37 |
37 class EditImage : AppCompatActivity() { | 38 class EditImage : AppCompatActivity() { |
38 class State: ViewModel() { | 39 class State: ViewModel() { |
39 var uri: Uri? = null | 40 var uri: Uri? = null |
40 var bitmap: Bitmap? = null | 41 var bitmap: Bitmap? = null |
41 var reader: Future<Unit>? = null | 42 var reader: Future<Unit>? = null |
43 var permissionsCallback: (() -> Unit)? = null | |
44 var sharable: Boolean = false | |
42 } | 45 } |
43 private lateinit var viewModel: State | 46 private lateinit var viewModel: State |
44 | 47 |
45 private val STDDIMS = arrayOf<Int>(1600, 1280, 1024, 800, 640, 512, 400, 320).apply { | 48 private val STDDIMS = arrayOf<Int>(1600, 1280, 1024, 800, 640, 512, 400, 320).apply { |
46 sort() | 49 sort() |
47 } | 50 } |
51 | |
52 private val IMAGE_TO_SEND = "sent_image.jpg" | |
48 | 53 |
49 private lateinit var binding: ActivityEditImageBinding | 54 private lateinit var binding: ActivityEditImageBinding |
50 | 55 |
51 override fun onCreate(savedInstanceState: Bundle?) { | 56 override fun onCreate(savedInstanceState: Bundle?) { |
52 super.onCreate(savedInstanceState) | 57 super.onCreate(savedInstanceState) |
130 } | 135 } |
131 | 136 |
132 // User has opened a new image. | 137 // User has opened a new image. |
133 if (imageUri != viewModel.uri) { | 138 if (imageUri != viewModel.uri) { |
134 viewModel.uri = imageUri | 139 viewModel.uri = imageUri |
140 makeMundane() | |
135 binding.progressBar.visibility = ProgressBar.VISIBLE | 141 binding.progressBar.visibility = ProgressBar.VISIBLE |
136 viewModel.reader = ThreadPools.WORKERS.submit(Callable<Unit> { | 142 viewModel.reader = ThreadPools.WORKERS.submit(Callable<Unit> { |
137 val newBitmap = contentResolver.openInputStream(imageUri).use { | 143 val newBitmap = contentResolver.openInputStream(imageUri).use { |
138 BitmapFactory.decodeStream(it) | 144 BitmapFactory.decodeStream(it) |
139 } | 145 } |
151 | 157 |
152 // Rotation (of the phone). | 158 // Rotation (of the phone). |
153 val oldBitmap = viewModel.bitmap | 159 val oldBitmap = viewModel.bitmap |
154 if (oldBitmap != null) | 160 if (oldBitmap != null) |
155 setImage(oldBitmap) | 161 setImage(oldBitmap) |
162 binding.fabulous.visibility = if (viewModel.sharable) FloatingActionButton.VISIBLE else FloatingActionButton.GONE | |
156 } | 163 } |
157 | 164 |
158 override fun onDestroy() { | 165 override fun onDestroy() { |
159 // Read tasks may get badly constipated, since the image may well be on | 166 // Read tasks may get badly constipated, since the image may well be on |
160 // cloud server like Google Pictures. So be sure to terminate any active | 167 // cloud server like Google Pictures. So be sure to terminate any active |
235 else | 242 else |
236 Bitmap.createScaledBitmap(oldBitmap, newWidth, newHeight, true) | 243 Bitmap.createScaledBitmap(oldBitmap, newWidth, newHeight, true) |
237 runOnUiThread { | 244 runOnUiThread { |
238 binding.progressBar.visibility = ProgressBar.INVISIBLE | 245 binding.progressBar.visibility = ProgressBar.INVISIBLE |
239 setImage(newBitmap) | 246 setImage(newBitmap) |
247 makeFabulous() | |
240 } | 248 } |
241 } | 249 } |
242 } | 250 } |
243 | 251 |
244 private fun showCustomScaleDialog(): Unit { | 252 private fun showCustomScaleDialog(): Unit { |
312 } | 320 } |
313 } | 321 } |
314 | 322 |
315 fun cancelClicked(view: View): Unit { | 323 fun cancelClicked(view: View): Unit { |
316 unsetImage() | 324 unsetImage() |
325 makeMundane() | |
317 finish() | 326 finish() |
327 } | |
328 | |
329 private fun makeFabulous(): Unit { | |
330 binding.fabulous.visibility = FloatingActionButton.VISIBLE | |
331 viewModel.sharable = true | |
332 } | |
333 | |
334 private fun makeMundane(): Unit { | |
335 binding.fabulous.visibility = FloatingActionButton.GONE | |
336 viewModel.sharable = false | |
318 } | 337 } |
319 | 338 |
320 private val REQUEST_WRITE_EXTERNAL = 42 | 339 private val REQUEST_WRITE_EXTERNAL = 42 |
321 | 340 |
322 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray): Unit { | 341 override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray): Unit { |
323 if (requestCode != REQUEST_WRITE_EXTERNAL) { | 342 if (requestCode != REQUEST_WRITE_EXTERNAL) { |
324 Log.e("EditImage", "unexpected request code in onRequestPermissionsResult!") | 343 Log.e("EditImage", "unexpected request code in onRequestPermissionsResult!") |
325 return | 344 return |
326 } | 345 } |
327 if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { | 346 if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { |
328 doneClicked(null) | 347 val cb = viewModel.permissionsCallback |
348 if (cb != null) { | |
349 viewModel.permissionsCallback = null | |
350 cb() | |
351 } | |
329 } else { | 352 } else { |
330 showError(getString(R.string.error_unable_no_permissions)) | 353 showError(getString(R.string.error_unable_no_permissions)) |
331 } | 354 } |
332 } | 355 } |
333 | 356 |
334 fun requestWritePermission(): Unit { | 357 private fun requestWritePermission(): Unit { |
335 requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_EXTERNAL) | 358 requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_WRITE_EXTERNAL) |
336 } | 359 } |
337 | 360 |
338 fun doneClicked(view: View?): Unit { | 361 private fun needsWritePermission(callback: () -> Unit): Boolean { |
339 // If we need WRITE_EXTERNAL_STORAGE, request it and bail. We will be called again | |
340 // (with the permission) if it is granted. | |
341 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) { | 362 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) { |
342 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { | 363 if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { |
364 viewModel.permissionsCallback = callback | |
343 if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) | 365 if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) |
344 AlertDialog.Builder(this).also { | 366 AlertDialog.Builder(this).also { |
345 it.setMessage(getString(R.string.permission_needed, getString(R.string.app_name))) | 367 it.setMessage( |
368 getString( | |
369 R.string.permission_needed, | |
370 getString(R.string.app_name) | |
371 ) | |
372 ) | |
346 it.setNeutralButton(R.string.ok_text) { dialog, _ -> | 373 it.setNeutralButton(R.string.ok_text) { dialog, _ -> |
347 dialog.dismiss() | 374 dialog.dismiss() |
348 requestWritePermission() | 375 requestWritePermission() |
349 } | 376 } |
350 it.create() | 377 it.create() |
351 }.show() | 378 }.show() |
352 else | 379 else |
353 requestWritePermission() | 380 requestWritePermission() |
354 return | 381 return true |
355 } | 382 } |
356 } | 383 } |
384 return false | |
385 } | |
386 | |
387 fun shareClicked(view: View): Unit { | |
388 // If we need WRITE_EXTERNAL_STORAGE, request it and bail. We will be called again | |
389 // (with the permission) if it is granted. | |
390 if (needsWritePermission({ shareClicked(view) })) | |
391 return | |
357 | 392 |
358 // If we get here, we have permission to save (if we need it). | 393 // If we get here, we have permission to save (if we need it). |
394 val contentValues = makeContentValues(IMAGE_TO_SEND) | |
395 | |
396 // Delete any old file(s) | |
397 val cols = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) | |
398 arrayOf<String>(MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE, MediaStore.MediaColumns.RELATIVE_PATH) | |
399 else | |
400 arrayOf<String>(MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE) | |
401 val query = StringBuilder() | |
402 for (col in cols) { | |
403 if (query.isNotEmpty()) | |
404 query.append(" and ") | |
405 query.append(col) | |
406 query.append(" = ?") | |
407 } | |
408 try { | |
409 contentResolver.delete( | |
410 MediaStore.Images.Media.EXTERNAL_CONTENT_URI, | |
411 query.toString(), | |
412 cols.map { contentValues.getAsString(it) }.toTypedArray() | |
413 ) | |
414 } catch (e: Exception) { | |
415 Log.e("EditImage", "unexpected exception when sharing!", e) | |
416 } | |
417 | |
418 // Save new file, use it to share data. | |
419 saveAs(contentValues) { | |
420 val shareIntent: Intent = Intent().apply { | |
421 action = Intent.ACTION_SEND | |
422 putExtra(Intent.EXTRA_STREAM, it) | |
423 type = "image/jpeg" | |
424 } | |
425 startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.share_text))) | |
426 } | |
427 } | |
428 | |
429 fun doneClicked(view: View): Unit { | |
430 // If we need WRITE_EXTERNAL_STORAGE, request it and bail. We will be called again | |
431 // (with the permission) if it is granted. | |
432 if (needsWritePermission({ doneClicked(view) })) | |
433 return | |
434 | |
435 // If we get here, we have permission to save (if we need it). | |
436 val image = viewModel.bitmap!! | |
437 var fileName = getFileName(viewModel.uri!!) | |
438 if (fileName == null) { | |
439 val d = java.util.Date() | |
440 fileName = "IMG_%tY%tm%td_%tH%tM%tS".format(d, d, d, d, d, d) | |
441 } | |
442 val dot = fileName.lastIndexOf('.') | |
443 if (dot != -1) | |
444 fileName = fileName.substring(0, dot) | |
445 fileName = "${fileName}_${image.width}x${image.height}.jpg" | |
446 saveAs(makeContentValues(fileName)) { | |
447 unsetImage() | |
448 makeMundane() | |
449 finish() | |
450 } | |
451 } | |
452 | |
453 private fun makeContentValues(fileName: String): ContentValues = ContentValues().apply { | |
454 put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) | |
455 put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") | |
456 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { | |
457 put(MediaStore.MediaColumns.RELATIVE_PATH, | |
458 File(Environment.DIRECTORY_PICTURES, getString(R.string.app_name)).path) | |
459 } | |
460 } | |
461 | |
462 private fun saveAs(contentValues: ContentValues, callback: (Uri) -> Unit) { | |
359 val image = viewModel.bitmap!! | 463 val image = viewModel.bitmap!! |
360 binding.progressBar.visibility = ProgressBar.VISIBLE | 464 binding.progressBar.visibility = ProgressBar.VISIBLE |
361 ThreadPools.WORKERS.execute { | 465 ThreadPools.WORKERS.execute { |
362 val contentValues = ContentValues().apply { | |
363 var fileName = getFileName(viewModel.uri!!) | |
364 if (fileName == null) { | |
365 val d = java.util.Date() | |
366 fileName = "IMG_%tY%tm%td_%tH%tM%tS".format(d, d, d, d, d, d) | |
367 } | |
368 val dot = fileName.lastIndexOf('.') | |
369 if (dot != -1) | |
370 fileName = fileName.substring(0, dot) | |
371 fileName = "${fileName}_${image.width}x${image.height}.jpg" | |
372 put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) | |
373 put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") | |
374 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { | |
375 put(MediaStore.MediaColumns.RELATIVE_PATH, | |
376 File(Environment.DIRECTORY_PICTURES, getString(R.string.app_name)).path) | |
377 } | |
378 } | |
379 var errorMessage: String? = null | 466 var errorMessage: String? = null |
467 val myUri = try { | |
468 contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) | |
469 } catch(e: Exception) { | |
470 Log.e("EditImage", "unexpected exception when saving!", e) | |
471 null | |
472 } | |
380 try { | 473 try { |
381 val myUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) | |
382 if (myUri == null) { | 474 if (myUri == null) { |
383 throw IOException(getString(R.string.error_create_mediastore)) | 475 throw IOException(getString(R.string.error_create_mediastore)) |
384 } | 476 } |
385 val stream = contentResolver.openOutputStream(myUri) | 477 val stream = contentResolver.openOutputStream(myUri) |
386 if (stream == null) { | 478 if (stream == null) { |
404 errorMessage = e.message ?: getString(R.string.error_unexpected, e::class.qualifiedName) | 496 errorMessage = e.message ?: getString(R.string.error_unexpected, e::class.qualifiedName) |
405 } | 497 } |
406 runOnUiThread { | 498 runOnUiThread { |
407 binding.progressBar.visibility = ProgressBar.INVISIBLE | 499 binding.progressBar.visibility = ProgressBar.INVISIBLE |
408 if (errorMessage == null) { | 500 if (errorMessage == null) { |
409 unsetImage() | 501 callback(myUri!!) |
410 finish() | |
411 } else { | 502 } else { |
412 showError(errorMessage) | 503 showError(errorMessage) |
413 } | 504 } |
414 } | 505 } |
415 } | 506 } |