Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 41d22b1

Browse files
committedAug 15, 2024
Replace custom ZoomableImageView implementation
(with SubsamplingScaleImageView)
1 parent fa05f74 commit 41d22b1

File tree

1 file changed

+130
-49
lines changed

1 file changed

+130
-49
lines changed
 

‎app/src/main/java/app/grapheneos/camera/GallerySliderAdapter.kt

+130-49
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
package app.grapheneos.camera
22

3+
import android.annotation.SuppressLint
34
import android.content.Intent
4-
import android.graphics.Bitmap
55
import android.graphics.ImageDecoder
6+
import android.graphics.PointF
7+
import android.util.Log
68
import android.view.LayoutInflater
79
import android.view.View
810
import android.view.ViewGroup
911
import android.widget.ImageView
1012
import androidx.recyclerview.widget.RecyclerView
1113
import app.grapheneos.camera.capturer.getVideoThumbnail
1214
import app.grapheneos.camera.databinding.GallerySlideBinding
13-
import app.grapheneos.camera.ui.ZoomableImageView
15+
import app.grapheneos.camera.ktx.fixOrientationForImage
1416
import app.grapheneos.camera.ui.activities.InAppGallery
1517
import app.grapheneos.camera.ui.activities.VideoPlayer
1618
import app.grapheneos.camera.ui.fragment.GallerySlide
1719
import app.grapheneos.camera.util.executeIfAlive
20+
import com.davemorrissey.labs.subscaleview.ImageSource
21+
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
1822
import kotlin.math.max
1923

