From 8aece5e3f9209d7c161410c304655f0aec2d6054 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sun, 7 Jan 2018 02:43:57 -0500 Subject: Feature/website debug (#603) * Create beginning of web downloader * Clean up * Update KAU for reified activity launching * Update web attachments and setFrostColor * Test other zipper * Test simpler image saving model * Finish up image activity * Restore aggressive overlays * Try new zipper * Test again * Fix tests * Add working build * Rename * Support cancellation --- .../pitchedapps/frost/activities/ImageActivity.kt | 136 +++++++++++++++------ 1 file changed, 102 insertions(+), 34 deletions(-) (limited to 'app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt index d63fa25e..99fa6eee 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -1,5 +1,6 @@ package com.pitchedapps.frost.activities +import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.graphics.Bitmap @@ -7,18 +8,20 @@ import android.graphics.Color import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle +import android.os.Environment import android.support.design.widget.FloatingActionButton -import android.support.v4.content.FileProvider import android.view.View import android.view.ViewGroup import android.widget.ProgressBar import android.widget.TextView import ca.allanwang.kau.internal.KauBaseActivity +import ca.allanwang.kau.logging.KauLoggerExtension import ca.allanwang.kau.mediapicker.scanMedia import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE import ca.allanwang.kau.permissions.kauRequestPermissions import ca.allanwang.kau.utils.* import com.bumptech.glide.Glide +import com.bumptech.glide.RequestManager import com.bumptech.glide.request.target.BaseTarget import com.bumptech.glide.request.target.SizeReadyCallback import com.bumptech.glide.request.target.Target @@ -27,14 +30,17 @@ import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.typeface.IIcon -import com.pitchedapps.frost.BuildConfig import com.pitchedapps.frost.R +import com.pitchedapps.frost.facebook.requests.call import com.pitchedapps.frost.utils.* import com.sothree.slidinguppanel.SlidingUpPanelLayout +import okhttp3.Request import org.jetbrains.anko.activityUiThreadWithContext import org.jetbrains.anko.doAsync import java.io.File import java.io.IOException +import java.text.SimpleDateFormat +import java.util.* /** * Created by Allan Wang on 2017-07-15. @@ -49,17 +55,19 @@ class ImageActivity : KauBaseActivity() { val fab: FloatingActionButton by bindView(R.id.image_fab) var errorRef: Throwable? = null + private val tempDir: File by lazy { File(cacheDir, IMAGE_FOLDER) } + /** * Reference to the temporary file path * Should be nonnull if the image is successfully loaded * As this is temporary, the image is deleted upon exit */ - private var tempFilePath: String? = null + internal var tempFile: File? = null /** * Reference to path for downloaded image * Nonnull once the image is downloaded by the user */ - internal var downloadPath: String? = null + internal var savedFile: File? = null /** * Indicator for fab's click result */ @@ -70,15 +78,28 @@ class ImageActivity : KauBaseActivity() { value.update(fab) } + companion object { + /** + * Cache folder to store images + * Linked to the uri provider + */ + private const val IMAGE_FOLDER = "images" + private const val TIME_FORMAT = "yyyyMMdd_HHmmss" + private const val IMG_TAG = "Frost" + private const val IMG_EXTENSION = ".png" + private val L = KauLoggerExtension("Image", com.pitchedapps.frost.utils.L) + } + val imageUrl: String by lazy { intent.getStringExtra(ARG_IMAGE_URL).trim('"') } val text: String? by lazy { intent.getStringExtra(ARG_TEXT) } + private val glide: RequestManager by lazy { Glide.with(this) } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) intent?.extras ?: return finish() L.i { "Displaying image" } - L._i { imageUrl } val layout = if (!text.isNullOrBlank()) R.layout.activity_image else R.layout.activity_image_textless setContentView(layout) container.setBackgroundColor(Prefs.bgColor.withMinAlpha(222)) @@ -101,8 +122,10 @@ class ImageActivity : KauBaseActivity() { imageCallback(null, false) } }) - Glide.with(this).asBitmap().load(imageUrl).into(PhotoTarget(this::imageCallback)) - setFrostColors(themeWindow = false) + glide.asBitmap().load(imageUrl).into(PhotoTarget(this::imageCallback)) + setFrostColors { + themeWindow = false + } } /** @@ -119,7 +142,7 @@ class ImageActivity : KauBaseActivity() { } else { photo.setImage(ImageSource.uri(it)) fabAction = FabStates.DOWNLOAD - photo.animate().alpha(1f).scaleXY(1f).withEndAction { fab.show() }.start() + photo.animate().alpha(1f).scaleXY(1f).withEndAction(fab::show).start() } }) } else { @@ -135,52 +158,105 @@ class ImageActivity : KauBaseActivity() { override fun removeCallback(cb: SizeReadyCallback?) {} - override fun onResourceReady(resource: Bitmap, transition: Transition?) = callback(resource, true) + override fun onResourceReady(resource: Bitmap, transition: Transition?) = + callback(resource, true) - override fun onLoadFailed(errorDrawable: Drawable?) = callback(null, false) + override fun onLoadFailed(errorDrawable: Drawable?) = + callback(null, false) - override fun getSize(cb: SizeReadyCallback) = cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) + override fun getSize(cb: SizeReadyCallback) = + cb.onSizeReady(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) } private fun saveTempImage(resource: Bitmap, callback: (uri: Uri?) -> Unit) { var photoFile: File? = null try { - photoFile = createPrivateMediaFile(".png") + photoFile = createPrivateMediaFile() } catch (e: IOException) { errorRef = e + logImage(e) } finally { if (photoFile == null) { callback(null) } else { - tempFilePath = photoFile.absolutePath - L.d { "Temp image path $tempFilePath" } + tempFile = photoFile + L.d { "Temp image path ${tempFile?.absolutePath}" } // File created; proceed with request - val photoURI = FileProvider.getUriForFile(this, - BuildConfig.APPLICATION_ID + ".provider", - photoFile) + val photoURI = frostUriFromFile(photoFile) photoFile.outputStream().use { resource.compress(Bitmap.CompressFormat.PNG, 100, it) } callback(photoURI) } } } - internal fun downloadImage() { + private fun logImage(e: Exception?) { + if (!Prefs.analytics) return + val error = e ?: IOException("$imageUrl failed to load") + L.e(error) { "$imageUrl failed to load" } + } + + @Throws(IOException::class) + private fun createPrivateMediaFile(): File { + val timeStamp = SimpleDateFormat(TIME_FORMAT, Locale.getDefault()).format(Date()) + val imageFileName = "${IMG_TAG}_${timeStamp}_" + if (!tempDir.exists()) + tempDir.mkdirs() + return File.createTempFile(imageFileName, IMG_EXTENSION, tempDir) + } + + @Throws(IOException::class) + private fun createPublicMediaFile(): File { + val timeStamp = SimpleDateFormat(TIME_FORMAT, Locale.getDefault()).format(Date()) + val imageFileName = "${IMG_TAG}_${timeStamp}_" + val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + val frostDir = File(storageDir, IMG_TAG) + if (!frostDir.exists()) frostDir.mkdirs() + return File.createTempFile(imageFileName, IMG_EXTENSION, frostDir) + } + + @Throws(IOException::class) + private fun downloadImageTo(file: File) { + val body = Request.Builder() + .url(imageUrl) + .get() + .call() + .execute() + .body() ?: throw IOException("Failed to retrieve image body") + body.byteStream().use { input -> + file.outputStream().use { output -> + input.copyTo(output) + } + } + } + + internal fun saveImage() { kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { granted, _ -> L.d { "Download image callback granted: $granted" } if (granted) { doAsync { - val destination = createMediaFile(".png") - downloadPath = destination.absolutePath + val destination = createPublicMediaFile() var success = true try { - File(tempFilePath).copyTo(destination, true) - scanMedia(destination) + val temp = tempFile + if (temp != null) + temp.copyTo(destination, true) + else + downloadImageTo(destination) } catch (e: Exception) { errorRef = e success = false } finally { L.d { "Download image async finished: $success" } + if (success) { + scanMedia(destination) + savedFile = destination + } else { + try { + destination.delete() + } catch (ignore: Exception) { + } + } activityUiThreadWithContext { val text = if (success) R.string.image_download_success else R.string.image_download_fail frostSnackbar(text) @@ -192,15 +268,9 @@ class ImageActivity : KauBaseActivity() { } } - internal fun deleteTempFile() { - if (tempFilePath != null) { - File(tempFilePath!!).delete() - tempFilePath = null - } - } - override fun onDestroy() { - deleteTempFile() + tempFile = null + tempDir.deleteRecursively() L.d { "Closing $localClassName" } super.onDestroy() } @@ -229,14 +299,12 @@ internal enum class FabStates(val iicon: IIcon, val iconColor: Int = Prefs.iconC override fun onClick(activity: ImageActivity) {} }, DOWNLOAD(GoogleMaterial.Icon.gmd_file_download) { - override fun onClick(activity: ImageActivity) = activity.downloadImage() + override fun onClick(activity: ImageActivity) = activity.saveImage() }, SHARE(GoogleMaterial.Icon.gmd_share) { override fun onClick(activity: ImageActivity) { try { - val photoURI = FileProvider.getUriForFile(activity, - BuildConfig.APPLICATION_ID + ".provider", - File(activity.downloadPath)) + val photoURI = activity.frostUriFromFile(activity.savedFile!!) val intent = Intent(Intent.ACTION_SEND).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK putExtra(Intent.EXTRA_STREAM, photoURI) -- cgit v1.2.3