-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Include RetrofitClient, Interceptors and two Composables
- Loading branch information
Showing
8 changed files
with
631 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
...mponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/ui/pager/PageIndicator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
) | ||
} | ||
} | ||
} |
192 changes: 192 additions & 0 deletions
192
jchucomponentscompose/src/main/java/com/jeluchu/jchucomponentscompose/ui/pager/Pager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.