Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 16b949d8 authored by moezbhatti's avatar moezbhatti
Browse files

Upgrade to ViewPager2

Should hopefully address #1340
parent d707cb30
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ buildscript {
    ext.androidx_emoji_version = '1.0.0'
    ext.androidx_exifinterface_version = '1.0.0'
    ext.androidx_testrunner_version = '1.1.0-alpha3'
    ext.androidx_viewpager_version = '1.0.0-alpha01'
    ext.autodispose_version = '0.7.0'
    ext.conductor_version = '2.1.5'
    ext.dagger_version = "2.16"
+2 −1
Original line number Diff line number Diff line
@@ -113,8 +113,9 @@ dependencies {
    implementation "androidx.appcompat:appcompat:$androidx_appcompat_version"
    implementation "androidx.constraintlayout:constraintlayout:$androidx_constraintlayout_version"
    implementation "androidx.core:core-ktx:$androidx_core_version"
    implementation "com.google.android.material:material:$material_version"
    implementation "androidx.emoji:emoji-appcompat:$androidx_emoji_version"
    implementation "androidx.viewpager2:viewpager2:$androidx_viewpager_version"
    implementation "com.google.android.material:material:$material_version"

    // conductor
    implementation "com.bluelinelabs:conductor:$conductor_version"
+27 −12
Original line number Diff line number Diff line
@@ -24,8 +24,11 @@ import android.view.MenuItem
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.moez.QKSMS.R
import com.moez.QKSMS.common.base.QkActivity
import com.moez.QKSMS.common.util.DateFormatter
import com.moez.QKSMS.common.util.extensions.addOnPageChangeListener
import com.moez.QKSMS.common.util.extensions.setVisible
import com.moez.QKSMS.model.MmsPart
@@ -38,9 +41,12 @@ import javax.inject.Inject

class GalleryActivity : QkActivity(), GalleryView {

    @Inject lateinit var dateFormatter: DateFormatter
    @Inject lateinit var viewModelFactory: ViewModelProvider.Factory
    @Inject lateinit var pagerAdapter: GalleryPagerAdapter

    val partId by lazy { intent.getLongExtra("partId", 0L) }

    private val optionsItemSubject: Subject<Int> = PublishSubject.create()
    private val pageChangedSubject: Subject<MmsPart> = PublishSubject.create()
    private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory)[GalleryViewModel::class.java] }
@@ -52,20 +58,29 @@ class GalleryActivity : QkActivity(), GalleryView {
        showBackButton(true)
        viewModel.bindView(this)

        pagerAdapter.setInitialPositionHandler = { position ->
            pager.setCurrentItem(position, false)

            // The ViewPager's page change listener isn't called if the initial position is 0
            if (position == 0) {
                pageChanged(0)
        pager.adapter = pagerAdapter
        pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                this@GalleryActivity.onPageSelected(position)
            }
        })

        pagerAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
            override fun onChanged() {
                pagerAdapter.data?.takeIf { pagerAdapter.itemCount > 0 }
                        ?.indexOfFirst { part -> part.id == partId }
                        ?.let { index ->
                            onPageSelected(index)
                            pager.setCurrentItem(index, false)
                            pagerAdapter.unregisterAdapterDataObserver(this)
                        }
        pager.adapter = pagerAdapter
        pager.addOnPageChangeListener(this::pageChanged)
            }
        })
    }

    private fun pageChanged(position: Int) {
        toolbarSubtitle.text = pagerAdapter.getPageTitle(position)
    fun onPageSelected(position: Int) {
        toolbarSubtitle.text = pagerAdapter.getItem(position)?.messages?.firstOrNull()?.date
                ?.let(dateFormatter::getDetailedTimestamp)
        toolbarSubtitle.isVisible = toolbarTitle.text.isNotBlank()

        pagerAdapter.getItem(position)?.run(pageChangedSubject::onNext)
@@ -75,7 +90,7 @@ class GalleryActivity : QkActivity(), GalleryView {
        toolbar.setVisible(state.navigationVisible)

        title = state.title
        pagerAdapter.parts = state.parts
        pagerAdapter.updateData(state.parts)
    }

    override fun optionsItemSelected(): Observable<Int> = optionsItemSubject
+1 −1
Original line number Diff line number Diff line
@@ -34,7 +34,7 @@ class GalleryActivityModule {

    @Provides
    @Named("partId")
    fun providePartId(activity: GalleryActivity): Long = activity.intent.getLongExtra("partId", 0L)
    fun providePartId(activity: GalleryActivity): Long = activity.partId

    @Provides
    @IntoMap
+38 −73
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.viewpager.widget.PagerAdapter
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.ExoPlayerFactory
import com.google.android.exoplayer2.source.ExtractorMediaSource
@@ -32,57 +31,36 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import com.google.android.mms.ContentType
import com.moez.QKSMS.R
import com.moez.QKSMS.common.util.DateFormatter
import com.moez.QKSMS.common.base.QkRealmAdapter
import com.moez.QKSMS.common.base.QkViewHolder
import com.moez.QKSMS.extensions.isImage
import com.moez.QKSMS.extensions.isVideo
import com.moez.QKSMS.model.MmsPart
import com.moez.QKSMS.util.GlideApp
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import io.realm.RealmResults
import kotlinx.android.synthetic.main.gallery_image_page.view.*
import kotlinx.android.synthetic.main.gallery_video_page.view.*
import java.util.*
import javax.inject.Inject
import javax.inject.Named

class GalleryPagerAdapter @Inject constructor(
    context: Context,
    @Named("partId") private val partId: Long,
    private val dateFormatter: DateFormatter
) : PagerAdapter() {
class GalleryPagerAdapter @Inject constructor(private val context: Context) : QkRealmAdapter<MmsPart>() {

    var parts: RealmResults<MmsPart>? = null
        set(value) {
            if (field === value) return
            field = value

            field?.asFlowable()
                    ?.filter { it.isLoaded }
                    ?.subscribe { notifyDataSetChanged() }
                    ?.run(disposables::add)
    companion object {
        private const val VIEW_TYPE_INVALID = 0
        private const val VIEW_TYPE_IMAGE = 1
        private const val VIEW_TYPE_VIDEO = 2
    }

    val clicks: Subject<View> = PublishSubject.create()

    /**
     * The Adapter isn't able to set the position itself, so it's owner must apply the initial
     * position once the data is loaded
     */
    var setInitialPositionHandler: ((Int) -> Unit)? = null

    private var loaded = false

    private val contentResolver = context.contentResolver
    private val disposables = CompositeDisposable()
    private val exoPlayers = Collections.newSetFromMap(WeakHashMap<ExoPlayer?, Boolean>())

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val part = getItem(position)
        val inflater = LayoutInflater.from(container.context)
        return when {
            part?.isImage() == true -> inflater.inflate(R.layout.gallery_image_page, container, false).apply {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return QkViewHolder(when (viewType) {
            VIEW_TYPE_IMAGE -> inflater.inflate(R.layout.gallery_image_page, parent, false).apply {

                // When calling the public setter, it doesn't allow the midscale to be the same as the
                // maxscale or the minscale. We don't want 3 levels and we don't want to modify the library
@@ -101,71 +79,58 @@ class GalleryPagerAdapter @Inject constructor(
                        setFloat(image.attacher, 3f)
                    }
                }
            }

            VIEW_TYPE_VIDEO -> inflater.inflate(R.layout.gallery_video_page, parent, false)

            else -> inflater.inflate(R.layout.gallery_invalid_page, parent, false)

        }.apply { setOnClickListener(clicks::onNext) })
    }

    override fun onBindViewHolder(holder: QkViewHolder, position: Int) {
        val part = getItem(position)!!
        val view = holder.containerView
        when (getItemViewType(position)) {
            VIEW_TYPE_IMAGE -> {
                // We need to explicitly request a gif from glide for animations to work
                when (part.getUri().let(contentResolver::getType)) {
                    ContentType.IMAGE_GIF -> GlideApp.with(context)
                            .asGif()
                            .load(part.getUri())
                            .into(image)
                            .into(view.image)

                    else -> GlideApp.with(context)
                            .asBitmap()
                            .load(part.getUri())
                            .into(image)
                            .into(view.image)
                }

                container.addView(this)
            }

            part?.isVideo() == true -> inflater.inflate(R.layout.gallery_video_page, container, false).apply {
            VIEW_TYPE_VIDEO -> {
                val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(null)
                val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
                val exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector)
                video.player = exoPlayer
                view.video.player = exoPlayer
                exoPlayers.add(exoPlayer)

                val dataSourceFactory = DefaultDataSourceFactory(context, Util.getUserAgent(context, "QKSMS"))
                val videoSource = ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(part.getUri())
                exoPlayer?.prepare(videoSource)

                container.addView(this)
            }

            else -> inflater.inflate(R.layout.gallery_invalid_page, container, false).apply {
                container.addView(this)
            }
        }.apply { setOnClickListener(clicks::onNext) }
    }

    override fun getPageTitle(position: Int): CharSequence? {
        return getItem(position)?.messages?.firstOrNull()?.date?.let(dateFormatter::getDetailedTimestamp)
    }

    override fun isViewFromObject(view: View, `object`: Any): Boolean {
        return view == `object`
        }

    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
        container.removeView(`object` as? View)
    }

    override fun getCount() = parts?.size ?: 0

    fun getItem(position: Int): MmsPart? = parts?.get(position)

    override fun notifyDataSetChanged() {
        super.notifyDataSetChanged()

        if (!loaded && parts?.isLoaded == true) {
            loaded = true
            parts?.indexOfFirst { it.id == partId }
                    ?.let { setInitialPositionHandler?.invoke(it) }
    override fun getItemViewType(position: Int): Int {
        val part = getItem(position)
        return when {
            part?.isImage() == true -> VIEW_TYPE_IMAGE
            part?.isVideo() == true -> VIEW_TYPE_VIDEO
            else -> VIEW_TYPE_INVALID
        }
    }

    fun destroy() {
        disposables.dispose()
        exoPlayers.forEach { exoPlayer -> exoPlayer?.release() }
    }

Loading