From 8c343fd09cdf80cfbcdf49fd5cc72bec759478cf Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 19 Nov 2021 20:30:19 -0800 Subject: Test image activity --- app/build.gradle | 6 +- .../main/kotlin/com/pitchedapps/frost/FrostApp.kt | 5 + .../pitchedapps/frost/activities/ImageActivity.kt | 28 +- .../frost/activities/ImageActivityOrig.kt | 438 +++++++++++++++++++++ app/src/main/res/layout/activity_image.xml | 2 + app/src/main/res/layout/activity_image_2.xml | 72 ++++ 6 files changed, 537 insertions(+), 14 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivityOrig.kt create mode 100644 app/src/main/res/layout/activity_image_2.xml (limited to 'app') diff --git a/app/build.gradle b/app/build.gradle index 8779069a..ba0b2018 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -274,6 +274,10 @@ dependencies { implementation "androidx.biometric:biometric:${Versions.andxBiometric}" + implementation 'com.github.piasy:BigImageViewer:1.8.1' + implementation 'com.github.piasy:GlideImageLoader:1.8.1' + implementation 'com.github.piasy:GlideImageViewFactory:1.8.1' + implementation kau.Dependencies.hilt kapt kau.Dependencies.hiltCompiler testImplementation kau.Dependencies.hiltTest @@ -314,7 +318,7 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:${Versions.okhttp}" androidTestImplementation "com.squareup.okhttp3:mockwebserver:${Versions.okhttp}" - implementation "com.davemorrissey.labs:subsampling-scale-image-view:${Versions.scaleImageView}" +// implementation "com.davemorrissey.labs:subsampling-scale-image-view:${Versions.scaleImageView}" implementation "androidx.room:room-ktx:${Versions.room}" implementation "androidx.room:room-runtime:${Versions.room}" diff --git a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt index f5ef0b74..d444d225 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/FrostApp.kt @@ -22,8 +22,11 @@ import android.os.Bundle import android.util.Log import ca.allanwang.kau.logging.KL import ca.allanwang.kau.utils.buildIsLollipopAndUp +import com.github.piasy.biv.BigImageViewer +import com.github.piasy.biv.loader.glide.GlideImageLoader import com.pitchedapps.frost.db.CookieDao import com.pitchedapps.frost.db.NotificationDao +import com.pitchedapps.frost.facebook.requests.httpClient import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.services.scheduleNotificationsFromPrefs @@ -66,6 +69,8 @@ class FrostApp : Application() { scheduleNotificationsFromPrefs(prefs) + BigImageViewer.initialize(GlideImageLoader.with(this, httpClient)) + if (BuildConfig.DEBUG) { registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityPaused(activity: Activity) {} 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 b8a71fa1..b5e41751 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.graphics.Color +import android.net.Uri import android.os.Bundle import android.view.View import android.widget.ImageView @@ -42,14 +43,12 @@ import ca.allanwang.kau.utils.tint import ca.allanwang.kau.utils.toast import ca.allanwang.kau.utils.withAlpha import ca.allanwang.kau.utils.withMinAlpha -import com.davemorrissey.labs.subscaleview.ImageSource -import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView 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.R -import com.pitchedapps.frost.databinding.ActivityImageBinding +import com.pitchedapps.frost.databinding.ActivityImage2Binding import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER import com.pitchedapps.frost.facebook.get import com.pitchedapps.frost.facebook.requests.call @@ -134,7 +133,7 @@ class ImageActivity : KauBaseActivity() { "${abs(FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode() ?: 0)}_${abs(imageUrl.hashCode())}" } - lateinit var binding: ActivityImageBinding + lateinit var binding: ActivityImage2Binding private var bottomBehavior: BottomSheetBehavior? = null private val baseBackgroundColor: Int @@ -170,19 +169,22 @@ class ImageActivity : KauBaseActivity() { L.v { "Launching with true url $result" } result } - binding = ActivityImageBinding.inflate(layoutInflater) + binding = ActivityImage2Binding.inflate(layoutInflater) setContentView(binding.root) binding.init() launch(CoroutineExceptionHandler { _, throwable -> loadError(throwable) }) { val tempFile = downloadTempImage() this@ImageActivity.tempFile = tempFile binding.imageProgress.fadeOut() - binding.imagePhoto.setImage(ImageSource.uri(frostUriFromFile(tempFile))) +// binding.imagePhoto.setImageURI(frostUriFromFile(tempFile)) +// Glide.with(binding.imagePhoto).asFile().load(trueImageUrl) + binding.imagePhoto.showImage(Uri.parse(trueImageUrl.await())) +// binding.imagePhoto.setImage(ImageSource.uri(frostUriFromFile(tempFile))) binding.imagePhoto.animate().alpha(1f).scaleXY(1f).start() } } - private fun ActivityImageBinding.init() { + private fun ActivityImage2Binding.init() { imageContainer.setBackgroundColor(baseBackgroundColor) toolbar.setBackgroundColor(baseBackgroundColor) this@ImageActivity.imageText.also { text -> @@ -226,12 +228,12 @@ class ImageActivity : KauBaseActivity() { share.apply { setState(FabStates.SHARE) } - imagePhoto.setOnImageEventListener(object : - SubsamplingScaleImageView.DefaultOnImageEventListener() { - override fun onImageLoadError(e: Exception) { - loadError(e) - } - }) +// imagePhoto.setOnImageEventListener(object : +// SubsamplingScaleImageView.DefaultOnImageEventListener() { +// override fun onImageLoadError(e: Exception) { +// loadError(e) +// } +// }) activityThemer.setFrostColors { themeWindow = false } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivityOrig.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivityOrig.kt new file mode 100644 index 00000000..3f6a90fe --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivityOrig.kt @@ -0,0 +1,438 @@ +/* + * Copyright 2018 Allan Wang + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.pitchedapps.frost.activities + +import android.content.Context +import android.content.Intent +import android.content.res.ColorStateList +import android.graphics.Color +import android.os.Bundle +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.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 +import ca.allanwang.kau.utils.scaleXY +import ca.allanwang.kau.utils.setIcon +import ca.allanwang.kau.utils.tint +import ca.allanwang.kau.utils.toast +import ca.allanwang.kau.utils.withAlpha +import ca.allanwang.kau.utils.withMinAlpha +import com.davemorrissey.labs.subscaleview.ImageSource +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.R +import com.pitchedapps.frost.databinding.ActivityImageBinding +import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER +import com.pitchedapps.frost.facebook.get +import com.pitchedapps.frost.facebook.requests.call +import com.pitchedapps.frost.facebook.requests.getFullSizedImageUrl +import com.pitchedapps.frost.facebook.requests.requestBuilder +import com.pitchedapps.frost.injectors.ThemeProvider +import com.pitchedapps.frost.prefs.Prefs +import com.pitchedapps.frost.services.LocalService +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.ActivityThemer +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 dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.max + +/** + * Created by Allan Wang on 2017-07-15. + */ +@AndroidEntryPoint +class ImageActivityOrig : KauBaseActivity() { + + @Inject + lateinit var activityThemer: ActivityThemer + + @Inject + lateinit var prefs: Prefs + + @Inject + lateinit var themeProvider: ThemeProvider + + @Volatile + internal var errorRef: Throwable? = null + + /** + * Reference to the temporary file path + */ + internal var tempFile: File? = null + + private lateinit var dragHelper: ViewDragHelper + + 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" + const val PURGE_TIME: Long = 10 * 60 * 1000 // 10 min block + private val L = KauLoggerExtension("Image", com.pitchedapps.frost.utils.L) + + fun cacheDir(context: Context): File = + File(context.cacheDir, IMAGE_FOLDER) + } + + private val cookie: String? by lazy { intent.getStringExtra(ARG_COOKIE) } + + val imageUrl: String by lazy { intent.getStringExtra(ARG_IMAGE_URL)?.trim('"') ?: "" } + + private lateinit var trueImageUrl: Deferred + + private val imageText: String? by lazy { intent.getStringExtra(ARG_TEXT) } + + // a unique image identifier based on the id (if it exists), and its hash + private val imageHash: String by lazy { + "${abs(FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode() ?: 0)}_${abs(imageUrl.hashCode())}" + } + + lateinit var binding: ActivityImageBinding + private var bottomBehavior: BottomSheetBehavior? = null + + private val baseBackgroundColor: Int + get() = if (prefs.blackMediaBg) Color.BLACK + else themeProvider.bgColor.withMinAlpha(235) + + private fun loadError(e: Throwable) { + if (e.message?.contains("") == true) { + applicationContext.toast(R.string.image_not_found) + finish() + return + } + errorRef = e + e.logFrostEvent("Image load error") + with(binding) { + if (imageProgress.isVisible) + imageProgress.fadeOut() + } + tempFile?.delete() + binding.error.fadeIn() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (imageUrl.isEmpty()) { + return finish() + } + L.i { "Displaying image" } + trueImageUrl = async(Dispatchers.IO) { + val result = if (!imageUrl.isIndirectImageUrl) imageUrl + else cookie?.getFullSizedImageUrl(imageUrl) ?: imageUrl + if (result != imageUrl) + L.v { "Launching with true url $result" } + result + } + binding = ActivityImageBinding.inflate(layoutInflater) + setContentView(binding.root) + binding.init() + launch(CoroutineExceptionHandler { _, throwable -> loadError(throwable) }) { + val tempFile = downloadTempImage() + this@ImageActivityOrig.tempFile = tempFile + binding.imageProgress.fadeOut() +// binding.imagePhoto.setImageURI(frostUriFromFile(tempFile)) + binding.imagePhoto.setImage(ImageSource.uri(frostUriFromFile(tempFile))) + binding.imagePhoto.animate().alpha(1f).scaleXY(1f).start() + } + } + + private fun ActivityImageBinding.init() { + imageContainer.setBackgroundColor(baseBackgroundColor) + toolbar.setBackgroundColor(baseBackgroundColor) + this@ImageActivityOrig.imageText.also { text -> + if (text.isNullOrBlank()) { + imageText.gone() + } else { + imageText.setTextColor(if (prefs.blackMediaBg) Color.WHITE else themeProvider.textColor) + imageText.setBackgroundColor( + baseBackgroundColor.colorToForeground(0.2f).withAlpha(255) + ) + imageText.text = text + bottomBehavior = BottomSheetBehavior.from(imageText).apply { + addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + override fun onSlide(bottomSheet: View, slideOffset: Float) { + imageText.alpha = slideOffset / 2 + 0.5f + } + + override fun onStateChanged(bottomSheet: View, newState: Int) { + // No op + } + }) + } + imageText.bringToFront() + } + } + val foregroundTint = if (prefs.blackMediaBg) Color.WHITE else themeProvider.accentColor + + fun ImageView.setState(state: FabStatesOrig) { + setIcon(state.iicon, color = foregroundTint, sizeDp = 24) + setOnClickListener { state.onClick(this@ImageActivityOrig) } + } + + imageProgress.tint(foregroundTint) + error.apply { + invisible() + setState(FabStatesOrig.ERROR) + } + download.apply { + setState(FabStatesOrig.DOWNLOAD) + } + share.apply { + setState(FabStatesOrig.SHARE) + } +// imagePhoto.setOnImageEventListener(object : +// SubsamplingScaleImageView.DefaultOnImageEventListener() { +// override fun onImageLoadError(e: Exception) { +// loadError(e) +// } +// }) + activityThemer.setFrostColors { + themeWindow = false + } + dragHelper = ViewDragHelper.create(imageDrag, ViewDragCallback()).apply { + setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP or ViewDragHelper.EDGE_BOTTOM) + } + imageDrag.dragHelper = dragHelper + imageDrag.viewToIgnore = imageText + } + + private inner class ViewDragCallback : ViewDragHelper.Callback() { + private var scrollPercent: Float = 0f + private var scrollThreshold = 0.5f + private var scrollToTop = false + + override fun tryCaptureView(view: View, i: Int): Boolean { + L.d { "Try capture ${view.id} $i ${binding.imagePhoto.id} ${binding.imageText.id}" } + return view === binding.imagePhoto + } + + override fun getViewHorizontalDragRange(child: View): Int = 0 + + override fun getViewVerticalDragRange(child: View): Int = child.height + + override fun onViewPositionChanged( + changedView: View, + left: Int, + top: Int, + dx: Int, + dy: Int + ) { + super.onViewPositionChanged(changedView, left, top, dx, dy) + with(binding) { + // make sure that we are using the proper axis + scrollPercent = abs(top.toFloat() / imageContainer.height) + scrollToTop = top < 0 + val multiplier = max(1f - scrollPercent, 0f) + + toolbar.alpha = multiplier + bottomBehavior?.also { + imageText.alpha = + multiplier * (if (it.state == BottomSheetBehavior.STATE_COLLAPSED) 0.5f else 1f) + } + imageContainer.setBackgroundColor(baseBackgroundColor.adjustAlpha(multiplier)) + + if (scrollPercent >= 1) { + if (!isFinishing) { + finish() + overridePendingTransition(0, 0) + } + } + } + } + + override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { + val overScrolled = scrollPercent > scrollThreshold + val maxOffset = releasedChild.height + 10 + val finalTop = when { + scrollToTop && (overScrolled || yvel < -dragHelper.minVelocity) -> -maxOffset + !scrollToTop && (overScrolled || yvel > dragHelper.minVelocity) -> maxOffset + else -> 0 + } + dragHelper.settleCapturedViewAt(0, finalTop) + binding.imageDrag.invalidate() + } + + override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int = 0 + + override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int = top + } + + private fun getImageExtension(type: String?): String? { + if (type?.startsWith("image/") != true) { + return null + } + return when (type.substring(6)) { + "jpeg" -> "jpg" + "png" -> "png" + "gif" -> "gif" + else -> null + } + } + + @Throws(IOException::class) + private suspend fun downloadTempImage(): File = withContext(Dispatchers.IO) { + + // We assume all images are jpg + // Activity launcher may be able to provide specifics, but this beats sending a request + // just to get the content header + val file = File(cacheDir(this@ImageActivityOrig), "$imageHash.jpg") + + 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 file + } + + val response = cookie.requestBuilder() + .url(trueImageUrl.await()) + .get() + .call() + .execute() + + if (!response.isSuccessful) { + throw IOException("Unsuccessful response for image: ${response.peekBody(128).string()}") + } + + val body = response.body ?: throw IOException("Failed to retrieve image body") + file.copyFromInputStream(body.byteStream()) + file + } + + internal suspend fun saveImage() { + frostDownload(cookie = cookie, url = trueImageUrl.await()) + } + + override fun onDestroy() { + LocalService.schedule(this, LocalService.Flag.PURGE_IMAGE) + super.onDestroy() + } +} + +internal enum class FabStatesOrig( + val iicon: IIcon, + val iconColorProvider: (ThemeProvider) -> Int = { it.iconColor }, + val backgroundTint: Int = Int.MAX_VALUE +) { + ERROR(GoogleMaterial.Icon.gmd_error, { Color.WHITE }, Color.RED) { + override fun onClick(activity: ImageActivityOrig) { + val err = + activity.errorRef?.takeIf { it !is FileNotFoundException && it.message != "Image failed to decode using JPEG decoder" } + ?: return + activity.materialDialog { + title(R.string.kau_error) + message(text = err.message ?: err.javaClass.name) + } + } + }, + NOTHING(GoogleMaterial.Icon.gmd_adjust) { + override fun onClick(activity: ImageActivityOrig) {} + }, + DOWNLOAD(GoogleMaterial.Icon.gmd_file_download) { + override fun onClick(activity: ImageActivityOrig) { + activity.launch { + activity.binding.download.fadeOut() + activity.saveImage() + } + } + }, + SHARE(GoogleMaterial.Icon.gmd_share) { + override fun onClick(activity: ImageActivityOrig) { + val file = activity.tempFile ?: return + try { + val photoURI = activity.frostUriFromFile(file) + val intent = Intent(Intent.ACTION_SEND).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + putExtra(Intent.EXTRA_STREAM, photoURI) + type = "image/png" + } + activity.startActivity(intent) + } catch (e: Exception) { + activity.errorRef = e + e.logFrostEvent("Image share failed") + activity.frostSnackbar(R.string.image_share_failed, activity.themeProvider) + } + } + }; + + /** + * Change the fab look + * If it's in view, give it some animations + * + * TODO investigate what is wrong with fadeScaleTransition + * + * https://github.com/AllanWang/KAU/issues/184 + * + */ + fun update(fab: FloatingActionButton, themeProvider: ThemeProvider) { + val tint = + if (backgroundTint != Int.MAX_VALUE) backgroundTint else themeProvider.accentColor + val iconColor = iconColorProvider(themeProvider) + if (fab.isHidden) { + fab.setIcon(iicon, color = iconColor) + fab.backgroundTintList = ColorStateList.valueOf(tint) + fab.show() + } else { + fab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { + override fun onHidden(fab: FloatingActionButton) { + fab.setIcon(iicon, color = iconColor) + fab.show() + } + }) + } + } + + abstract fun onClick(activity: ImageActivityOrig) +} diff --git a/app/src/main/res/layout/activity_image.xml b/app/src/main/res/layout/activity_image.xml index 6530dc4a..7e19dabf 100644 --- a/app/src/main/res/layout/activity_image.xml +++ b/app/src/main/res/layout/activity_image.xml @@ -17,6 +17,8 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3 From 4799eebdc642f740b55aad2e0d9a3624fc11c53f Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 19 Nov 2021 21:09:43 -0800 Subject: Snapshot work --- .idea/compiler.xml | 4 ++- .../pitchedapps/frost/activities/ImageActivity.kt | 30 ++++++++++++---------- .../com/pitchedapps/frost/facebook/FbCookie.kt | 2 +- .../pitchedapps/frost/facebook/requests/Images.kt | 8 ++++-- .../frost/web/FrostUrlOverlayValidator.kt | 10 ++++---- settings.gradle.kts | 3 +++ 6 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 settings.gradle.kts (limited to 'app') diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8a..fcfdfd20 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,8 @@ - + + + \ No newline at end of file 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 b5e41751..6d618347 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -43,6 +43,7 @@ import ca.allanwang.kau.utils.tint import ca.allanwang.kau.utils.toast import ca.allanwang.kau.utils.withAlpha import ca.allanwang.kau.utils.withMinAlpha +import com.github.piasy.biv.view.ImageShownCallback import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.floatingactionbutton.FloatingActionButton import com.mikepenz.iconics.typeface.IIcon @@ -166,7 +167,9 @@ class ImageActivity : KauBaseActivity() { val result = if (!imageUrl.isIndirectImageUrl) imageUrl else cookie?.getFullSizedImageUrl(imageUrl) ?: imageUrl if (result != imageUrl) - L.v { "Launching with true url $result" } + L.v { "Launching image with true url $result" } + else + L.v { "Launching image with url $result" } result } binding = ActivityImage2Binding.inflate(layoutInflater) @@ -175,15 +178,22 @@ class ImageActivity : KauBaseActivity() { launch(CoroutineExceptionHandler { _, throwable -> loadError(throwable) }) { val tempFile = downloadTempImage() this@ImageActivity.tempFile = tempFile - binding.imageProgress.fadeOut() -// binding.imagePhoto.setImageURI(frostUriFromFile(tempFile)) -// Glide.with(binding.imagePhoto).asFile().load(trueImageUrl) - binding.imagePhoto.showImage(Uri.parse(trueImageUrl.await())) -// binding.imagePhoto.setImage(ImageSource.uri(frostUriFromFile(tempFile))) - binding.imagePhoto.animate().alpha(1f).scaleXY(1f).start() + binding.showImage(trueImageUrl.await()) } } + private fun ActivityImage2Binding.showImage(url: String) { + imageProgress.fadeOut() + imagePhoto.showImage(Uri.parse(url)) + imagePhoto.setImageShownCallback(object : ImageShownCallback { + override fun onThumbnailShown() {} + + override fun onMainImageShown() { + imagePhoto.animate().alpha(1f).scaleXY(1f).start() + } + }) + } + private fun ActivityImage2Binding.init() { imageContainer.setBackgroundColor(baseBackgroundColor) toolbar.setBackgroundColor(baseBackgroundColor) @@ -228,12 +238,6 @@ class ImageActivity : KauBaseActivity() { share.apply { setState(FabStates.SHARE) } -// imagePhoto.setOnImageEventListener(object : -// SubsamplingScaleImageView.DefaultOnImageEventListener() { -// override fun onImageLoadError(e: Exception) { -// loadError(e) -// } -// }) activityThemer.setFrostColors { themeWindow = false } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt index 1a714374..ab041adc 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/FbCookie.kt @@ -60,7 +60,7 @@ class FbCookie @Inject internal constructor( * Note that this is a synchronized call */ val webCookie: String? - get() = CookieManager.getInstance().getCookie(FB_COOKIE_DOMAIN) + get() = CookieManager.getInstance().getCookie(HTTPS_FACEBOOK_COM) val messengerCookie: String? get() = CookieManager.getInstance().getCookie(HTTPS_MESSENGER_COM) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt index 70f911a8..37f8fab3 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt @@ -31,12 +31,15 @@ import kotlinx.coroutines.withTimeout /** * Attempts to get the fbcdn url of the supplied image redirect url */ -suspend fun String.getFullSizedImageUrl(url: String, timeout: Long = 3000): String? = - withContext(Dispatchers.IO) { +suspend fun String.getFullSizedImageUrl(url: String, timeout: Long = 3000): String? { + L.v { "Image full size 1 from $url" } + return withContext(Dispatchers.IO) { try { withTimeout(timeout) { + L.v { "Image full size from $url" } val redirect = requestBuilder().url(url).get().call() .execute().body?.string() ?: return@withTimeout null + L.v { "Image full size from redirect $redirect" } FB_REDIRECT_URL_MATCHER.find(redirect)[1]?.formattedFbUrl } } catch (e: Exception) { @@ -44,3 +47,4 @@ suspend fun String.getFullSizedImageUrl(url: String, timeout: Long = 3000): Stri null } } +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt index 94300348..a0842267 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/web/FrostUrlOverlayValidator.kt @@ -61,16 +61,16 @@ fun FrostWebView.requestWebOverlay(url: String): Boolean { context.runOnUiThread { context.showVideo(url) } return true } - if (url.isImageUrl) { - L.d { "Found fb image" } - context.launchImageActivity(url) - return true - } if (url.isIndirectImageUrl) { L.d { "Found indirect fb image" } context.launchImageActivity(url, cookie = fbCookie.webCookie) return true } + if (url.isImageUrl) { + L.d { "Found fb image" } + context.launchImageActivity(url) + return true + } if (!url.isIndependent) { L.d { "Forbid overlay switch" } return false diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..270f7597 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,3 @@ +include(":app", ":gradle-plugin") + +project(":gradle-plugin").projectDir = file("buildSrc") \ No newline at end of file -- cgit v1.2.3 From 195322ce95df6bfed533f675f694cc00d78acc02 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 19 Nov 2021 21:28:27 -0800 Subject: Replace tempFile with glide ref --- .../pitchedapps/frost/activities/ImageActivity.kt | 27 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'app') 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 6d618347..c381591e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -43,6 +43,7 @@ import ca.allanwang.kau.utils.tint import ca.allanwang.kau.utils.toast import ca.allanwang.kau.utils.withAlpha import ca.allanwang.kau.utils.withMinAlpha +import com.github.piasy.biv.loader.ImageLoader import com.github.piasy.biv.view.ImageShownCallback import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.floatingactionbutton.FloatingActionButton @@ -102,7 +103,7 @@ class ImageActivity : KauBaseActivity() { /** * Reference to the temporary file path */ - internal var tempFile: File? = null + internal val tempFile: File? get() = binding.imagePhoto.currentImageFile private lateinit var dragHelper: ViewDragHelper @@ -176,19 +177,19 @@ class ImageActivity : KauBaseActivity() { setContentView(binding.root) binding.init() launch(CoroutineExceptionHandler { _, throwable -> loadError(throwable) }) { - val tempFile = downloadTempImage() - this@ImageActivity.tempFile = tempFile +// val tempFile = downloadTempImage() +// this@ImageActivity.tempFile = tempFile binding.showImage(trueImageUrl.await()) } } private fun ActivityImage2Binding.showImage(url: String) { - imageProgress.fadeOut() imagePhoto.showImage(Uri.parse(url)) imagePhoto.setImageShownCallback(object : ImageShownCallback { override fun onThumbnailShown() {} override fun onMainImageShown() { + imageProgress.fadeOut() imagePhoto.animate().alpha(1f).scaleXY(1f).start() } }) @@ -227,6 +228,24 @@ class ImageActivity : KauBaseActivity() { setOnClickListener { state.onClick(this@ImageActivity) } } + imagePhoto.setImageLoaderCallback(object : ImageLoader.Callback { + override fun onCacheHit(imageType: Int, image: File?) {} + + override fun onCacheMiss(imageType: Int, image: File?) {} + + override fun onStart() {} + + override fun onProgress(progress: Int) {} + + override fun onFinish() {} + + override fun onSuccess(image: File) {} + + override fun onFail(error: Exception) { + loadError(error) + } + }) + imageProgress.tint(foregroundTint) error.apply { invisible() -- cgit v1.2.3 From 2ee9bffe2a0a39f5fe1d44f82d9c5735b69803c6 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 19 Nov 2021 21:30:35 -0800 Subject: Delete extra copies --- .../pitchedapps/frost/activities/ImageActivity.kt | 10 +- .../frost/activities/ImageActivityOrig.kt | 438 --------------------- app/src/main/res/layout/activity_image.xml | 2 +- app/src/main/res/layout/activity_image_2.xml | 72 ---- 4 files changed, 6 insertions(+), 516 deletions(-) delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivityOrig.kt delete mode 100644 app/src/main/res/layout/activity_image_2.xml (limited to 'app') 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 c381591e..da991236 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -50,7 +50,7 @@ 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.R -import com.pitchedapps.frost.databinding.ActivityImage2Binding +import com.pitchedapps.frost.databinding.ActivityImageBinding import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER import com.pitchedapps.frost.facebook.get import com.pitchedapps.frost.facebook.requests.call @@ -135,7 +135,7 @@ class ImageActivity : KauBaseActivity() { "${abs(FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode() ?: 0)}_${abs(imageUrl.hashCode())}" } - lateinit var binding: ActivityImage2Binding + lateinit var binding: ActivityImageBinding private var bottomBehavior: BottomSheetBehavior? = null private val baseBackgroundColor: Int @@ -173,7 +173,7 @@ class ImageActivity : KauBaseActivity() { L.v { "Launching image with url $result" } result } - binding = ActivityImage2Binding.inflate(layoutInflater) + binding = ActivityImageBinding.inflate(layoutInflater) setContentView(binding.root) binding.init() launch(CoroutineExceptionHandler { _, throwable -> loadError(throwable) }) { @@ -183,7 +183,7 @@ class ImageActivity : KauBaseActivity() { } } - private fun ActivityImage2Binding.showImage(url: String) { + private fun ActivityImageBinding.showImage(url: String) { imagePhoto.showImage(Uri.parse(url)) imagePhoto.setImageShownCallback(object : ImageShownCallback { override fun onThumbnailShown() {} @@ -195,7 +195,7 @@ class ImageActivity : KauBaseActivity() { }) } - private fun ActivityImage2Binding.init() { + private fun ActivityImageBinding.init() { imageContainer.setBackgroundColor(baseBackgroundColor) toolbar.setBackgroundColor(baseBackgroundColor) this@ImageActivity.imageText.also { text -> diff --git a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivityOrig.kt b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivityOrig.kt deleted file mode 100644 index 3f6a90fe..00000000 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivityOrig.kt +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright 2018 Allan Wang - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.pitchedapps.frost.activities - -import android.content.Context -import android.content.Intent -import android.content.res.ColorStateList -import android.graphics.Color -import android.os.Bundle -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.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 -import ca.allanwang.kau.utils.scaleXY -import ca.allanwang.kau.utils.setIcon -import ca.allanwang.kau.utils.tint -import ca.allanwang.kau.utils.toast -import ca.allanwang.kau.utils.withAlpha -import ca.allanwang.kau.utils.withMinAlpha -import com.davemorrissey.labs.subscaleview.ImageSource -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.R -import com.pitchedapps.frost.databinding.ActivityImageBinding -import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER -import com.pitchedapps.frost.facebook.get -import com.pitchedapps.frost.facebook.requests.call -import com.pitchedapps.frost.facebook.requests.getFullSizedImageUrl -import com.pitchedapps.frost.facebook.requests.requestBuilder -import com.pitchedapps.frost.injectors.ThemeProvider -import com.pitchedapps.frost.prefs.Prefs -import com.pitchedapps.frost.services.LocalService -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.ActivityThemer -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 dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import javax.inject.Inject -import kotlin.math.abs -import kotlin.math.max - -/** - * Created by Allan Wang on 2017-07-15. - */ -@AndroidEntryPoint -class ImageActivityOrig : KauBaseActivity() { - - @Inject - lateinit var activityThemer: ActivityThemer - - @Inject - lateinit var prefs: Prefs - - @Inject - lateinit var themeProvider: ThemeProvider - - @Volatile - internal var errorRef: Throwable? = null - - /** - * Reference to the temporary file path - */ - internal var tempFile: File? = null - - private lateinit var dragHelper: ViewDragHelper - - 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" - const val PURGE_TIME: Long = 10 * 60 * 1000 // 10 min block - private val L = KauLoggerExtension("Image", com.pitchedapps.frost.utils.L) - - fun cacheDir(context: Context): File = - File(context.cacheDir, IMAGE_FOLDER) - } - - private val cookie: String? by lazy { intent.getStringExtra(ARG_COOKIE) } - - val imageUrl: String by lazy { intent.getStringExtra(ARG_IMAGE_URL)?.trim('"') ?: "" } - - private lateinit var trueImageUrl: Deferred - - private val imageText: String? by lazy { intent.getStringExtra(ARG_TEXT) } - - // a unique image identifier based on the id (if it exists), and its hash - private val imageHash: String by lazy { - "${abs(FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode() ?: 0)}_${abs(imageUrl.hashCode())}" - } - - lateinit var binding: ActivityImageBinding - private var bottomBehavior: BottomSheetBehavior? = null - - private val baseBackgroundColor: Int - get() = if (prefs.blackMediaBg) Color.BLACK - else themeProvider.bgColor.withMinAlpha(235) - - private fun loadError(e: Throwable) { - if (e.message?.contains("") == true) { - applicationContext.toast(R.string.image_not_found) - finish() - return - } - errorRef = e - e.logFrostEvent("Image load error") - with(binding) { - if (imageProgress.isVisible) - imageProgress.fadeOut() - } - tempFile?.delete() - binding.error.fadeIn() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (imageUrl.isEmpty()) { - return finish() - } - L.i { "Displaying image" } - trueImageUrl = async(Dispatchers.IO) { - val result = if (!imageUrl.isIndirectImageUrl) imageUrl - else cookie?.getFullSizedImageUrl(imageUrl) ?: imageUrl - if (result != imageUrl) - L.v { "Launching with true url $result" } - result - } - binding = ActivityImageBinding.inflate(layoutInflater) - setContentView(binding.root) - binding.init() - launch(CoroutineExceptionHandler { _, throwable -> loadError(throwable) }) { - val tempFile = downloadTempImage() - this@ImageActivityOrig.tempFile = tempFile - binding.imageProgress.fadeOut() -// binding.imagePhoto.setImageURI(frostUriFromFile(tempFile)) - binding.imagePhoto.setImage(ImageSource.uri(frostUriFromFile(tempFile))) - binding.imagePhoto.animate().alpha(1f).scaleXY(1f).start() - } - } - - private fun ActivityImageBinding.init() { - imageContainer.setBackgroundColor(baseBackgroundColor) - toolbar.setBackgroundColor(baseBackgroundColor) - this@ImageActivityOrig.imageText.also { text -> - if (text.isNullOrBlank()) { - imageText.gone() - } else { - imageText.setTextColor(if (prefs.blackMediaBg) Color.WHITE else themeProvider.textColor) - imageText.setBackgroundColor( - baseBackgroundColor.colorToForeground(0.2f).withAlpha(255) - ) - imageText.text = text - bottomBehavior = BottomSheetBehavior.from(imageText).apply { - addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { - override fun onSlide(bottomSheet: View, slideOffset: Float) { - imageText.alpha = slideOffset / 2 + 0.5f - } - - override fun onStateChanged(bottomSheet: View, newState: Int) { - // No op - } - }) - } - imageText.bringToFront() - } - } - val foregroundTint = if (prefs.blackMediaBg) Color.WHITE else themeProvider.accentColor - - fun ImageView.setState(state: FabStatesOrig) { - setIcon(state.iicon, color = foregroundTint, sizeDp = 24) - setOnClickListener { state.onClick(this@ImageActivityOrig) } - } - - imageProgress.tint(foregroundTint) - error.apply { - invisible() - setState(FabStatesOrig.ERROR) - } - download.apply { - setState(FabStatesOrig.DOWNLOAD) - } - share.apply { - setState(FabStatesOrig.SHARE) - } -// imagePhoto.setOnImageEventListener(object : -// SubsamplingScaleImageView.DefaultOnImageEventListener() { -// override fun onImageLoadError(e: Exception) { -// loadError(e) -// } -// }) - activityThemer.setFrostColors { - themeWindow = false - } - dragHelper = ViewDragHelper.create(imageDrag, ViewDragCallback()).apply { - setEdgeTrackingEnabled(ViewDragHelper.EDGE_TOP or ViewDragHelper.EDGE_BOTTOM) - } - imageDrag.dragHelper = dragHelper - imageDrag.viewToIgnore = imageText - } - - private inner class ViewDragCallback : ViewDragHelper.Callback() { - private var scrollPercent: Float = 0f - private var scrollThreshold = 0.5f - private var scrollToTop = false - - override fun tryCaptureView(view: View, i: Int): Boolean { - L.d { "Try capture ${view.id} $i ${binding.imagePhoto.id} ${binding.imageText.id}" } - return view === binding.imagePhoto - } - - override fun getViewHorizontalDragRange(child: View): Int = 0 - - override fun getViewVerticalDragRange(child: View): Int = child.height - - override fun onViewPositionChanged( - changedView: View, - left: Int, - top: Int, - dx: Int, - dy: Int - ) { - super.onViewPositionChanged(changedView, left, top, dx, dy) - with(binding) { - // make sure that we are using the proper axis - scrollPercent = abs(top.toFloat() / imageContainer.height) - scrollToTop = top < 0 - val multiplier = max(1f - scrollPercent, 0f) - - toolbar.alpha = multiplier - bottomBehavior?.also { - imageText.alpha = - multiplier * (if (it.state == BottomSheetBehavior.STATE_COLLAPSED) 0.5f else 1f) - } - imageContainer.setBackgroundColor(baseBackgroundColor.adjustAlpha(multiplier)) - - if (scrollPercent >= 1) { - if (!isFinishing) { - finish() - overridePendingTransition(0, 0) - } - } - } - } - - override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { - val overScrolled = scrollPercent > scrollThreshold - val maxOffset = releasedChild.height + 10 - val finalTop = when { - scrollToTop && (overScrolled || yvel < -dragHelper.minVelocity) -> -maxOffset - !scrollToTop && (overScrolled || yvel > dragHelper.minVelocity) -> maxOffset - else -> 0 - } - dragHelper.settleCapturedViewAt(0, finalTop) - binding.imageDrag.invalidate() - } - - override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int = 0 - - override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int = top - } - - private fun getImageExtension(type: String?): String? { - if (type?.startsWith("image/") != true) { - return null - } - return when (type.substring(6)) { - "jpeg" -> "jpg" - "png" -> "png" - "gif" -> "gif" - else -> null - } - } - - @Throws(IOException::class) - private suspend fun downloadTempImage(): File = withContext(Dispatchers.IO) { - - // We assume all images are jpg - // Activity launcher may be able to provide specifics, but this beats sending a request - // just to get the content header - val file = File(cacheDir(this@ImageActivityOrig), "$imageHash.jpg") - - 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 file - } - - val response = cookie.requestBuilder() - .url(trueImageUrl.await()) - .get() - .call() - .execute() - - if (!response.isSuccessful) { - throw IOException("Unsuccessful response for image: ${response.peekBody(128).string()}") - } - - val body = response.body ?: throw IOException("Failed to retrieve image body") - file.copyFromInputStream(body.byteStream()) - file - } - - internal suspend fun saveImage() { - frostDownload(cookie = cookie, url = trueImageUrl.await()) - } - - override fun onDestroy() { - LocalService.schedule(this, LocalService.Flag.PURGE_IMAGE) - super.onDestroy() - } -} - -internal enum class FabStatesOrig( - val iicon: IIcon, - val iconColorProvider: (ThemeProvider) -> Int = { it.iconColor }, - val backgroundTint: Int = Int.MAX_VALUE -) { - ERROR(GoogleMaterial.Icon.gmd_error, { Color.WHITE }, Color.RED) { - override fun onClick(activity: ImageActivityOrig) { - val err = - activity.errorRef?.takeIf { it !is FileNotFoundException && it.message != "Image failed to decode using JPEG decoder" } - ?: return - activity.materialDialog { - title(R.string.kau_error) - message(text = err.message ?: err.javaClass.name) - } - } - }, - NOTHING(GoogleMaterial.Icon.gmd_adjust) { - override fun onClick(activity: ImageActivityOrig) {} - }, - DOWNLOAD(GoogleMaterial.Icon.gmd_file_download) { - override fun onClick(activity: ImageActivityOrig) { - activity.launch { - activity.binding.download.fadeOut() - activity.saveImage() - } - } - }, - SHARE(GoogleMaterial.Icon.gmd_share) { - override fun onClick(activity: ImageActivityOrig) { - val file = activity.tempFile ?: return - try { - val photoURI = activity.frostUriFromFile(file) - val intent = Intent(Intent.ACTION_SEND).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - putExtra(Intent.EXTRA_STREAM, photoURI) - type = "image/png" - } - activity.startActivity(intent) - } catch (e: Exception) { - activity.errorRef = e - e.logFrostEvent("Image share failed") - activity.frostSnackbar(R.string.image_share_failed, activity.themeProvider) - } - } - }; - - /** - * Change the fab look - * If it's in view, give it some animations - * - * TODO investigate what is wrong with fadeScaleTransition - * - * https://github.com/AllanWang/KAU/issues/184 - * - */ - fun update(fab: FloatingActionButton, themeProvider: ThemeProvider) { - val tint = - if (backgroundTint != Int.MAX_VALUE) backgroundTint else themeProvider.accentColor - val iconColor = iconColorProvider(themeProvider) - if (fab.isHidden) { - fab.setIcon(iicon, color = iconColor) - fab.backgroundTintList = ColorStateList.valueOf(tint) - fab.show() - } else { - fab.hide(object : FloatingActionButton.OnVisibilityChangedListener() { - override fun onHidden(fab: FloatingActionButton) { - fab.setIcon(iicon, color = iconColor) - fab.show() - } - }) - } - } - - abstract fun onClick(activity: ImageActivityOrig) -} diff --git a/app/src/main/res/layout/activity_image.xml b/app/src/main/res/layout/activity_image.xml index 7e19dabf..689c4f4d 100644 --- a/app/src/main/res/layout/activity_image.xml +++ b/app/src/main/res/layout/activity_image.xml @@ -19,7 +19,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- cgit v1.2.3 From e6a45b29cae18161edd05337ae6ba3482ba3068c Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 19 Nov 2021 21:31:35 -0800 Subject: Delete unused code --- .../pitchedapps/frost/activities/ImageActivity.kt | 57 ---------------------- 1 file changed, 57 deletions(-) (limited to 'app') 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 da991236..0f1cf4a5 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -113,8 +113,6 @@ class ImageActivity : KauBaseActivity() { * Linked to the uri provider */ private const val IMAGE_FOLDER = "images" - private const val TIME_FORMAT = "yyyyMMdd_HHmmss" - private const val IMG_TAG = "Frost" const val PURGE_TIME: Long = 10 * 60 * 1000 // 10 min block private val L = KauLoggerExtension("Image", com.pitchedapps.frost.utils.L) @@ -130,11 +128,6 @@ class ImageActivity : KauBaseActivity() { private val imageText: String? by lazy { intent.getStringExtra(ARG_TEXT) } - // a unique image identifier based on the id (if it exists), and its hash - private val imageHash: String by lazy { - "${abs(FB_IMAGE_ID_MATCHER.find(imageUrl)[1]?.hashCode() ?: 0)}_${abs(imageUrl.hashCode())}" - } - lateinit var binding: ActivityImageBinding private var bottomBehavior: BottomSheetBehavior? = null @@ -177,8 +170,6 @@ class ImageActivity : KauBaseActivity() { setContentView(binding.root) binding.init() launch(CoroutineExceptionHandler { _, throwable -> loadError(throwable) }) { -// val tempFile = downloadTempImage() -// this@ImageActivity.tempFile = tempFile binding.showImage(trueImageUrl.await()) } } @@ -328,54 +319,6 @@ class ImageActivity : KauBaseActivity() { override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int = top } - private fun getImageExtension(type: String?): String? { - if (type?.startsWith("image/") != true) { - return null - } - return when (type.substring(6)) { - "jpeg" -> "jpg" - "png" -> "png" - "gif" -> "gif" - else -> null - } - } - - @Throws(IOException::class) - private suspend fun downloadTempImage(): File = withContext(Dispatchers.IO) { - - // We assume all images are jpg - // Activity launcher may be able to provide specifics, but this beats sending a request - // just to get the content header - val file = File(cacheDir(this@ImageActivity), "$imageHash.jpg") - - 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 file - } - - val response = cookie.requestBuilder() - .url(trueImageUrl.await()) - .get() - .call() - .execute() - - if (!response.isSuccessful) { - throw IOException("Unsuccessful response for image: ${response.peekBody(128).string()}") - } - - val body = response.body ?: throw IOException("Failed to retrieve image body") - file.copyFromInputStream(body.byteStream()) - file - } - internal suspend fun saveImage() { frostDownload(cookie = cookie, url = trueImageUrl.await()) } -- cgit v1.2.3 From 4135d5e7734592deb7107ea9a751b153536d01f7 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 19 Nov 2021 21:35:49 -0800 Subject: Clean up and fix changes from compose test --- app/build.gradle | 8 +++----- .../pitchedapps/frost/activities/ImageActivity.kt | 23 +++++++++++----------- .../pitchedapps/frost/facebook/requests/Images.kt | 8 ++------ build.gradle | 3 +-- buildSrc/src/main/kotlin/Versions.kt | 4 ++-- 5 files changed, 20 insertions(+), 26 deletions(-) (limited to 'app') diff --git a/app/build.gradle b/app/build.gradle index ba0b2018..2655f695 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -274,9 +274,9 @@ dependencies { implementation "androidx.biometric:biometric:${Versions.andxBiometric}" - implementation 'com.github.piasy:BigImageViewer:1.8.1' - implementation 'com.github.piasy:GlideImageLoader:1.8.1' - implementation 'com.github.piasy:GlideImageViewFactory:1.8.1' + implementation "com.github.piasy:BigImageViewer:${Versions.bigImageViewer}" + implementation "com.github.piasy:GlideImageLoader:${Versions.bigImageViewer}" + implementation "com.github.piasy:GlideImageViewFactory:${Versions.bigImageViewer}" implementation kau.Dependencies.hilt kapt kau.Dependencies.hiltCompiler @@ -318,8 +318,6 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:${Versions.okhttp}" androidTestImplementation "com.squareup.okhttp3:mockwebserver:${Versions.okhttp}" -// implementation "com.davemorrissey.labs:subsampling-scale-image-view:${Versions.scaleImageView}" - implementation "androidx.room:room-ktx:${Versions.room}" implementation "androidx.room:room-runtime:${Versions.room}" kapt "androidx.room:room-compiler:${Versions.room}" 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 0f1cf4a5..e1a6a931 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -219,6 +219,18 @@ class ImageActivity : KauBaseActivity() { 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.setImageLoaderCallback(object : ImageLoader.Callback { override fun onCacheHit(imageType: Int, image: File?) {} @@ -237,17 +249,6 @@ class ImageActivity : KauBaseActivity() { } }) - imageProgress.tint(foregroundTint) - error.apply { - invisible() - setState(FabStates.ERROR) - } - download.apply { - setState(FabStates.DOWNLOAD) - } - share.apply { - setState(FabStates.SHARE) - } activityThemer.setFrostColors { themeWindow = false } diff --git a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt index 37f8fab3..70f911a8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/facebook/requests/Images.kt @@ -31,15 +31,12 @@ import kotlinx.coroutines.withTimeout /** * Attempts to get the fbcdn url of the supplied image redirect url */ -suspend fun String.getFullSizedImageUrl(url: String, timeout: Long = 3000): String? { - L.v { "Image full size 1 from $url" } - return withContext(Dispatchers.IO) { +suspend fun String.getFullSizedImageUrl(url: String, timeout: Long = 3000): String? = + withContext(Dispatchers.IO) { try { withTimeout(timeout) { - L.v { "Image full size from $url" } val redirect = requestBuilder().url(url).get().call() .execute().body?.string() ?: return@withTimeout null - L.v { "Image full size from redirect $redirect" } FB_REDIRECT_URL_MATCHER.find(redirect)[1]?.formattedFbUrl } } catch (e: Exception) { @@ -47,4 +44,3 @@ suspend fun String.getFullSizedImageUrl(url: String, timeout: Long = 3000): Stri null } } -} diff --git a/build.gradle b/build.gradle index c309a2e7..5a21959f 100644 --- a/build.gradle +++ b/build.gradle @@ -9,12 +9,11 @@ buildscript { dependencies { classpath kau.Plugins.android -// classpath kau.Plugins.kotlin + classpath kau.Plugins.kotlin classpath kau.Plugins.spotless classpath kau.Plugins.dexCount classpath kau.Plugins.hilt classpath kau.Plugins.gitVersion - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31' } wrapper.setDistributionType(Wrapper.DistributionType.ALL) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 5c68a01d..a8069507 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -19,8 +19,8 @@ object Versions { const val room = "2.3.0" // http://robolectric.org/getting-started/ const val roboelectric = "4.6" - // https://github.com/davemorrissey/subsampling-scale-image-view#quick-start - const val scaleImageView = "3.10.0" + // https://github.com/Piasy/BigImageViewer#add-the-dependencies + const val bigImageViewer = "1.8.1" // https://github.com/node-gradle/gradle-node-plugin/releases const val nodeGradle = "3.1.1" } \ No newline at end of file -- cgit v1.2.3 From 9a3f7861a798ce3c195915b5fb9dc7f63bdb4aac Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 19 Nov 2021 21:43:52 -0800 Subject: Format and update changelog --- .../main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt | 6 ------ app/src/main/play/en-US/whatsnew | 5 ++--- app/src/main/res/xml/frost_changelog.xml | 6 ++++++ docs/Changelog.md | 3 +++ 4 files changed, 11 insertions(+), 9 deletions(-) (limited to 'app') 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 e1a6a931..d8f5a46e 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/activities/ImageActivity.kt @@ -29,7 +29,6 @@ import ca.allanwang.kau.internal.KauBaseActivity import ca.allanwang.kau.logging.KauLoggerExtension 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 @@ -51,11 +50,8 @@ import com.mikepenz.iconics.typeface.IIcon import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.pitchedapps.frost.R import com.pitchedapps.frost.databinding.ActivityImageBinding -import com.pitchedapps.frost.facebook.FB_IMAGE_ID_MATCHER import com.pitchedapps.frost.facebook.get -import com.pitchedapps.frost.facebook.requests.call import com.pitchedapps.frost.facebook.requests.getFullSizedImageUrl -import com.pitchedapps.frost.facebook.requests.requestBuilder import com.pitchedapps.frost.injectors.ThemeProvider import com.pitchedapps.frost.prefs.Prefs import com.pitchedapps.frost.services.LocalService @@ -74,10 +70,8 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.io.File import java.io.FileNotFoundException -import java.io.IOException import javax.inject.Inject import kotlin.math.abs import kotlin.math.max diff --git a/app/src/main/play/en-US/whatsnew b/app/src/main/play/en-US/whatsnew index 06b44904..2e50ba70 100644 --- a/app/src/main/play/en-US/whatsnew +++ b/app/src/main/play/en-US/whatsnew @@ -1,4 +1,3 @@ -v3.1.1 +v3.1.2 -* Many internal fixes to address 3.1.0 issues -* Theme updates \ No newline at end of file +* Fix loading full size images \ No newline at end of file diff --git a/app/src/main/res/xml/frost_changelog.xml b/app/src/main/res/xml/frost_changelog.xml index e5f0ade6..9bc126fb 100644 --- a/app/src/main/res/xml/frost_changelog.xml +++ b/app/src/main/res/xml/frost_changelog.xml @@ -5,6 +5,12 @@ --> + + + + + + diff --git a/docs/Changelog.md b/docs/Changelog.md index d763f8da..87a48b87 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,5 +1,8 @@ # Changelog +## v3.1.2 +* Fix loading full size images + ## v3.1.1 * Many internal fixes to address 3.1.0 issues * Theme updates -- cgit v1.2.3 From 2febf338614e318ad39ed4a2960a351fb3875c43 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Fri, 19 Nov 2021 21:47:22 -0800 Subject: Remove image purging --- app/src/main/AndroidManifest.xml | 5 -- .../pitchedapps/frost/activities/ImageActivity.kt | 21 +---- .../com/pitchedapps/frost/services/LocalService.kt | 90 ---------------------- 3 files changed, 2 insertions(+), 114 deletions(-) delete mode 100644 app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt (limited to 'app') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5485ac15..eeab750f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -173,11 +173,6 @@ android:enabled="true" android:label="@string/frost_notifications" android:permission="android.permission.BIND_JOB_SERVICE" /> - . - */ -package com.pitchedapps.frost.services - -import android.app.job.JobInfo -import android.app.job.JobParameters -import android.app.job.JobScheduler -import android.content.ComponentName -import android.content.Context -import android.os.PersistableBundle -import com.pitchedapps.frost.activities.ImageActivity -import com.pitchedapps.frost.utils.L -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.FileFilter - -class LocalService : BaseJobService() { - - enum class Flag { - PURGE_IMAGE - } - - companion object { - private const val FLAG = "extra_local_flag" - - /** - * Launches a local service with the provided flag - */ - fun schedule(context: Context, flag: Flag): Boolean { - val scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler - val serviceComponent = ComponentName(context, LocalService::class.java) - val bundle = PersistableBundle() - bundle.putString(FLAG, flag.name) - - val builder = JobInfo.Builder(LOCAL_SERVICE_BASE + flag.ordinal, serviceComponent) - .setMinimumLatency(0L) - .setExtras(bundle) - .setOverrideDeadline(2000L) - - val result = scheduler.schedule(builder.build()) - if (result <= 0) { - L.eThrow("FrostRequestService scheduler failed for ${flag.name}") - return false - } - L.d { "Scheduled ${flag.name}" } - return true - } - } - - override fun onStartJob(params: JobParameters?): Boolean { - super.onStartJob(params) - val flagString = params?.extras?.getString(FLAG) - val flag: Flag = try { - Flag.valueOf(flagString!!) - } catch (e: Exception) { - L.e { "Local service with invalid flag $flagString" } - return true - } - launch { - when (flag) { - Flag.PURGE_IMAGE -> purgeImages() - } - } - return false - } - - private suspend fun purgeImages() { - withContext(Dispatchers.IO) { - val purge = System.currentTimeMillis() - ImageActivity.PURGE_TIME - ImageActivity.cacheDir(this@LocalService) - .listFiles(FileFilter { it.isFile && it.lastModified() < purge }) - ?.forEach { it.delete() } - } - } -} -- cgit v1.2.3