Skip to content

Commit

Permalink
Include RetrofitClient, Interceptors and two Composables
Browse files Browse the repository at this point in the history
  • Loading branch information
jeluchu committed Sep 5, 2021
1 parent 3f3aa01 commit 3ac6a3f
Show file tree
Hide file tree
Showing 8 changed files with 631 additions and 2 deletions.
4 changes: 2 additions & 2 deletions jchucomponentscompose/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ dependencies {
implementation 'androidx.compose.runtime:runtime:1.0.1'
implementation 'androidx.compose.runtime:runtime-livedata:1.0.1'
implementation 'androidx.constraintlayout:constraintlayout-compose:1.0.0-beta02'
implementation 'androidx.navigation:navigation-compose:2.4.0-alpha07'
implementation 'androidx.navigation:navigation-compose:2.4.0-alpha08'

// FIREBASE LIBRARY ----------------------------------------------------------------------------
implementation("com.google.firebase:firebase-analytics-ktx:19.0.1")
Expand Down Expand Up @@ -92,7 +92,7 @@ afterEvaluate {
from components.release
groupId = "com.jeluchu"
artifactId = "jchucomponentscompose"
version = "0.1.1"
version = "0.2.1"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.jeluchu.jchucomponentscompose.ui.pager

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun PageIndicator(
pagesCount: Int,
currentPageIndex: Int,
color: Color,
modifier: Modifier = Modifier
) {
Row(modifier = modifier.wrapContentSize()) {
for (pageIndex in 0 until pagesCount) {
val (tint, width) = if (currentPageIndex == pageIndex) {
color to 16.dp
} else {
color.copy(alpha = 0.5f) to 4.dp
}
Spacer(
modifier = Modifier
.padding(4.dp)
.height(4.dp)
.width(width)
.background(tint, RoundedCornerShape(percent = 50))
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package com.jeluchu.jchucomponentscompose.ui.pager

import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.ParentDataModifier
import androidx.compose.ui.unit.Density
import kotlinx.coroutines.launch
import kotlin.math.roundToInt

class PagerState(
currentPage: Int = 0,
minPage: Int = 0,
maxPage: Int = 0
) {
private var _minPage by mutableStateOf(minPage)
var minPage: Int
get() = _minPage
set(value) {
_minPage = value.coerceAtMost(_maxPage)
_currentPage = _currentPage.coerceIn(_minPage, _maxPage)
}

private var _maxPage by mutableStateOf(maxPage, structuralEqualityPolicy())
var maxPage: Int
get() = _maxPage
set(value) {
_maxPage = value.coerceAtLeast(_minPage)
_currentPage = _currentPage.coerceIn(_minPage, maxPage)
}

private var _currentPage by mutableStateOf(currentPage.coerceIn(minPage, maxPage))
var currentPage: Int
get() = _currentPage
set(value) {
_currentPage = value.coerceIn(minPage, maxPage)
}

enum class SelectionState { Selected, Undecided }

var selectionState by mutableStateOf(SelectionState.Selected)

suspend inline fun <R> selectPage(block: PagerState.() -> R): R = try {
selectionState = SelectionState.Undecided
block()
} finally {
selectPage()
}

suspend fun selectPage() {
currentPage -= currentPageOffset.roundToInt()
snapToOffset(0f)
selectionState = SelectionState.Selected
}

private var _currentPageOffset = Animatable(0f).apply {
updateBounds(-1f, 1f)
}
val currentPageOffset: Float
get() = _currentPageOffset.value

suspend fun snapToOffset(offset: Float) {
val max = if (currentPage == minPage) 0f else 1f
val min = if (currentPage == maxPage) 0f else -1f
_currentPageOffset.snapTo(offset.coerceIn(min, max))
}

suspend fun fling(velocity: Float) {
if (velocity < 0 && currentPage == maxPage) return
if (velocity > 0 && currentPage == minPage) return

_currentPageOffset.animateTo(currentPageOffset.roundToInt().toFloat())
selectPage()
}

override fun toString(): String = "PagerState{minPage=$minPage, maxPage=$maxPage, " +
"currentPage=$currentPage, currentPageOffset=$currentPageOffset}"
}

@Immutable
private data class PageData(val page: Int) : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?): Any = this@PageData
}

private val Measurable.page: Int
get() = (parentData as? PageData)?.page ?: error("no PageData for measurable $this")

@Composable
fun Pager(
state: PagerState,
modifier: Modifier = Modifier,
offscreenLimit: Int = 2,
pageContent: @Composable PagerScope.() -> Unit
) {
var pageSize by remember { mutableStateOf(0) }
val coroutineScope = rememberCoroutineScope()
Layout(
content = {
val minPage = (state.currentPage - offscreenLimit).coerceAtLeast(state.minPage)
val maxPage = (state.currentPage + offscreenLimit).coerceAtMost(state.maxPage)

for (page in minPage..maxPage) {
val pageData = PageData(page)
val scope = PagerScope(state, page)
key(pageData) {
Box(contentAlignment = Alignment.Center, modifier = pageData) {
scope.pageContent()
}
}
}
},
modifier = modifier.draggable(
orientation = Orientation.Horizontal,
onDragStarted = {
state.selectionState = PagerState.SelectionState.Undecided
},
onDragStopped = { velocity ->
coroutineScope.launch {
// Velocity is in pixels per second, but we deal in percentage offsets, so we
// need to scale the velocity to match
state.fling(velocity / pageSize)
}
},
state = rememberDraggableState { dy ->
coroutineScope.launch {
with(state) {
val pos = pageSize * currentPageOffset
val max = if (currentPage == minPage) 0 else pageSize * offscreenLimit
val min = if (currentPage == maxPage) 0 else -pageSize * offscreenLimit
val newPos = (pos + dy).coerceIn(min.toFloat(), max.toFloat())
snapToOffset(newPos / pageSize)
}
}
},
)
) { measurables, constraints ->
layout(constraints.maxWidth, constraints.maxHeight) {
val currentPage = state.currentPage
val offset = state.currentPageOffset
val childConstraints = constraints.copy(minWidth = 0, minHeight = 0)

measurables
.map {
it.measure(childConstraints) to it.page
}
.forEach { (placeable, page) ->
val xCenterOffset = (constraints.maxWidth - placeable.width) / 2
val yCenterOffset = (constraints.maxHeight - placeable.height) / 2

if (currentPage == page) {
pageSize = placeable.width
}

val xItemOffset = ((page + offset - currentPage) * placeable.width).roundToInt()

placeable.place(
x = xCenterOffset + xItemOffset,
y = yCenterOffset
)
}
}
}
}

class PagerScope(
private val state: PagerState,
val page: Int
) {
val currentPage: Int
get() = state.currentPage

val currentPageOffset: Float
get() = state.currentPageOffset

val selectionState: PagerState.SelectionState
get() = state.selectionState
}
Loading

0 comments on commit 3ac6a3f

Please sign in to comment.