diff --git a/samples/gallery/build.gradle b/samples/gallery/build.gradle index 5ef74a01ef..9025bd7f55 100644 --- a/samples/gallery/build.gradle +++ b/samples/gallery/build.gradle @@ -5,6 +5,7 @@ apply plugin: 'com.google.devtools.ksp' dependencies { implementation project(':library') + implementation project(':integration:compose') implementation(project(':integration:recyclerview')) { transitive = false } @@ -12,11 +13,11 @@ dependencies { implementation "androidx.recyclerview:recyclerview:$ANDROID_X_RECYCLERVIEW_VERSION" implementation "androidx.fragment:fragment-ktx:$ANDROID_X_FRAGMENT_VERSION" implementation "androidx.core:core-ktx:$ANDROID_X_CORE_KTX_VERSION" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:$ANDROID_X_LIFECYCLE_KTX_VERSION" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:$ANDROID_X_LIFECYCLE_KTX_VERSION" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$JETBRAINS_KOTLINX_COROUTINES_VERSION" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$JETBRAINS_KOTLINX_COROUTINES_VERSION" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$JETBRAINS_KOTLIN_VERSION" + implementation "androidx.compose.foundation:foundation:$ANDROID_X_COMPOSE_VERSION" + implementation "androidx.compose.ui:ui:$ANDROID_X_COMPOSE_VERSION" ksp project(':annotation:ksp') } @@ -37,6 +38,15 @@ android { versionCode 1 versionName '1.0' } + buildFeatures { + compose = true + } + kotlinOptions { + jvmTarget = "11" + } + composeOptions { + kotlinCompilerExtensionVersion '1.2.0' + } compileOptions { sourceCompatibility JavaVersion.VERSION_11 diff --git a/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/HorizontalGalleryFragment.kt b/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/HorizontalGalleryFragment.kt index 1173f1131c..d6ae5a91f7 100644 --- a/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/HorizontalGalleryFragment.kt +++ b/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/HorizontalGalleryFragment.kt @@ -4,48 +4,88 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.ComposeView +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.GridLayoutManager import com.bumptech.glide.Glide -import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader -import kotlinx.coroutines.launch +import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi +import com.bumptech.glide.integration.compose.GlideImage +import com.bumptech.glide.integration.compose.GlideLazyListPreloader +import com.bumptech.glide.signature.MediaStoreSignature /** Displays media store data in a recycler view. */ +@OptIn(ExperimentalGlideComposeApi::class) class HorizontalGalleryFragment : Fragment() { - private lateinit var adapter: RecyclerAdapter - private lateinit var recyclerView: RecyclerView - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, + ): View { val galleryViewModel: GalleryViewModel by viewModels() - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - galleryViewModel.mediaStoreData.collect { data -> - adapter.setData(data) - } + return ComposeView(requireContext()).apply { + setContent { + LoadableDeviceMedia(galleryViewModel) } } } - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - val result = inflater.inflate(R.layout.recycler_view, container, false) - recyclerView = result.findViewById(R.id.recycler_view) as RecyclerView - val layoutManager = GridLayoutManager(activity, 1) - layoutManager.orientation = RecyclerView.HORIZONTAL - recyclerView.layoutManager = layoutManager - recyclerView.setHasFixedSize(true) - - val glideRequests = Glide.with(this) - adapter = RecyclerAdapter(requireContext(), glideRequests) - val preloader = RecyclerViewPreloader(glideRequests, adapter, adapter, 3) - recyclerView.addOnScrollListener(preloader) - recyclerView.adapter = adapter - return result + @Composable + fun LoadableDeviceMedia(viewModel: GalleryViewModel) { + val mediaStoreData = viewModel.mediaStoreData.collectAsState() + DeviceMedia(mediaStoreData.value) + } + + @Composable + fun DeviceMedia(mediaStoreData: List) { + val state = rememberLazyListState() + LazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp), state = state) { + items(mediaStoreData) { mediaStoreItem -> + MediaStoreView(mediaStoreItem, Modifier.fillParentMaxSize()) + } + } + + GlideLazyListPreloader( + state = state, + data = mediaStoreData, + size = THUMBNAIL_SIZE, + numberOfItemsToPreload = 15, + fixedVisibleItemCount = 2, + ) { item, requestBuilder -> requestBuilder.load(item.uri).signature(item.signature()) } + } + + private fun MediaStoreData.signature() = + MediaStoreSignature(mimeType, dateModified, orientation) + + @Composable + fun MediaStoreView(item: MediaStoreData, modifier: Modifier) { + val requestManager = Glide.with(requireContext()) + val signature = item.signature() + + GlideImage( + model = item.uri, + contentDescription = item.displayName, + modifier = modifier, + ) { + it.thumbnail( + requestManager.asDrawable() + .load(item.uri) + .signature(signature) + .override(THUMBNAIL_DIMENSION) + ) + .signature(signature) + } + } + + companion object { + private const val THUMBNAIL_DIMENSION = 50 + private val THUMBNAIL_SIZE = Size(THUMBNAIL_DIMENSION.toFloat(), THUMBNAIL_DIMENSION.toFloat()) } -} \ No newline at end of file +} diff --git a/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/MediaStoreDataSource.kt b/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/MediaStoreDataSource.kt index c05ac2663e..71c1ffa280 100644 --- a/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/MediaStoreDataSource.kt +++ b/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/MediaStoreDataSource.kt @@ -69,6 +69,7 @@ class MediaStoreDataSource internal constructor( val mimeTypeColNum = cursor.getColumnIndexOrThrow(MediaColumns.MIME_TYPE) val orientationColNum = cursor.getColumnIndexOrThrow(MediaColumns.ORIENTATION) val mediaTypeColumnIndex = cursor.getColumnIndexOrThrow(FileColumns.MEDIA_TYPE) + val displayNameIndex = cursor.getColumnIndexOrThrow(FileColumns.DISPLAY_NAME) while (cursor.moveToNext()) { val id = cursor.getLong(idColNum) @@ -76,6 +77,7 @@ class MediaStoreDataSource internal constructor( val mimeType = cursor.getString(mimeTypeColNum) val dateModified = cursor.getLong(dateModifiedColNum) val orientation = cursor.getInt(orientationColNum) + val displayName = cursor.getString(displayNameIndex) val type = if (cursor.getInt(mediaTypeColumnIndex) == FileColumns.MEDIA_TYPE_IMAGE) Type.IMAGE @@ -89,7 +91,9 @@ class MediaStoreDataSource internal constructor( mimeType = mimeType, dateModified = dateModified, orientation = orientation, - dateTaken = dateTaken)) + dateTaken = dateTaken, + displayName = displayName, + )) } } return data @@ -103,6 +107,7 @@ class MediaStoreDataSource internal constructor( MediaColumns.DATE_MODIFIED, MediaColumns.MIME_TYPE, MediaColumns.ORIENTATION, + MediaColumns.DISPLAY_NAME, FileColumns.MEDIA_TYPE) } } @@ -117,6 +122,7 @@ data class MediaStoreData( val dateModified: Long, val orientation: Int, val dateTaken: Long, + val displayName: String? ) : Parcelable /** The type of data. */ diff --git a/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/RecyclerAdapter.kt b/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/RecyclerAdapter.kt deleted file mode 100644 index fd7c8c79f7..0000000000 --- a/samples/gallery/src/main/java/com/bumptech/glide/samples/gallery/RecyclerAdapter.kt +++ /dev/null @@ -1,121 +0,0 @@ -package com.bumptech.glide.samples.gallery - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Point -import android.graphics.drawable.Drawable -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.ViewTreeObserver -import android.view.WindowManager -import android.widget.ImageView -import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.samples.gallery.RecyclerAdapter.ListViewHolder -import com.bumptech.glide.ListPreloader.PreloadSizeProvider -import com.bumptech.glide.ListPreloader.PreloadModelProvider -import com.bumptech.glide.RequestBuilder -import com.bumptech.glide.RequestManager -import com.bumptech.glide.load.Key -import com.bumptech.glide.signature.MediaStoreSignature -import com.bumptech.glide.util.Preconditions - -/** Displays [com.bumptech.glide.samples.gallery.MediaStoreData] in a recycler view. */ -internal class RecyclerAdapter( - context: Context, - glideRequests: RequestManager, -) : RecyclerView.Adapter(), PreloadSizeProvider, - PreloadModelProvider { - private var data: List = emptyList() - private val screenWidth: Int - private val requestBuilder: RequestBuilder - private var actualDimensions: IntArray? = null - - override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ListViewHolder { - val inflater = LayoutInflater.from(viewGroup.context) - val view = inflater.inflate(R.layout.recycler_item, viewGroup, false) - view.layoutParams.width = screenWidth - if (actualDimensions == null) { - view.viewTreeObserver - .addOnPreDrawListener( - object : ViewTreeObserver.OnPreDrawListener { - override fun onPreDraw(): Boolean { - if (actualDimensions == null) { - actualDimensions = intArrayOf(view.width, view.height) - } - view.viewTreeObserver.removeOnPreDrawListener(this) - return true - } - }) - } - return ListViewHolder(view) - } - - override fun onBindViewHolder(viewHolder: ListViewHolder, position: Int) { - val current = data[position] - val signature: Key = - MediaStoreSignature(current.mimeType, current.dateModified, current.orientation) - requestBuilder.clone().signature(signature).load(current.uri).into(viewHolder.image) - } - - @SuppressLint("NotifyDataSetChanged") - fun setData(mediaStoreData: List) { - data = mediaStoreData - notifyDataSetChanged() - } - - override fun getItemId(position: Int): Long { - return data[position].rowId - } - - override fun getItemCount(): Int { - return data.size - } - - override fun getItemViewType(position: Int): Int { - return 0 - } - - override fun getPreloadItems(position: Int): List { - return if (data.isEmpty()) emptyList() else listOf(data[position]) - } - - override fun getPreloadRequestBuilder(item: MediaStoreData): RequestBuilder? { - val signature = MediaStoreSignature(item.mimeType, item.dateModified, item.orientation) - return requestBuilder.clone().signature(signature).load(item.uri) - } - - override fun getPreloadSize( - item: MediaStoreData, adapterPosition: Int, perItemPosition: Int, - ): IntArray? { - return actualDimensions - } - - /** - * ViewHolder containing views to display individual [ ]. - */ - internal class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val image: ImageView - - init { - image = itemView.findViewById(R.id.image) - } - } - - companion object { - // Display#getSize(Point) - private fun getScreenWidth(context: Context): Int { - val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - val display = Preconditions.checkNotNull(wm).defaultDisplay - val size = Point() - display.getSize(size) - return size.x - } - } - - init { - requestBuilder = glideRequests.asDrawable().fitCenter() - setHasStableIds(true) - screenWidth = getScreenWidth(context) - } -} \ No newline at end of file