Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix visibility calculation and update compose versions #2

Merged
merged 2 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix visibility calculation and update compose versions
  • Loading branch information
erolaksoy committed Mar 6, 2024
commit 063c6703edeb91c52672061844545b1ee1462075
2 changes: 1 addition & 1 deletion buildSrc/src/main/java/AppVersions.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const val PUBLISHING_GROUP = "com.erolaksoy"
const val PUBLISHING_ARTIFACT_ID = "compose-impression"
const val VERSION = "0.1"
const val VERSION = "0.1.1"
29 changes: 14 additions & 15 deletions gradle/libraries.versions.toml
Original file line number Diff line number Diff line change
@@ -1,42 +1,41 @@
[versions]
agp = "8.0.2"
androidx_activity_compose = "1.7.2"
agp = "8.2.2"
androidx_activity_compose = "1.8.2"
androidx_test = "1.5.0"
androidx_test_ext = "1.1.5"
appcompat = "1.6.1"
compile_sdk_version = "33"
compose = "1.4.3"
compose_compilerextension = "1.4.5"
constraint_layout = "2.1.4"
compile_sdk_version = "34"
compose = "1.6.2"
compose_compilerextension = "1.5.1"
core_ktx = "1.10.1"
detekt = "1.23.0"
dokka = "1.8.10"
espresso_core = "3.5.1"
ksp = "1.8.20-1.0.11"
junit = "4.13.2"
kotlin = "1.8.20"
kotlin = "1.9.0"
ktlint = "0.45.2"
ktlint_gradle = "10.2.1"
min_sdk_version = "21"
target_sdk_version = "33"
min_sdk_version = "24"
target_sdk_version = "34"

[libraries]
junit = { module = "junit:junit", version.ref = "junit" }
androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "androidx_activity_compose" }
androidx_appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx_core_ktx = { module = "androidx.core:core-ktx", version.ref = "core.ktx" }
androidx_test_rules = { module = "androidx.test:rules", version.ref = "androidx.test" }
androidx_test_runner = { module = "androidx.test:runner", version.ref = "androidx.test" }
androidx_test_ext_junit = { module = "androidx.test.ext:junit", version.ref = "androidx.test.ext" }
androidx_test_ext_junit_ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx.test.ext" }
androidx_test_rules = { module = "androidx.test:rules", version.ref = "androidx_test" }
androidx_test_runner = { module = "androidx.test:runner", version.ref = "androidx_test" }
androidx_test_ext_junit = { module = "androidx.test.ext:junit", version.ref = "androidx_test_ext" }
androidx_test_ext_junit_ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx_test_ext" }
compose_material = { module = "androidx.compose.material:material", version.ref = "compose" }
compose_foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
compose_ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose_ui_test_junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }
compose_ui_test_manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" }
detekt_formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
espressoCore = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso.core" }
espressoCore = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso_core" }
agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
kgp = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
dokkaCore = { module = "org.jetbrains.dokka:dokka-core", version.ref = "dokka" }
Expand All @@ -46,6 +45,6 @@ kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-pl
[plugins]
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
dokka = { id = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint.gradle" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint_gradle" }
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Tue Jun 06 20:46:26 TRT 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
51 changes: 41 additions & 10 deletions impression/src/main/java/com/erolaksoy/impression/Impression.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package com.erolaksoy.impression

import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.onGloballyPositioned
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
Expand All @@ -25,15 +25,34 @@ fun <T : Any> Modifier.impression(

val coroutineScope = rememberCoroutineScope()

LaunchedEffect(key) {
impressionState.seenEvent.collect {
if (key == it) {
onImpressionHappened(key)
DisposableEffect(key) {
val job = coroutineScope.launch {
impressionState.seenEvent.collect {
if (key == it) {
onImpressionHappened(key)
}
}
}
onDispose { job.cancel() }
}

onPlaced {
onGloballyPositioned {
coroutineScope.launch {
if (it.isAttached.not()) return@launch
impressionState.onItemPlaced(key = key)
}
}
}

@Stable
fun <T : Any> Modifier.impression(
key: T,
impressionState: ImpressionState,
) = composed {

val coroutineScope = rememberCoroutineScope()

onGloballyPositioned {
coroutineScope.launch {
if (it.isAttached.not()) return@launch
impressionState.onItemPlaced(key = key)
Expand All @@ -44,13 +63,17 @@ fun <T : Any> Modifier.impression(
@Composable
fun rememberImpressionState(
lazyListState: LazyListState,
block: ImpressionState.() -> Unit = {},
block: ImpressionState.() -> Unit = defaultImpressionStateBlock,
): ImpressionState {
return remember {
ImpressionStateImpl(lazyListState).apply(block)
}
}

internal val defaultImpressionStateBlock: ImpressionState.() -> Unit = {
addValidator(VisibilityPercentImpressionValidator())
}

class ImpressionStateImpl(private val state: LazyListState) : ImpressionState {
private val _seenEvent = MutableSharedFlow<Any>(extraBufferCapacity = 1)
override val seenEvent: SharedFlow<Any> = _seenEvent.asSharedFlow()
Expand All @@ -63,8 +86,12 @@ class ImpressionStateImpl(private val state: LazyListState) : ImpressionState {
override suspend fun onItemPlaced(key: Any) = withContext(Dispatchers.Default) {
if (_recordedItems.contains(key)) return@withContext

val filteredList = state.layoutInfo.visibleItemsInfo.filter {
_validators.any { it.isValid(state, key) }
val visibleItemsInfo = state.layoutInfo.visibleItemsInfo

val filteredList = if (_validators.isEmpty()) {
visibleItemsInfo
} else {
visibleItemsInfo.filter { _validators.any { it.isValid(state, key) } }
}

val isItemInList = filteredList.any { it.key == key }
Expand All @@ -79,6 +106,10 @@ class ImpressionStateImpl(private val state: LazyListState) : ImpressionState {
_validators.add(validator)
}

override fun clearValidators() {
_validators.clear()
}

override fun clearAll() {
_recordedItems.clear()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ interface ImpressionState {
*/
fun addValidator(validator: ImpressionValidator)

/**
* Removes all validators from the impression state.
*/
fun clearValidators()

/**
* Clears all state from the impression state.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import androidx.compose.foundation.lazy.LazyListItemInfo
import androidx.compose.foundation.lazy.LazyListState

class VisibilityPercentImpressionValidator(
@FloatRange(0.0, 1.0) private val visibilityPercentThreshold: Float,
@FloatRange(0.0, 1.0) private val visibilityPercentThreshold: Float = VISIBILITY_THRESHOLD,
) : ImpressionValidator {

override suspend fun isValid(state: LazyListState, key: Any): Boolean {
Expand All @@ -23,6 +23,11 @@ class VisibilityPercentImpressionValidator(
private fun LazyListState.visibilityPercent(itemInfo: LazyListItemInfo): Float {
val start = (layoutInfo.viewportStartOffset - itemInfo.offset).coerceAtLeast(0)
val end = (itemInfo.offset + itemInfo.size - layoutInfo.viewportEndOffset).coerceAtLeast(0)
return (1f - (start + end).toFloat() / itemInfo.size).coerceAtLeast(0f)
val visibleArea = maxOf(0f, (start + end).toFloat())
return 1f - (visibleArea / itemInfo.size.toFloat())
}

companion object {
private const val VISIBILITY_THRESHOLD = 0.5F
}
}