aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2020-03-01 22:54:38 -0800
committerGitHub <noreply@github.com>2020-03-01 22:54:38 -0800
commit6d29dcd17be1d5d2ddb99bf3befea125ca955420 (patch)
tree5a2a0669effbd22e0bdd7062193da42afdb6be7e
parente732e30d97babca49ba5f7d97aea1620ba14024b (diff)
parent23acc1b70887dc7b1a9600a8cdef1e2c9676665d (diff)
downloadfrost-6d29dcd17be1d5d2ddb99bf3befea125ca955420.tar.gz
frost-6d29dcd17be1d5d2ddb99bf3befea125ca955420.tar.bz2
frost-6d29dcd17be1d5d2ddb99bf3befea125ca955420.zip
Merge pull request #1658 from AllanWang/save-image
Save image
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt197
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt13
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt2
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt2
-rw-r--r--app/src/main/play/en-US/whatsnew3
-rw-r--r--app/src/main/res/layout/activity_image.xml39
-rw-r--r--app/src/main/res/values/styles.xml9
-rw-r--r--app/src/main/res/xml/frost_changelog.xml2
-rw-r--r--docs/Changelog.md1
9 files changed, 121 insertions, 147 deletions
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 6ae7622d..f8ba32c4 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
@@ -21,19 +21,18 @@ import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
-import android.os.Environment
import android.view.View
+import android.widget.ImageView
import androidx.customview.widget.ViewDragHelper
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.adjustAlpha
import ca.allanwang.kau.utils.colorToForeground
import ca.allanwang.kau.utils.copyFromInputStream
+import ca.allanwang.kau.utils.fadeIn
import ca.allanwang.kau.utils.fadeOut
import ca.allanwang.kau.utils.gone
+import ca.allanwang.kau.utils.invisible
import ca.allanwang.kau.utils.isHidden
import ca.allanwang.kau.utils.isVisible
import ca.allanwang.kau.utils.materialDialog
@@ -61,20 +60,13 @@ import com.pitchedapps.frost.utils.ARG_COOKIE
import com.pitchedapps.frost.utils.ARG_IMAGE_URL
import com.pitchedapps.frost.utils.ARG_TEXT
import com.pitchedapps.frost.utils.Prefs
+import com.pitchedapps.frost.utils.frostDownload
import com.pitchedapps.frost.utils.frostSnackbar
import com.pitchedapps.frost.utils.frostUriFromFile
import com.pitchedapps.frost.utils.isIndirectImageUrl
import com.pitchedapps.frost.utils.logFrostEvent
import com.pitchedapps.frost.utils.sendFrostEmail
import com.pitchedapps.frost.utils.setFrostColors
-import java.io.File
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import kotlin.math.abs
-import kotlin.math.max
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
@@ -82,7 +74,11 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
-import org.koin.core.inject
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import kotlin.math.abs
+import kotlin.math.max
/**
* Created by Allan Wang on 2017-07-15.
@@ -97,26 +93,10 @@ class ImageActivity : KauBaseActivity() {
/**
* Reference to the temporary file path
*/
- internal lateinit var tempFile: File
- /**
- * Reference to path for downloaded image
- * Nonnull once the image is downloaded by the user
- */
- internal var savedFile: File? = null
- /**
- * Indicator for fab's click result
- */
- internal var fabAction: FabStates = FabStates.NOTHING
- set(value) {
- if (field == value) return
- field = value
- value.update(binding.imageFab, prefs)
- }
+ internal var tempFile: File? = null
private lateinit var dragHelper: ViewDragHelper
- private var imgExtension: String = ".jpg"
-
companion object {
/**
* Cache folder to store images
@@ -145,7 +125,7 @@ class ImageActivity : KauBaseActivity() {
"${abs(FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode() ?: 0)}_${abs(imageUrl.hashCode())}"
}
- private lateinit var binding: ActivityImageBinding
+ lateinit var binding: ActivityImageBinding
private var bottomBehavior: BottomSheetBehavior<View>? = null
private val baseBackgroundColor = if (prefs.blackMediaBg) Color.BLACK
@@ -163,8 +143,8 @@ class ImageActivity : KauBaseActivity() {
if (imageProgress.isVisible)
imageProgress.fadeOut()
}
- tempFile.delete()
- fabAction = FabStates.ERROR
+ tempFile?.delete()
+ binding.error.fadeIn()
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -182,34 +162,31 @@ class ImageActivity : KauBaseActivity() {
}
binding = ActivityImageBinding.inflate(layoutInflater)
setContentView(binding.root)
- binding.onCreate()
- tempFile = File(cacheDir(this), imageHash)
+ binding.init()
launch(CoroutineExceptionHandler { _, throwable -> loadError(throwable) }) {
- downloadImageTo(tempFile)
+ val tempFile = downloadTempImage()
+ this@ImageActivity.tempFile = tempFile
binding.imageProgress.fadeOut()
binding.imagePhoto.setImage(ImageSource.uri(frostUriFromFile(tempFile)))
- fabAction = FabStates.DOWNLOAD
binding.imagePhoto.animate().alpha(1f).scaleXY(1f).start()
}
}
- private fun ActivityImageBinding.onCreate() {
+ private fun ActivityImageBinding.init() {
imageContainer.setBackgroundColor(baseBackgroundColor)
+ toolbar.setBackgroundColor(baseBackgroundColor)
this@ImageActivity.imageText.also { text ->
if (text.isNullOrBlank()) {
imageText.gone()
} else {
imageText.setTextColor(if (prefs.blackMediaBg) Color.WHITE else prefs.textColor)
imageText.setBackgroundColor(
- (if (prefs.blackMediaBg) Color.BLACK else prefs.bgColor)
- .colorToForeground(0.2f).withAlpha(255)
+ baseBackgroundColor.colorToForeground(0.2f).withAlpha(255)
)
imageText.text = text
bottomBehavior = BottomSheetBehavior.from<View>(imageText).apply {
- setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
+ addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
- if (slideOffset == 0f && !imageFab.isShown) imageFab.show()
- else if (slideOffset != 0f && imageFab.isShown) imageFab.hide()
imageText.alpha = slideOffset / 2 + 0.5f
}
@@ -221,8 +198,24 @@ class ImageActivity : KauBaseActivity() {
imageText.bringToFront()
}
}
- imageProgress.tint(if (prefs.blackMediaBg) Color.WHITE else prefs.accentColor)
- imageFab.setOnClickListener { fabAction.onClick(this@ImageActivity) }
+ val foregroundTint = if (prefs.blackMediaBg) Color.WHITE else prefs.accentColor
+
+ fun ImageView.setState(state: FabStates) {
+ setIcon(state.iicon, color = foregroundTint, sizeDp = 24)
+ setOnClickListener { state.onClick(this@ImageActivity) }
+ }
+
+ imageProgress.tint(foregroundTint)
+ error.apply {
+ invisible()
+ setState(FabStates.ERROR)
+ }
+ download.apply {
+ setState(FabStates.DOWNLOAD)
+ }
+ share.apply {
+ setState(FabStates.SHARE)
+ }
imagePhoto.setOnImageEventListener(object :
SubsamplingScaleImageView.DefaultOnImageEventListener() {
override fun onImageLoadError(e: Exception) {
@@ -267,7 +260,7 @@ class ImageActivity : KauBaseActivity() {
scrollToTop = top < 0
val multiplier = max(1f - scrollPercent, 0f)
- imageFab.alpha = multiplier
+ toolbar.alpha = multiplier
bottomBehavior?.also {
imageText.alpha =
multiplier * (if (it.state == BottomSheetBehavior.STATE_COLLAPSED) 0.5f else 1f)
@@ -305,100 +298,38 @@ class ImageActivity : KauBaseActivity() {
return null
}
return when (type.substring(6)) {
- "jpeg" -> ".jpg"
- "png" -> ".png"
- "gif" -> ".gif"
+ "jpeg" -> "jpg"
+ "png" -> "png"
+ "gif" -> "gif"
else -> null
}
}
@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, imgExtension, frostDir)
- }
-
- /**
- * Saves the image to the specified file, creating it if it doesn't exist.
- * Returns true if a change is made, false otherwise.
- * Throws an error if something goes wrong.
- */
- @Throws(IOException::class)
- private suspend fun downloadImageTo(file: File): Boolean {
- val exceptionHandler = CoroutineExceptionHandler { _, err ->
- if (file.isFile && file.length() == 0L) {
- file.delete()
- }
- throw err
+ private suspend fun downloadTempImage(): File = withContext(Dispatchers.IO) {
+ val response = cookie.requestBuilder()
+ .url(trueImageUrl.await())
+ .get()
+ .call()
+ .execute()
+
+ if (!response.isSuccessful) {
+ throw IOException("Unsuccessful response for image: ${response.peekBody(128).string()}")
}
- return withContext(Dispatchers.IO + exceptionHandler) {
- if (!file.isFile) {
- file.parentFile?.mkdirs()
- file.createNewFile()
- } else {
- file.setLastModified(System.currentTimeMillis())
- }
-
- // Forbid overwrites
- if (file.isFile && file.length() > 0) {
- L.i { "Forbid image overwrite" }
- return@withContext false
- }
- // Fast route, image is already downloaded
- if (tempFile.isFile && tempFile.length() > 0) {
- if (tempFile == file) {
- return@withContext false
- }
- tempFile.copyTo(file, true)
- return@withContext true
- }
+ val imgExtension = getImageExtension(response.header("Content-Type")) ?: "jpg"
- // No temp file, download ourselves
- val response = cookie.requestBuilder()
- .url(trueImageUrl.await())
- .get()
- .call()
- .execute()
+ val body = response.body ?: throw IOException("Failed to retrieve image body")
- if (!response.isSuccessful) {
- throw IOException("Unsuccessful response for image: ${response.peekBody(128).string()}")
- }
-
- imgExtension = getImageExtension(response.header("Content-Type")) ?: ".jpg"
-
- val body = response.body ?: throw IOException("Failed to retrieve image body")
-
- file.copyFromInputStream(body.byteStream())
-
- return@withContext true
+ val tempFile = File(cacheDir(this@ImageActivity), "$imageHash.$imgExtension")
+ if (!tempFile.exists() || tempFile.length() == 0L) {
+ tempFile.copyFromInputStream(body.byteStream())
}
+ tempFile
}
- internal fun saveImage() {
- kauRequestPermissions(PERMISSION_WRITE_EXTERNAL_STORAGE) { granted, _ ->
- L.d { "Download image callback granted: $granted" }
- if (granted) {
- val errorHandler = CoroutineExceptionHandler { _, throwable ->
- loadError(throwable)
- frostSnackbar(R.string.image_download_fail)
- }
- launch(errorHandler) {
- val destination = createPublicMediaFile()
- downloadImageTo(destination)
- L.d { "Download image async finished" }
- scanMedia(destination)
- savedFile = destination
- frostSnackbar(R.string.image_download_success)
- fabAction = FabStates.SHARE
- }
- }
- }
+ internal suspend fun saveImage() {
+ frostDownload(cookie = cookie, url = trueImageUrl.await())
}
override fun onDestroy() {
@@ -435,12 +366,18 @@ internal enum class FabStates(
override fun onClick(activity: ImageActivity) {}
},
DOWNLOAD(GoogleMaterial.Icon.gmd_file_download) {
- override fun onClick(activity: ImageActivity) = activity.saveImage()
+ override fun onClick(activity: ImageActivity) {
+ activity.launch {
+ activity.binding.download.fadeOut()
+ activity.saveImage()
+ }
+ }
},
SHARE(GoogleMaterial.Icon.gmd_share) {
override fun onClick(activity: ImageActivity) {
+ val file = activity.tempFile ?: return
try {
- val photoURI = activity.frostUriFromFile(activity.savedFile!!)
+ val photoURI = activity.frostUriFromFile(file)
val intent = Intent(Intent.ACTION_SEND).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
putExtra(Intent.EXTRA_STREAM, photoURI)
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
index 5e909b03..60c642f4 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
@@ -22,6 +22,7 @@ import android.content.Context.DOWNLOAD_SERVICE
import android.net.Uri
import android.os.Environment
import android.webkit.URLUtil
+import androidx.core.content.getSystemService
import ca.allanwang.kau.permissions.PERMISSION_WRITE_EXTERNAL_STORAGE
import ca.allanwang.kau.permissions.kauRequestPermissions
import ca.allanwang.kau.utils.isAppEnabled
@@ -39,7 +40,7 @@ import com.pitchedapps.frost.facebook.USER_AGENT
* With reference to <a href="https://stackoverflow.com/questions/33434532/android-webview-download-files-like-browsers-do">Stack Overflow</a>
*/
fun Context.frostDownload(
- cookie: CookieEntity,
+ cookie: String?,
url: String?,
userAgent: String = USER_AGENT,
contentDisposition: String? = null,
@@ -51,7 +52,7 @@ fun Context.frostDownload(
}
fun Context.frostDownload(
- cookie: CookieEntity,
+ cookie: String?,
uri: Uri?,
userAgent: String = USER_AGENT,
contentDisposition: String? = null,
@@ -64,7 +65,8 @@ fun Context.frostDownload(
toast(R.string.error_invalid_download)
return L.e { "Invalid download $uri" }
}
- if (!isAppEnabled(DOWNLOAD_MANAGER_PACKAGE)) {
+ val dm = getSystemService<DownloadManager>()
+ if (dm == null || !isAppEnabled(DOWNLOAD_MANAGER_PACKAGE)) {
materialDialog {
title(R.string.no_download_manager)
message(R.string.no_download_manager_desc)
@@ -80,14 +82,15 @@ fun Context.frostDownload(
val request = DownloadManager.Request(uri)
request.setMimeType(mimeType)
val title = URLUtil.guessFileName(uri.toString(), contentDisposition, mimeType)
- request.addRequestHeader("Cookie", cookie.cookie)
+ if (cookie != null) {
+ request.addRequestHeader("Cookie", cookie)
+ }
request.addRequestHeader("User-Agent", userAgent)
request.setDescription(string(R.string.downloading))
request.setTitle(title)
request.allowScanningByMediaScanner()
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "Frost/$title")
- val dm = getSystemService(DOWNLOAD_SERVICE) as DownloadManager
try {
dm.enqueue(request)
} catch (e: Exception) {
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt
index ec822bfa..20480d10 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostVideoViewer.kt
@@ -118,7 +118,7 @@ class FrostVideoViewer @JvmOverloads constructor(
R.id.action_pip -> video.isExpanded = false
R.id.action_download -> context.ctxCoroutine.launchMain {
val cookie = cookieDao.currentCookie(prefs) ?: return@launchMain
- context.frostDownload(cookie, video.videoUri)
+ context.frostDownload(cookie.cookie, video.videoUri)
}
}
true
diff --git a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
index 9ca622a3..b04b8855 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/views/FrostWebView.kt
@@ -98,7 +98,7 @@ class FrostWebView @JvmOverloads constructor(
context.ctxCoroutine.launchMain {
val cookie = cookieDao.currentCookie(prefs) ?: return@launchMain
context.frostDownload(
- cookie,
+ cookie.cookie,
url,
userAgent,
contentDisposition,
diff --git a/app/src/main/play/en-US/whatsnew b/app/src/main/play/en-US/whatsnew
index 4474eebc..cd456c04 100644
--- a/app/src/main/play/en-US/whatsnew
+++ b/app/src/main/play/en-US/whatsnew
@@ -1,4 +1,5 @@
v2.4.4
* Lots of under the hood fixes
-* Fixed sharing \ No newline at end of file
+* Fixed sharing
+* Fix photo downloads for Android Q+ \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_image.xml b/app/src/main/res/layout/activity_image.xml
index 7d79cb74..6530dc4a 100644
--- a/app/src/main/res/layout/activity_image.xml
+++ b/app/src/main/res/layout/activity_image.xml
@@ -27,6 +27,37 @@
</com.pitchedapps.frost.views.DragFrame>
+ <LinearLayout
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:gravity="end"
+ android:orientation="horizontal"
+ android:paddingStart="8dp"
+ android:paddingTop="10dp"
+ android:paddingEnd="8dp"
+ android:paddingBottom="10dp">
+
+ <ImageView
+ android:id="@+id/error"
+ style="@style/Image.Icon" />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <ImageView
+ android:id="@+id/download"
+ style="@style/Image.Icon" />
+
+ <ImageView
+ android:id="@+id/share"
+ style="@style/Image.Icon" />
+
+ </LinearLayout>
+
<TextView
android:id="@+id/image_text"
android:layout_width="match_parent"
@@ -36,12 +67,4 @@
app:behavior_peekHeight="44dp"
app:layout_behavior="@string/bottom_sheet_behavior" />
- <com.google.android.material.floatingactionbutton.FloatingActionButton
- android:id="@+id/image_fab"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end|bottom"
- android:layout_margin="@dimen/kau_fab_margin"
- android:visibility="invisible" />
-
</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index b94f754f..fcacacc3 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -182,4 +182,13 @@
<item name="android:paddingEnd">@dimen/drawer_nav_horizontal_margins</item>
</style>
+ <style name="Image.Icon" parent="">
+ <item name="android:layout_width">36dp</item>
+ <item name="android:layout_height">36dp</item>
+ <item name="android:layout_marginStart">8dp</item>
+ <item name="android:padding">6dp</item>
+ <item name="android:layout_marginEnd">8dp</item>
+ <item name="android:background">?selectableItemBackgroundBorderless</item>
+ </style>
+
</resources>
diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml
index 99a48160..28ac4402 100644
--- a/app/src/main/res/xml/frost_changelog.xml
+++ b/app/src/main/res/xml/frost_changelog.xml
@@ -9,7 +9,7 @@
<version title="v2.4.4" />
<item text="Lots of under the hood fixes" />
<item text="Fixed sharing" />
- <item text="" />
+ <item text="Fix photo downloads for Android Q+" />
<item text="" />
<version title="v2.4.3" />
diff --git a/docs/Changelog.md b/docs/Changelog.md
index 4280780a..51ef89f8 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -3,6 +3,7 @@
## v2.4.4
* Lots of under the hood fixes
* Fixed sharing
+* Fix photo downloads for Android Q+
## v2.4.3
* Fix Android theme