aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt218
-rw-r--r--app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt8
-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/res/layout/activity_image.xml39
-rw-r--r--app/src/main/res/values/styles.xml9
6 files changed, 105 insertions, 173 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 629d5c9b..4abdd15f 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt
@@ -16,29 +16,23 @@
*/
package com.pitchedapps.frost.activities
-import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
-import android.net.Uri
-import android.os.Build
import android.os.Bundle
-import android.os.Environment
-import android.provider.MediaStore
import android.view.View
-import androidx.core.net.toFile
+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
@@ -54,7 +48,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.mikepenz.iconics.typeface.IIcon
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
-import com.pitchedapps.frost.BuildConfig
import com.pitchedapps.frost.R
import com.pitchedapps.frost.databinding.ActivityImageBinding
import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER
@@ -67,6 +60,7 @@ 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
@@ -83,9 +77,6 @@ import org.koin.android.ext.android.inject
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
@@ -102,28 +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
-
- internal var savedUri: Uri? = 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
@@ -152,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
@@ -166,13 +139,12 @@ class ImageActivity : KauBaseActivity() {
}
errorRef = e
e.logFrostEvent("Image load error")
- L.e(e) { "ASDF" }
with(binding) {
if (imageProgress.isVisible)
imageProgress.fadeOut()
}
- tempFile.delete()
- fabAction = FabStates.ERROR
+ tempFile?.delete()
+ binding.error.fadeIn()
}
override fun onCreate(savedInstanceState: Bundle?) {
@@ -190,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
}
@@ -229,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) {
@@ -275,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)
@@ -320,122 +305,29 @@ class ImageActivity : KauBaseActivity() {
}
}
- private fun newImageUri(mimeType: String): Uri? {
- val imageExtension = getImageExtension(mimeType)
- val timeStamp = SimpleDateFormat(TIME_FORMAT, Locale.getDefault()).format(Date())
- val imageFileName = "${IMG_TAG}_${timeStamp}"
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- val contentValues = ContentValues().apply {
- put(MediaStore.MediaColumns.DISPLAY_NAME, imageFileName)
- put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
- put(MediaStore.MediaColumns.RELATIVE_PATH, IMG_TAG)
- }
- return contentResolver.insert(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- contentValues
- )
- }
- val storageDir =
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
- val frostDir = File(storageDir, IMG_TAG)
- if (!frostDir.exists()) frostDir.mkdirs()
- val imageFile = File(frostDir, "$imageFileName.$imageExtension")
- return try {
- frostUriFromFile(imageFile)
- } catch (e: IllegalArgumentException) {
- if (BuildConfig.DEBUG) {
- L.e(e) { "Could not get uri for ${imageFile.absolutePath}" }
- }
- 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
- }
-
- // No temp file, download ourselves
- val response = cookie.requestBuilder()
- .url(trueImageUrl.await())
- .get()
- .call()
- .execute()
-
- if (!response.isSuccessful) {
- throw IOException("Unsuccessful response for image: ${response.peekBody(128).string()}")
- }
+ val imgExtension = getImageExtension(response.header("Content-Type")) ?: "jpg"
- imgExtension = getImageExtension(response.header("Content-Type")) ?: "jpg"
+ val body = response.body ?: throw IOException("Failed to retrieve image body")
- 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")
+ 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() {
@@ -472,12 +364,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.saveImage()
+ activity.binding.download.fadeOut()
+ }
+ }
},
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 3c713ec9..60c642f4 100644
--- a/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
+++ b/app/src/main/kotlin/com/pitchedapps/frost/utils/Downloader.kt
@@ -40,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,
@@ -52,7 +52,7 @@ fun Context.frostDownload(
}
fun Context.frostDownload(
- cookie: CookieEntity,
+ cookie: String?,
uri: Uri?,
userAgent: String = USER_AGENT,
contentDisposition: String? = null,
@@ -82,7 +82,9 @@ 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)
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/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>