Skip to content

Instantly share code, notes, and snippets.

@shakil807g
Last active January 19, 2022 01:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shakil807g/313f01dfaadb9e69d29beca261c0f5a2 to your computer and use it in GitHub Desktop.
Save shakil807g/313f01dfaadb9e69d29beca261c0f5a2 to your computer and use it in GitHub Desktop.
package com.facr.fotbal.common
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.lerp
import androidx.compose.ui.util.lerp
import com.facr.fotbal.R
import dev.chrisbanes.accompanist.insets.statusBarsPadding
import kotlin.math.max
import kotlin.math.min
private val MinImageOffset = 2.dp
private val MaxImageOffset = 30.dp
private val ExpandedImageWidth = 200.dp
private val ExpandedImageHeight = 80.dp
private val CollapsedImageWidth = 160.dp
private val CollapsedImageHeight = 50.dp
private val MinTitleOffset = 56.dp
private val TransparentSpace = 120.dp
private val GradientScroll = TransparentSpace - MinTitleOffset
private val MaxTitleOffset = MinTitleOffset + GradientScroll + 100.dp
private val HzPadding = Modifier.padding(horizontal = 24.dp)
@Composable
fun AnimatedHeaderLayout(
hideableHeader: @Composable (getAlpha: () -> Float) -> Unit,
body: @Composable ColumnScope.(getAlpha: () -> Float) -> Unit
) {
Box(Modifier.fillMaxSize()) {
val scroll = rememberScrollState(0)
val collapseRange = with(LocalDensity.current) { (MaxTitleOffset - MinTitleOffset).toPx() }
val collapseFraction by remember(collapseRange) {
derivedStateOf {
(scroll.value / collapseRange).coerceIn(
0f,
1f
)
}
}
val getAlpha = { (1f - collapseFraction).coerceIn(0f, 1f) }
/* val alphaFraction by remember(collapseRange) {
derivedStateOf {
(1f - collapseFraction).coerceIn(
0f,
1f
)
}
}*/
Spacer(
modifier = Modifier
.height(TransparentSpace)
.fillMaxWidth()
.background(MaterialTheme.colors.primary)
)
Column {
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(MinTitleOffset)
)
Column(
modifier = Modifier.verticalScroll(scroll)
) {
Spacer(Modifier.height(GradientScroll))
Box(modifier = Modifier.background(MaterialTheme.colors.primary)) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 100.dp)
.background(MaterialTheme.colors.background)
.align(Alignment.TopCenter)
.animateContentSize()
) {
body(getAlpha)
}
hideableHeader(getAlpha)
}
}
}
CollapsingImageLayout(
collapseFraction = collapseFraction,
modifier = HzPadding.then(Modifier.statusBarsPadding())
) {
Image(
ImageVector.vectorResource(id = R.drawable.app_logo),
contentScale = ContentScale.Fit,
modifier = Modifier.fillMaxSize(),
contentDescription = null
)
}
}
}
@Composable
private fun CollapsingImageLayout(
collapseFraction: Float,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
check(measurables.size == 1)
val imageMaxWidth = min(ExpandedImageWidth.roundToPx(), constraints.maxWidth)
val imageMinWidth = max(CollapsedImageWidth.roundToPx(), constraints.minWidth)
val imageWidth = lerp(imageMaxWidth, imageMinWidth, collapseFraction)
val imageMaxHeight = min(ExpandedImageHeight.roundToPx(), constraints.maxWidth)
val imageMinHeight = max(CollapsedImageHeight.roundToPx(), constraints.minWidth)
val imageHeight = lerp(imageMaxHeight, imageMinHeight, collapseFraction)
val imagePlaceable = measurables[0].measure(Constraints.fixed(imageWidth, imageHeight))
val imageY = lerp(MaxImageOffset, MinImageOffset, collapseFraction).roundToPx()
/*val imageX = lerp(
(constraints.maxWidth - imageWidth) / 2, // centered when expanded
constraints.maxWidth - imageWidth, // right aligned when collapsed
collapseFraction
)*/
val imageX = (constraints.maxWidth - imageWidth) / 2
layout(
width = constraints.maxWidth,
height = imageY + imageHeight
) {
imagePlaceable.place(imageX, imageY)
}
}
}
package com.facr.fotbal.ui.dashboard.home
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.*
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.facr.fotbal.R
import com.facr.fotbal.common.AnimatedHeaderLayout
import com.facr.fotbal.common.VerticleSpace
import com.facr.fotbal.domain.model.matches.Match
import com.facr.fotbal.ui.FacrViewModel
import com.facr.fotbal.ui.theme.facrTopAppBarHeading
import com.facr.fotbal.util.Constants
import com.facr.fotbal.util.Constants.Padding100dp
import com.facr.fotbal.util.Constants.Padding10dp
import com.facr.fotbal.util.Constants.Padding16dp
import com.facr.fotbal.util.Constants.Padding48dp
import com.facr.fotbal.util.Constants.Padding80dp
import com.facr.fotbal.util.Constants.Padding8dp
@ExperimentalFoundationApi
@ExperimentalAnimationApi
@ExperimentalMaterialApi
@Composable
fun HomeScreen(
facrViewModel: FacrViewModel,
onEventClicked: (Match) -> Unit
) {
val homeScreenViewModel: HomeScreenViewModel = viewModel()
val favClubMatches = homeScreenViewModel.favClubsUpCompingMatch.collectAsLazyPagingItems()
val favClubRecentMatches = homeScreenViewModel.favClubsRecentMatches.collectAsLazyPagingItems()
val selectedIndex = remember { mutableStateOf(0) }
AnimatedHeaderLayout(hideableHeader = { alphaFraction ->
UpcomingMatch(
favClubMatches,
onEventClicked,
getAlpha = alphaFraction
) { selectedIndex.value = it }
}) { alpha ->
Spacer(
modifier = Modifier.then(Modifier.requiredHeight(Padding100dp))
)
if (favClubMatches.loadState.refresh is LoadState.NotLoading && favClubMatches.itemCount > 1) {
Indicator(
getAlpha = alpha,
selectedIndex = selectedIndex.value,
itemSize = favClubMatches.itemCount
)
}
Text(
text = stringResource(R.string.dashboard_currentResult),
textAlign = TextAlign.Left,
color = MaterialTheme.colors.primary,
style = facrTopAppBarHeading(),
modifier = Modifier
.fillMaxWidth()
.padding(
top = Constants.Padding19dp,
bottom = Constants.Padding25dp,
start = Constants.Padding27dp,
end = Padding10dp
)
)
RecentMatchesResult(modifier = Modifier.fillMaxWidth(), favClubRecentMatches)
Text(
text = stringResource(R.string.dashboard_myTeams),
textAlign = TextAlign.Left,
color = MaterialTheme.colors.primary,
style = facrTopAppBarHeading(),
modifier = Modifier
.fillMaxWidth()
.padding(
start = Constants.Padding27dp,
end = Padding16dp,
top = Padding48dp,
bottom = Padding8dp
)
)
SelectedClubs(facrViewModel = facrViewModel, clubs = homeScreenViewModel.savedClubs)
Text(
text = stringResource(R.string.dashboard_myCompetitions),
textAlign = TextAlign.Left,
color = MaterialTheme.colors.primary,
style = facrTopAppBarHeading(),
modifier = Modifier
.fillMaxWidth()
.padding(
start = Constants.Padding27dp,
end = Padding16dp,
top = Padding16dp,
bottom = Padding8dp
)
)
SelectedCompetitions(
facrViewModel = facrViewModel,
homeScreenViewModel.savedCompetitions,
)
VerticleSpace(Padding80dp)
}
}
package com.facr.fotbal.ui.dashboard.home
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import com.facr.fotbal.common.Destination
import com.facr.fotbal.common.HorizontalSpace
import com.facr.fotbal.domain.model.matches.Match
import com.facr.fotbal.ui.LocalNavigator
import com.facr.fotbal.ui.item.FacrFinishedMatchItem
import com.facr.fotbal.ui.item.FacrUpComingMatchItem
import com.facr.fotbal.ui.item.PlaceHolderItem
import com.facr.fotbal.util.Constants.Padding16dp
import com.facr.fotbal.util.Constants.Padding4dp
import com.facr.fotbal.util.Pager
import com.facr.fotbal.util.PagerState
@Composable
fun UpcomingMatch(
matchesList: LazyPagingItems<Match>,
onEventClicked: (Match) -> Unit,
getAlpha: (() -> Float)? = null,
selectedIndex: (Int) -> Unit,
) {
val navigator = LocalNavigator.current!!
val homeScreenViewModel: HomeScreenViewModel = viewModel()
val matchEvents by homeScreenViewModel.savedMatchEvents.collectAsState(initial = emptySet())
val pagerState = remember() { PagerState() }
if (matchesList.loadState.refresh is LoadState.NotLoading && matchesList.itemCount > 0) {
pagerState.maxPage = matchesList.itemCount - 1
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
) {
Pager(
state = pagerState,
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
alpha = getAlpha?.invoke() ?: 1f
}
) {
selectedIndex(currentPage)
val item = matchesList[page]
item?.let {
if (it.isEventIcon) {
FacrUpComingMatchItem(
item,
onEventClicked = {
homeScreenViewModel.updateMatchEvent(matchEvents, it.id.toString())
onEventClicked(it)
},
isClickable = false,
matchEvent = matchEvents,
showEventIcon = item.isEventIcon
)
} else {
FacrFinishedMatchItem(match = it) {
navigator.navigate(Destination.MatchDetailScreen(it.id))
}
}
}
}
}
} else {
PlaceHolderItem()
}
}
@Composable
fun Indicator(
getAlpha: (() -> Float)? = null,
selectedIndex: Int,
maxItems: Int = 5,
itemSize: Int,
indicatorSize: Dp = 8.dp,
horizontalSpace: Dp = Padding4dp
) {
val finalSize = kotlin.math.min(maxItems, itemSize)
val selectedModIndex = selectedIndex % finalSize
Row(
modifier = Modifier
.fillMaxWidth()
.requiredWidth(200.dp)
.horizontalScroll(rememberScrollState())
.wrapContentSize(Alignment.Center)
.padding(top = Padding16dp, bottom = Padding16dp)
.graphicsLayer {
alpha = getAlpha?.invoke() ?: 1f
},
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
(0 until finalSize).forEach {
val color =
if (selectedModIndex == it) MaterialTheme.colors.primary else MaterialTheme.colors.surface
Box(
modifier = Modifier
.requiredSize(indicatorSize)
.clip(CircleShape)
.background(color = color)
)
HorizontalSpace(horizontalSpace)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment