Skip to content

Commit b0ac31e

Browse files
committed
Replace custom ZoomableImageView implementation
(with SubsamplingScaleImageView)
1 parent fa05f74 commit b0ac31e

File tree

1 file changed

+136
-49
lines changed

1 file changed

+136
-49
lines changed

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

+136-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,153 @@ 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+
Log.i(TAG, "onReady was called")
75+
}
76+
77+
override fun onImageLoaded() {
78+
Log.i(TAG, "onImageLoaded was called")
79+
mediaPreview.visibility = View.VISIBLE
80+
placeholderText.visibility = View.GONE
81+
82+
if (item.type == ITEM_TYPE_IMAGE) {
83+
mediaPreview.isPanEnabled = true
84+
mediaPreview.isZoomEnabled = true
5985

60-
gActivity.asyncImageLoader.executeIfAlive {
61-
val bitmap: Bitmap? = try {
62-
if (item.type == ITEM_TYPE_VIDEO) {
63-
getVideoThumbnail(gActivity, item.uri)
86+
mediaPreview.setOnClickListener {
87+
gActivity.toggleActionBarState()
88+
}
6489
} 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-
}
90+
playButton.visibility = View.VISIBLE
8291

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)
92+
mediaPreview.setOnClickListener {
93+
val curItem = getCurrentItem()
94+
if (curItem.type == ITEM_TYPE_VIDEO) {
95+
val intent = Intent(gActivity, VideoPlayer::class.java)
96+
intent.putExtra(VideoPlayer.VIDEO_URI, curItem.uri)
97+
intent.putExtra(VideoPlayer.IN_SECURE_MODE, gActivity.isSecureMode)
8998

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

96-
val resId = if (item.type == ITEM_TYPE_IMAGE) {
97-
R.string.inaccessible_image
98-
} else { R.string.inaccessible_video }
113+
onErrorLoadingMedia()
114+
}
99115

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

0 commit comments

Comments
 (0)