<|start_filename|>sample/src/main/java/com/github/globocom/viewport/sample/MainActivity.kt<|end_filename|>
package com.github.globocom.viewport.sample

import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.globocom.viewport.mobile.Threshold
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity(), AdapterView.OnItemSelectedListener {
    companion object {
        private const val LINEAR_LAYOUT_MANAGER = 0
        private const val GRID_LAYOUT_MANAGER = 1
    }

    private val spinnerThresholdValues = arrayListOf(
        Threshold.VISIBLE.name,
        Threshold.HALF.name,
        Threshold.ALMOST_VISIBLE.name,
        Threshold.ALMOST_HIDDEN.name
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setRecyclerView(LINEAR_LAYOUT_MANAGER, Threshold.VISIBLE)

        activity_main_view_port_recycler_view.apply {
            viewedItemsLiveData.observe(this@MainActivity, Observer {
                activity_main_text_view_items_viewed_tv.text =
                    getString(R.string.view_port_viewed_items, it.toString())
            })
            onlyNewViewedItemsLiveData.observe(this@MainActivity, Observer {
                activity_main_text_view_new_viewed_items.text =
                    getString(R.string.view_port_new_visible_items, it.toString())
            })
            lifecycleOwner = this@MainActivity
            threshold(Threshold.VISIBLE)
        }

        activity_main_button_linear_layout.setOnClickListener {
            setRecyclerView(LINEAR_LAYOUT_MANAGER, getSelectedThreshold())
            activity_main_view_port_recycler_view.invalidate()
        }

        activity_main_button_grid_layout.setOnClickListener {
            setRecyclerView(GRID_LAYOUT_MANAGER, getSelectedThreshold())
            activity_main_view_port_recycler_view.invalidate()
        }

        activity_main_spinner_threshold.run {
            this.adapter = ArrayAdapter(
                this@MainActivity,
                android.R.layout.simple_spinner_item,
                spinnerThresholdValues
            )
            onItemSelectedListener = this@MainActivity
        }
    }

    private fun setRecyclerView(
        layoutManagerOption: Int,
        threshold: Threshold
    ) {
        activity_main_view_port_recycler_view.apply {
            setHasFixedSize(true)
            threshold(threshold)
            layoutManager =
                if (layoutManagerOption == GRID_LAYOUT_MANAGER) GridLayoutManager(context, 4)
                else LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)

            adapter = ViewPortSampleAdapter((0..100).toList())
        }
    }

    private fun getSelectedThreshold(): Threshold {
        return Threshold.valueOf(spinnerThresholdValues[activity_main_spinner_threshold.selectedItemPosition])
    }

    override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
        activity_main_view_port_recycler_view.threshold(Threshold.valueOf(spinnerThresholdValues[position]))
    }

    override fun onNothingSelected(p0: AdapterView<*>?) {

    }
}


<|start_filename|>commons/src/main/java/com/github/globocom/viewport/commons/ViewPortPartialHelper.kt<|end_filename|>
package com.github.globocom.viewport.commons

import android.view.View
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView

object ViewPortPartialHelper {

    fun findPartialVisibleChild(
        recyclerView: RecyclerView,
        layoutManager: RecyclerView.LayoutManager,
        threshold: Float,
        fromEndToStart: Boolean
    ): Int {
        val helper: OrientationHelper = if (layoutManager.canScrollVertically()) {
            OrientationHelper.createVerticalHelper(layoutManager)
        } else {
            OrientationHelper.createHorizontalHelper(layoutManager)
        }

        val start: Int = helper.startAfterPadding
        val end: Int = helper.endAfterPadding

        val progression = 0.until(layoutManager.childCount).let {
            if (fromEndToStart) it.reversed() else it
        }

        for (index in progression) {
            val child: View? = layoutManager.getChildAt(index)
            val childStart: Int = helper.getDecoratedStart(child)
            val childEnd: Int = helper.getDecoratedEnd(child)
            val viewSize: Int = helper.getDecoratedMeasurement(child)

            if (checkThreshold(threshold, childStart, childEnd, start, end, viewSize)) {
                child?.let { return recyclerView.getChildAdapterPosition(it) }
            }
        }
        return RecyclerView.NO_POSITION
    }

    private fun checkThreshold(
        threshold: Float,
        childStart: Int,
        childEnd: Int,
        start: Int,
        end: Int,
        viewSize: Int
    ): Boolean {
        val visibleStart = childStart.coerceIn(start, end)
        val visibleEnd = childEnd.coerceIn(start, end)
        val visibleSize = visibleEnd - visibleStart
        val visibleProportion = visibleSize.toFloat() / viewSize.toFloat()
        return visibleProportion >= threshold
    }
}


<|start_filename|>library-mobile/src/main/java/com/github/globocom/viewport/mobile/Threshold.kt<|end_filename|>
package com.github.globocom.viewport.mobile

enum class Threshold(val proportion: Float) {
    VISIBLE(1f),
    HALF(0.5f),
    ALMOST_HIDDEN(0.25f),
    ALMOST_VISIBLE(0.75f);

    companion object {
        fun fromProportionValue(proportion: Float) =
            values().firstOrNull { it.proportion == proportion }
    }
}