2024
class GallerySliderAdapter(
2125
private val gActivity: InAppGallery,
2226
val items: ArrayList<CapturedItem>
2327
) : RecyclerView.Adapter<GallerySlide>() {
2428

29+
companion object {
30+
private const val TAG = "GallerySliderAdapter"
31+
}
32+
2533
var atLeastOneBindViewHolderCall = false
2634

2735
private val layoutInflater: LayoutInflater = LayoutInflater.from(gActivity)
@@ -34,74 +42,147 @@ class GallerySliderAdapter(
3442
return items[position].hashCode().toLong()
3543
}
3644

45+
@SuppressLint("ClickableViewAccessibility")
3746
override fun onBindViewHolder(holder: GallerySlide, position: Int) {
38-
val mediaPreview: ZoomableImageView = holder.binding.slidePreview
39-
// Log.d("GallerySliderAdapter", "postiion $position, preview ${System.identityHashCode(mediaPreview)}")
47+
holder.currentPostion = position
48+
49+
val mediaPreview: SubsamplingScaleImageView = holder.binding.slidePreview
4050
val playButton: ImageView = holder.binding.playButton
4151
val item = items[position]
4252

43-
mediaPreview.setGalleryActivity(gActivity)
44-
mediaPreview.disableZooming()
45-
mediaPreview.setOnClickListener(null)
46-
mediaPreview.visibility = View.INVISIBLE
47-
mediaPreview.setImageBitmap(null)
48-
4953
val placeholderText = holder.binding.placeholderText.root
5054
if (atLeastOneBindViewHolderCall) {
5155
placeholderText.visibility = View.VISIBLE
52-
placeholderText.setText("")
56+
placeholderText.text = ""
5357
}
5458
atLeastOneBindViewHolderCall = true
5559

5660
playButton.visibility = View.GONE
5761

58-
holder.currentPostion = position
62+
mediaPreview.isPanEnabled = false
63+
mediaPreview.isZoomEnabled = false
64+
65+
mediaPreview.setExecutor(gActivity.asyncImageLoader)
66+
67+
mediaPreview.maxScale = 3f
68+
69+
mediaPreview.setDoubleTapZoomScale(1.5F)
70+
mediaPreview.setDoubleTapZoomDuration(300)
71+
72+
mediaPreview.setOnImageEventListener(object : SubsamplingScaleImageView.OnImageEventListener {
73+
override fun onReady() {}
74+
75+
override fun onImageLoaded() {
76+
mediaPreview.visibility = View.VISIBLE
77+
placeholderText.visibility = View.GONE
78+
79+
if (item.type == ITEM_TYPE_IMAGE) {
80+
mediaPreview.isPanEnabled = true
81+
mediaPreview.isZoomEnabled = true
5982

60-
gActivity.asyncImageLoader.executeIfAlive {
61-
val bitmap: Bitmap? = try {
62-
if (item.type == ITEM_TYPE_VIDEO) {
63-
getVideoThumbnail(gActivity, item.uri)
83+
mediaPreview.setOnClickListener {
84+
gActivity.toggleActionBarState()
85+
}
6486
} else {
65-
val source = ImageDecoder.createSource(gActivity.contentResolver, item.uri)
66-
ImageDecoder.decodeBitmap(source, ImageDownscaler)
67-
}
68-
} catch (e: Exception) { null }
69-
70-
gActivity.mainExecutor.execute {
71-
if (holder.currentPostion == position) {
72-
if (bitmap != null) {
73-
placeholderText.visibility = View.GONE
74-
mediaPreview.visibility = View.VISIBLE
75-
mediaPreview.setImageBitmap(bitmap)
76-
77-
if (item.type == ITEM_TYPE_VIDEO) {
78-
playButton.visibility = View.VISIBLE
79-
} else if (item.type == ITEM_TYPE_IMAGE) {
80-
mediaPreview.enableZooming()
81-
}
87+
playButton.visibility = View.VISIBLE
8288

83-
mediaPreview.setOnClickListener {
84-
val curItem = getCurrentItem()
85-
if (curItem.type == ITEM_TYPE_VIDEO) {
86-
val intent = Intent(gActivity, VideoPlayer::class.java)
87-
intent.putExtra(VideoPlayer.VIDEO_URI, curItem.uri)
88-
intent.putExtra(VideoPlayer.IN_SECURE_MODE, gActivity.isSecureMode)
89+
mediaPreview.setOnClickListener {
90+
val curItem = getCurrentItem()
91+
if (curItem.type == ITEM_TYPE_VIDEO) {
92+
val intent = Intent(gActivity, VideoPlayer::class.java)
93+
intent.putExtra(VideoPlayer.VIDEO_URI, curItem.uri)
94+
intent.putExtra(VideoPlayer.IN_SECURE_MODE, gActivity.isSecureMode)
8995

90-
gActivity.startActivity(intent)
91-
}
96+
gActivity.startActivity(intent)
9297
}
93-
} else {
94-
mediaPreview.visibility = View.INVISIBLE
98+
}
99+
}
100+
}
101+
102+
override fun onImageLoadError(e: java.lang.Exception?) {
103+
Log.i(TAG, "onImageLoadError: Failed to load image")
104+
if (e == null) {
105+
Log.d(TAG, "onImageLoadError received null as an exception")
106+
} else {
107+
e.printStackTrace()
108+
}
95109

96-
val resId = if (item.type == ITEM_TYPE_IMAGE) {
97-
R.string.inaccessible_image
98-
} else { R.string.inaccessible_video }
110+
onErrorLoadingMedia()
111+
}
99112

100-
placeholderText.visibility = View.VISIBLE
101-
placeholderText.setText(gActivity.getString(resId, item.dateString))
113+
override fun onTileLoadError(e: java.lang.Exception?) {
114+
Log.i(TAG, "onTileLoadError: An unexpected error occurred while loading a tile")
115+
if (e == null) {
116+
Log.d(TAG, "onTileLoadError: Received null as an exception")
117+
} else {
118+
e.printStackTrace()
119+
}
120+
121+
onErrorLoadingMedia()
122+
}
123+
124+
override fun onPreviewLoadError(e: java.lang.Exception?) {}
125+
126+
override fun onPreviewReleased() {}
127+
128+
fun onErrorLoadingMedia() {
129+
mediaPreview.visibility = View.INVISIBLE
130+
131+
val placeholderTextFormat = if (item.type == ITEM_TYPE_VIDEO) {
132+
R.string.inaccessible_video
133+
} else {
134+
R.string.inaccessible_image
135+
}
136+
137+
placeholderText.visibility = View.VISIBLE
138+
placeholderText.text = gActivity.getString(placeholderTextFormat, item.dateString)
139+
}
140+
})
141+
142+
// Ensure that the touch events are being sent to the most recently viewed media
143+
mediaPreview.setOnTouchListener { v, event ->
144+
gActivity.gallerySlider.getChildAt(0).findViewById<SubsamplingScaleImageView>(R.id.slide_preview).onTouchEvent(event)
145+
}
146+
147+
mediaPreview.setOnStateChangedListener(object: SubsamplingScaleImageView.OnStateChangedListener {
148+
override fun onScaleChanged(newScale: Float, origin: Int) {
149+
gActivity.let {
150+
if (newScale == mediaPreview.minScale) {
151+
gActivity.gallerySlider.isUserInputEnabled = true
152+
it.showActionBar()
153+
it.vibrateDevice()
154+
} else {
155+
gActivity.gallerySlider.isUserInputEnabled = false
156+
it.hideActionBar()
157+
}
158+
}
159+
}
160+
161+
override fun onCenterChanged(newCenter: PointF?, origin: Int) {}
162+
163+
})
164+
165+
if (item.type == ITEM_TYPE_IMAGE) {
166+
mediaPreview.fixOrientationForImage(item.uri)
167+
mediaPreview.setImage(ImageSource.uri(item.uri))
168+
} else {
169+
gActivity.asyncImageLoader.executeIfAlive {
170+
val thumbnailBitmap = getVideoThumbnail(gActivity, item.uri)
171+
172+
if (thumbnailBitmap != null) {
173+
gActivity.mainExecutor.execute {
174+
if (holder.currentPostion == position) {
175+
thumbnailBitmap.let {
176+
mediaPreview.setImage(ImageSource.bitmap(thumbnailBitmap))
177+
}
178+
} else {
179+
thumbnailBitmap.recycle()
180+
}
102181
}
103182
} else {
104-
bitmap?.recycle()
183+
mediaPreview.visibility = View.INVISIBLE
184+
placeholderText.visibility = View.VISIBLE
185+
placeholderText.text = gActivity.getString(R.string.inaccessible_video, item.dateString)
105186
}
106187
}
107188
}

0 commit comments

Comments
 (0)
Please sign in to comment.