Merge pull request 'develop' (#5) from develop into master
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #5master
commit
a75ba9451a
@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">Allin_DEBUG</string>
|
<string name="app_name" translatable="false">Allin DEBUG</string>
|
||||||
</resources>
|
</resources>
|
@ -1,20 +0,0 @@
|
|||||||
package fr.iut.alldev.allin.di
|
|
||||||
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import fr.iut.alldev.allin.data.repository.UserRepository
|
|
||||||
import javax.inject.Qualifier
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class AllInCurrentUser
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
internal object CurrentUserModule {
|
|
||||||
@AllInCurrentUser
|
|
||||||
@Provides
|
|
||||||
fun provideUser(userRepository: UserRepository) = userRepository.currentUser
|
|
||||||
}
|
|
@ -0,0 +1,18 @@
|
|||||||
|
package fr.iut.alldev.allin.ext
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.BetFilter
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.BetFilter.FINISHED
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.BetFilter.INVITATION
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.BetFilter.IN_PROGRESS
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.BetFilter.PUBLIC
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
fun BetFilter.textId() =
|
||||||
|
when (this) {
|
||||||
|
PUBLIC -> R.string.bet_public
|
||||||
|
INVITATION -> R.string.bet_invitation
|
||||||
|
IN_PROGRESS -> R.string.bet_current
|
||||||
|
FINISHED -> R.string.bet_finished
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package fr.iut.alldev.allin.ext
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.safeContent
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
operator fun PaddingValues.plus(paddingValues: PaddingValues): PaddingValues {
|
||||||
|
val direction = LocalLayoutDirection.current
|
||||||
|
return PaddingValues(
|
||||||
|
top = this.calculateTopPadding() + paddingValues.calculateTopPadding(),
|
||||||
|
bottom = this.calculateBottomPadding() + paddingValues.calculateBottomPadding(),
|
||||||
|
start = this.calculateStartPadding(direction) + paddingValues.calculateStartPadding(direction),
|
||||||
|
end = this.calculateEndPadding(direction) + paddingValues.calculateEndPadding(direction),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
fun WindowInsets.asPaddingValues(top: Dp = 0.dp, bottom: Dp = 0.dp, start: Dp = 0.dp, end: Dp = 0.dp): PaddingValues =
|
||||||
|
this.asPaddingValues() + PaddingValues(start, top, end, bottom)
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
fun WindowInsets.asPaddingValues(horizontal: Dp = 0.dp, vertical: Dp = 0.dp): PaddingValues =
|
||||||
|
this.asPaddingValues() + PaddingValues(horizontal, vertical)
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
fun WindowInsets.asPaddingValues(all: Dp = 0.dp): PaddingValues = this.asPaddingValues() + PaddingValues(all)
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
fun WindowInsets.takeBottomOnly(): WindowInsets {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
return WindowInsets(bottom = this.getBottom(density))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
fun WindowInsets.takeTopOnly(): WindowInsets {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
return WindowInsets(top = this.getTop(density))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun bottomSheetNavigationBarsInsets(): WindowInsets {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val navBar = WindowInsets.navigationBars
|
||||||
|
if (navBar.getBottom(density) == 0) {
|
||||||
|
val safeContent = WindowInsets.safeContent
|
||||||
|
if (navBar.getBottom(density) == 0) {
|
||||||
|
return WindowInsets(bottom = 40.dp)
|
||||||
|
}
|
||||||
|
return safeContent.takeBottomOnly()
|
||||||
|
}
|
||||||
|
return navBar
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun bottomSheetNavigationBarsPadding(): PaddingValues = bottomSheetNavigationBarsInsets().asPaddingValues()
|
@ -1,138 +1,37 @@
|
|||||||
package fr.iut.alldev.allin.ui.bet
|
package fr.iut.alldev.allin.ui.bet
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material.pullrefresh.pullRefresh
|
|
||||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import fr.iut.alldev.allin.R
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
|
|
||||||
import fr.iut.alldev.allin.data.ext.formatToTime
|
|
||||||
import fr.iut.alldev.allin.data.model.bet.Bet
|
import fr.iut.alldev.allin.data.model.bet.Bet
|
||||||
import fr.iut.alldev.allin.theme.AllInTheme
|
import fr.iut.alldev.allin.ui.bet.components.BetScreenLoadedContent
|
||||||
import fr.iut.alldev.allin.ui.bet.components.BetScreenCard
|
import fr.iut.alldev.allin.ui.core.AllInLoading
|
||||||
import fr.iut.alldev.allin.ui.bet.components.BetScreenPopularCard
|
|
||||||
import fr.iut.alldev.allin.ui.core.AllInChip
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BetScreen(
|
fun BetScreen(
|
||||||
viewModel: BetViewModel = hiltViewModel(),
|
viewModel: BetViewModel = hiltViewModel(),
|
||||||
selectBet: (Bet, Boolean) -> Unit,
|
selectBet: (Bet, Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
val bets by viewModel.bets.collectAsState()
|
val filters by viewModel.filters.collectAsStateWithLifecycle()
|
||||||
|
val isRefreshing by viewModel.refreshing.collectAsStateWithLifecycle()
|
||||||
val horizontalPadding = 23.dp
|
|
||||||
|
when (val s = state) {
|
||||||
val refreshing by viewModel.isRefreshing.collectAsState()
|
is BetViewModel.State.Loaded -> {
|
||||||
val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.refresh() })
|
BetScreenLoadedContent(
|
||||||
|
popularBet = s.popularBet,
|
||||||
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
|
filters = filters,
|
||||||
|
bets = s.bets,
|
||||||
LazyColumn(
|
isRefreshing = isRefreshing,
|
||||||
Modifier
|
selectBet = selectBet,
|
||||||
.pullRefresh(pullRefreshState)
|
toggleFilter = { viewModel.toggleFilter(it) },
|
||||||
.padding(top = with(LocalDensity.current) {
|
refreshData = { viewModel.refreshData() }
|
||||||
progressAnimation.toDp()
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Box(
|
|
||||||
Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
BetScreenPopularCard(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 13.dp, bottom = 10.dp)
|
|
||||||
.padding(horizontal = 13.dp),
|
|
||||||
nbPlayers = 12,
|
|
||||||
points = 2.35f,
|
|
||||||
pointUnit = "k",
|
|
||||||
title = "Emre va réussir son TP de CI/CD mercredi?"
|
|
||||||
)
|
|
||||||
PullRefreshIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.TopCenter),
|
|
||||||
refreshing = refreshing,
|
|
||||||
state = pullRefreshState
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stickyHeader {
|
|
||||||
LazyRow(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(
|
|
||||||
Brush.verticalGradient(
|
|
||||||
0.5f to AllInTheme.themeColors.mainSurface,
|
|
||||||
1f to Color.Transparent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.padding(top = 5.dp, bottom = 19.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(9.dp)
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.width(horizontalPadding))
|
|
||||||
}
|
|
||||||
items(items) {
|
|
||||||
var isSelected by remember { mutableStateOf(false) }
|
|
||||||
AllInChip(
|
|
||||||
text = stringResource(id = it),
|
|
||||||
isSelected = isSelected,
|
|
||||||
onClick = {
|
|
||||||
isSelected = !isSelected
|
|
||||||
})
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
Spacer(modifier = Modifier.width(horizontalPadding))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items(bets) {
|
|
||||||
BetScreenCard(
|
|
||||||
creator = it.creator,
|
|
||||||
category = it.theme,
|
|
||||||
title = it.phrase,
|
|
||||||
date = it.endRegisterDate.formatToMediumDateNoYear(),
|
|
||||||
time = it.endRegisterDate.formatToTime(),
|
|
||||||
players = List(3) { null },
|
|
||||||
onClickParticipate = { selectBet(it, true) },
|
|
||||||
onClickCard = { selectBet(it, false) },
|
|
||||||
modifier = Modifier.padding(horizontal = horizontalPadding)
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val items = listOf(
|
BetViewModel.State.Loading -> {
|
||||||
R.string.Public,
|
AllInLoading(visible = true)
|
||||||
R.string.Invitation,
|
}
|
||||||
R.string.Current,
|
}
|
||||||
R.string.Finished
|
}
|
||||||
)
|
|
@ -0,0 +1,205 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.bet.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
|
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.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
|
||||||
|
import fr.iut.alldev.allin.data.ext.formatToTime
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.Bet
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.BetFilter
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.BetStatus
|
||||||
|
import fr.iut.alldev.allin.data.model.bet.BinaryBet
|
||||||
|
import fr.iut.alldev.allin.ext.textId
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
import fr.iut.alldev.allin.ui.core.AllInChip
|
||||||
|
import fr.iut.alldev.allin.ui.core.bet.AllInEmptyView
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
private const val DISABLED_OPACITY = .5f
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BetScreenLoadedContent(
|
||||||
|
popularBet: Bet?,
|
||||||
|
filters: List<BetFilter>,
|
||||||
|
bets: List<Bet>,
|
||||||
|
isRefreshing: Boolean,
|
||||||
|
selectBet: (Bet, Boolean) -> Unit,
|
||||||
|
toggleFilter: (BetFilter) -> Unit,
|
||||||
|
refreshData: () -> Unit
|
||||||
|
) {
|
||||||
|
val pullRefreshState = rememberPullRefreshState(isRefreshing, refreshData)
|
||||||
|
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
|
||||||
|
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.pullRefresh(pullRefreshState)
|
||||||
|
.padding(top = with(LocalDensity.current) {
|
||||||
|
progressAnimation.toDp()
|
||||||
|
}),
|
||||||
|
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
|
||||||
|
) {
|
||||||
|
popularBet?.let {
|
||||||
|
item {
|
||||||
|
Box(Modifier.fillMaxWidth()) {
|
||||||
|
BetScreenPopularCard(
|
||||||
|
nbPlayers = it.totalParticipants,
|
||||||
|
points = it.totalStakes,
|
||||||
|
title = it.phrase,
|
||||||
|
onClick = { selectBet(it, false) },
|
||||||
|
enabled = !isRefreshing,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 13.dp, bottom = 10.dp)
|
||||||
|
.padding(horizontal = 13.dp)
|
||||||
|
.let {
|
||||||
|
if (isRefreshing) it.alpha(DISABLED_OPACITY)
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stickyHeader {
|
||||||
|
LazyRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
0.5f to AllInTheme.colors.mainSurface,
|
||||||
|
1f to Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.zIndex(1f),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(9.dp),
|
||||||
|
contentPadding = PaddingValues(horizontal = 23.dp)
|
||||||
|
) {
|
||||||
|
items(BetFilter.entries) {
|
||||||
|
val isSelected by remember(filters) {
|
||||||
|
derivedStateOf {
|
||||||
|
filters.contains(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AllInChip(
|
||||||
|
text = stringResource(id = it.textId()),
|
||||||
|
isSelected = isSelected,
|
||||||
|
onClick = { toggleFilter(it) },
|
||||||
|
enabled = !isRefreshing,
|
||||||
|
modifier = Modifier.let {
|
||||||
|
if (isRefreshing) it.alpha(DISABLED_OPACITY)
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemsIndexed(
|
||||||
|
items = bets,
|
||||||
|
key = { _, it -> it.id }
|
||||||
|
) { idx, it ->
|
||||||
|
BetScreenCard(
|
||||||
|
creator = it.creator,
|
||||||
|
category = it.theme,
|
||||||
|
title = it.phrase,
|
||||||
|
date = it.endRegisterDate.formatToMediumDateNoYear(),
|
||||||
|
time = it.endRegisterDate.formatToTime(),
|
||||||
|
players = emptyList(), // TODO : Players
|
||||||
|
totalParticipants = it.totalParticipants,
|
||||||
|
onClickParticipate = { selectBet(it, true) },
|
||||||
|
onClickCard = { selectBet(it, false) },
|
||||||
|
enabled = !isRefreshing,
|
||||||
|
modifier = Modifier
|
||||||
|
.animateItemPlacement()
|
||||||
|
.padding(horizontal = 23.dp)
|
||||||
|
.let {
|
||||||
|
if (isRefreshing) it.alpha(DISABLED_OPACITY)
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (idx != bets.lastIndex) {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bets.isEmpty()) {
|
||||||
|
item {
|
||||||
|
AllInEmptyView(
|
||||||
|
text = stringResource(id = R.string.bet_empty_text),
|
||||||
|
subtext = null,
|
||||||
|
image = painterResource(id = R.drawable.video_game),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillParentMaxHeight(.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter),
|
||||||
|
refreshing = isRefreshing,
|
||||||
|
state = pullRefreshState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BetScreenLoadedContentPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
BetScreenLoadedContent(
|
||||||
|
popularBet = BinaryBet(
|
||||||
|
id = "Arleen",
|
||||||
|
creator = "Omar",
|
||||||
|
theme = "Kyli",
|
||||||
|
phrase = "Leigha",
|
||||||
|
endRegisterDate = ZonedDateTime.now(),
|
||||||
|
endBetDate = ZonedDateTime.now(),
|
||||||
|
isPublic = false,
|
||||||
|
betStatus = BetStatus.IN_PROGRESS,
|
||||||
|
totalParticipants = 200,
|
||||||
|
totalStakes = 2500
|
||||||
|
),
|
||||||
|
filters = emptyList(),
|
||||||
|
bets = emptyList(),
|
||||||
|
isRefreshing = true,
|
||||||
|
selectBet = { _, _ -> },
|
||||||
|
toggleFilter = { },
|
||||||
|
refreshData = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.betCreation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeContentPadding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.data.model.User
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
import fr.iut.alldev.allin.ui.betCreation.BetCreationViewModel
|
||||||
|
import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenAnswerTab
|
||||||
|
import fr.iut.alldev.allin.ui.betCreation.tabs.BetCreationScreenQuestionTab
|
||||||
|
import fr.iut.alldev.allin.ui.core.AllInSections
|
||||||
|
import fr.iut.alldev.allin.ui.core.RainbowButton
|
||||||
|
import fr.iut.alldev.allin.ui.core.SectionElement
|
||||||
|
import fr.iut.alldev.allin.ui.core.SelectionElement
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BetCreationScreenContent(
|
||||||
|
betTheme: String,
|
||||||
|
betThemeError: String?,
|
||||||
|
setBetTheme: (String) -> Unit,
|
||||||
|
betPhrase: String,
|
||||||
|
betPhraseError: String?,
|
||||||
|
setBetPhrase: (String) -> Unit,
|
||||||
|
isPrivate: Boolean,
|
||||||
|
setIsPrivate: (Boolean) -> Unit,
|
||||||
|
registerDate: ZonedDateTime,
|
||||||
|
registerDateError: String?,
|
||||||
|
betDate: ZonedDateTime,
|
||||||
|
betDateError: String?,
|
||||||
|
friends: List<User>,
|
||||||
|
selectedFriends: List<String>,
|
||||||
|
setRegisterDateDialog: (Boolean) -> Unit,
|
||||||
|
setEndDateDialog: (Boolean) -> Unit,
|
||||||
|
setRegisterTimeDialog: (Boolean) -> Unit,
|
||||||
|
setEndTimeDialog: (Boolean) -> Unit,
|
||||||
|
selectedBetTypeElement: SelectionElement?,
|
||||||
|
selectedBetType: BetCreationViewModel.BetTypeState,
|
||||||
|
typeError: String?,
|
||||||
|
setSelectedBetTypeElement: (SelectionElement) -> Unit,
|
||||||
|
selectionBetType: List<SelectionElement>,
|
||||||
|
addAnswer: (String) -> Unit,
|
||||||
|
deleteAnswer: (String) -> Unit,
|
||||||
|
onCreateBet: () -> Unit,
|
||||||
|
toggleFriend: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val focus = LocalFocusManager.current
|
||||||
|
|
||||||
|
Box(Modifier.fillMaxSize()) {
|
||||||
|
AllInSections(
|
||||||
|
onLoadSection = { focus.clearFocus() },
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter),
|
||||||
|
sections = listOf(
|
||||||
|
SectionElement(stringResource(id = R.string.bet_creation_question)) {
|
||||||
|
BetCreationScreenQuestionTab(
|
||||||
|
isPrivate = isPrivate,
|
||||||
|
setIsPrivate = setIsPrivate,
|
||||||
|
betPhrase = betPhrase,
|
||||||
|
setBetPhrase = setBetPhrase,
|
||||||
|
betTheme = betTheme,
|
||||||
|
setBetTheme = setBetTheme,
|
||||||
|
friends = friends,
|
||||||
|
selectedFriends = selectedFriends,
|
||||||
|
registerDate = registerDate,
|
||||||
|
betDate = betDate,
|
||||||
|
setRegisterDateDialog = setRegisterDateDialog,
|
||||||
|
setEndDateDialog = setEndDateDialog,
|
||||||
|
setRegisterTimeDialog = setRegisterTimeDialog,
|
||||||
|
setEndTimeDialog = setEndTimeDialog,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
betThemeError = betThemeError,
|
||||||
|
betPhraseError = betPhraseError,
|
||||||
|
registerDateError = registerDateError,
|
||||||
|
betDateError = betDateError,
|
||||||
|
toggleFriend = toggleFriend
|
||||||
|
)
|
||||||
|
},
|
||||||
|
SectionElement(stringResource(id = R.string.bet_creation_answer)) {
|
||||||
|
BetCreationScreenAnswerTab(
|
||||||
|
selectedBetType = selectedBetType,
|
||||||
|
selected = selectedBetTypeElement,
|
||||||
|
setSelected = setSelectedBetTypeElement,
|
||||||
|
elements = selectionBetType,
|
||||||
|
addAnswer = addAnswer,
|
||||||
|
deleteAnswer = deleteAnswer,
|
||||||
|
typeError = typeError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
0f to Color.Transparent,
|
||||||
|
0.50f to AllInTheme.colors.mainSurface
|
||||||
|
)
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
RainbowButton(
|
||||||
|
text = stringResource(id = R.string.bet_creation_publish),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = 14.dp)
|
||||||
|
.padding(horizontal = 20.dp)
|
||||||
|
.safeContentPadding(),
|
||||||
|
onClick = onCreateBet,
|
||||||
|
enabled = !isPrivate || selectedFriends.isNotEmpty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BetCreationScreenContentPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
BetCreationScreenContent(
|
||||||
|
friends = emptyList(),
|
||||||
|
betTheme = "Ina",
|
||||||
|
betThemeError = null,
|
||||||
|
setBetTheme = { },
|
||||||
|
betPhrase = "Bryon",
|
||||||
|
typeError = null,
|
||||||
|
betPhraseError = null,
|
||||||
|
setBetPhrase = { },
|
||||||
|
isPrivate = false,
|
||||||
|
setIsPrivate = { },
|
||||||
|
registerDate = ZonedDateTime.now(),
|
||||||
|
registerDateError = null,
|
||||||
|
betDate = ZonedDateTime.now(),
|
||||||
|
betDateError = null,
|
||||||
|
selectedFriends = mutableListOf(),
|
||||||
|
setRegisterDateDialog = { },
|
||||||
|
setEndDateDialog = { },
|
||||||
|
setRegisterTimeDialog = { },
|
||||||
|
setEndTimeDialog = { },
|
||||||
|
selectedBetTypeElement = null,
|
||||||
|
selectedBetType = BetCreationViewModel.BetTypeState.Binary,
|
||||||
|
setSelectedBetTypeElement = { },
|
||||||
|
selectionBetType = listOf(),
|
||||||
|
onCreateBet = { },
|
||||||
|
addAnswer = { },
|
||||||
|
deleteAnswer = { },
|
||||||
|
toggleFriend = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.betCreation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BetCreationScreenCustomAnswer(
|
||||||
|
text: String,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = AbsoluteSmoothCornerShape(15.dp, 100),
|
||||||
|
color = AllInColorToken.allInPurple,
|
||||||
|
contentColor = AllInColorToken.white
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.padding(start = 8.dp, end = 2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = AllInTheme.typography.h1
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = onDelete,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BetCreationScreenCustomAnswerPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
BetCreationScreenCustomAnswer(
|
||||||
|
text = "Text",
|
||||||
|
onDelete = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.betCreation.components
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
import fr.iut.alldev.allin.ui.core.AllInButton
|
||||||
|
import fr.iut.alldev.allin.ui.core.AllInTextField
|
||||||
|
import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BetCreationScreenCustomAnswerTextField(
|
||||||
|
value: String,
|
||||||
|
setValue: (String) -> Unit,
|
||||||
|
enabled: Boolean,
|
||||||
|
buttonEnabled: Boolean,
|
||||||
|
onAdd: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
AllInTextField(
|
||||||
|
value = value,
|
||||||
|
enabled = enabled,
|
||||||
|
modifier = modifier,
|
||||||
|
onValueChange = setValue,
|
||||||
|
maxChar = 15,
|
||||||
|
trailingContent = {
|
||||||
|
AllInButton(
|
||||||
|
color = AllInColorToken.allInPurple,
|
||||||
|
enabled = enabled && buttonEnabled,
|
||||||
|
text = stringResource(id = R.string.generic_add),
|
||||||
|
textColor = AllInColorToken.white,
|
||||||
|
shape = AbsoluteSmoothCornerShape(
|
||||||
|
cornerRadiusTR = 10.dp,
|
||||||
|
cornerRadiusBR = 10.dp,
|
||||||
|
smoothnessAsPercentTR = 100,
|
||||||
|
smoothnessAsPercentBR = 100
|
||||||
|
),
|
||||||
|
onClick = onAdd
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BetCreationScreenCustomAnswerTextFieldPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
BetCreationScreenCustomAnswerTextField(
|
||||||
|
onAdd = {},
|
||||||
|
enabled = true,
|
||||||
|
buttonEnabled = true,
|
||||||
|
value = "Test",
|
||||||
|
setValue = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BetCreationScreenCustomAnswerDisabledTextFieldPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
BetCreationScreenCustomAnswerTextField(
|
||||||
|
onAdd = {},
|
||||||
|
enabled = false,
|
||||||
|
buttonEnabled = false,
|
||||||
|
value = "Test",
|
||||||
|
setValue = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,73 +1,117 @@
|
|||||||
package fr.iut.alldev.allin.ui.betCreation.tabs
|
package fr.iut.alldev.allin.ui.betCreation.tabs
|
||||||
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import fr.iut.alldev.allin.data.ext.formatToMediumDate
|
import fr.iut.alldev.allin.data.ext.formatToMediumDate
|
||||||
import fr.iut.alldev.allin.data.ext.formatToTime
|
import fr.iut.alldev.allin.data.model.User
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabDateTimeSection
|
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabDateTimeSection
|
||||||
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabPrivacySection
|
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabPrivacySection
|
||||||
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabThemePhraseSection
|
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabThemePhraseSection
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
import fr.iut.alldev.allin.data.ext.formatToTime as formatToTime1
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BetCreationScreenQuestionTab(
|
fun BetCreationScreenQuestionTab(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
nbFriends: Int,
|
friends: List<User>,
|
||||||
betTheme: String,
|
betTheme: String,
|
||||||
betThemeError: String?,
|
betThemeError: String?,
|
||||||
setBetTheme: (String) -> Unit,
|
setBetTheme: (String) -> Unit,
|
||||||
betPhrase: String,
|
betPhrase: String,
|
||||||
betPhraseError: String?,
|
betPhraseError: String?,
|
||||||
setBetPhrase: (String) -> Unit,
|
setBetPhrase: (String) -> Unit,
|
||||||
isPublic: Boolean,
|
isPrivate: Boolean,
|
||||||
setIsPublic: (Boolean) -> Unit,
|
setIsPrivate: (Boolean) -> Unit,
|
||||||
registerDate: ZonedDateTime,
|
registerDate: ZonedDateTime,
|
||||||
registerDateError: String?,
|
registerDateError: String?,
|
||||||
betDate: ZonedDateTime,
|
betDate: ZonedDateTime,
|
||||||
betDateError: String?,
|
betDateError: String?,
|
||||||
selectedFriends: MutableList<Int>,
|
selectedFriends: List<String>,
|
||||||
setRegisterDateDialog: (Boolean) -> Unit,
|
setRegisterDateDialog: (Boolean) -> Unit,
|
||||||
setEndDateDialog: (Boolean) -> Unit,
|
setEndDateDialog: (Boolean) -> Unit,
|
||||||
setRegisterTimeDialog: (Boolean) -> Unit,
|
setRegisterTimeDialog: (Boolean) -> Unit,
|
||||||
setEndTimeDialog: (Boolean) -> Unit,
|
setEndTimeDialog: (Boolean) -> Unit,
|
||||||
|
toggleFriend: (String) -> Unit,
|
||||||
interactionSource: MutableInteractionSource
|
interactionSource: MutableInteractionSource
|
||||||
) {
|
) {
|
||||||
Column(modifier) {
|
LazyColumn(
|
||||||
QuestionTabThemePhraseSection(
|
modifier = modifier,
|
||||||
betTheme = betTheme,
|
contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 120.dp),
|
||||||
betThemeError = betThemeError,
|
verticalArrangement = Arrangement.spacedBy(35.dp)
|
||||||
setBetTheme = setBetTheme,
|
) {
|
||||||
betPhrase = betPhrase,
|
item {
|
||||||
betPhraseError = betPhraseError,
|
QuestionTabThemePhraseSection(
|
||||||
setBetPhrase = setBetPhrase,
|
betTheme = betTheme,
|
||||||
interactionSource = interactionSource
|
betThemeError = betThemeError,
|
||||||
)
|
setBetTheme = setBetTheme,
|
||||||
Spacer(modifier = Modifier.height(35.dp))
|
betPhrase = betPhrase,
|
||||||
QuestionTabDateTimeSection(
|
betPhraseError = betPhraseError,
|
||||||
registerDate = registerDate.formatToMediumDate(),
|
setBetPhrase = setBetPhrase,
|
||||||
registerTime = registerDate.formatToTime(),
|
interactionSource = interactionSource
|
||||||
registerDateError = registerDateError,
|
)
|
||||||
betDateError = betDateError,
|
}
|
||||||
endDate = betDate.formatToMediumDate(),
|
item {
|
||||||
endTime = betDate.formatToTime(),
|
QuestionTabDateTimeSection(
|
||||||
setEndDateDialog = setEndDateDialog,
|
registerDate = registerDate.formatToMediumDate(),
|
||||||
setRegisterDateDialog = setRegisterDateDialog,
|
registerTime = registerDate.formatToTime1(),
|
||||||
setRegisterTimeDialog = setRegisterTimeDialog,
|
registerDateError = registerDateError,
|
||||||
setEndTimeDialog = setEndTimeDialog,
|
betDateError = betDateError,
|
||||||
interactionSource = interactionSource,
|
endDate = betDate.formatToMediumDate(),
|
||||||
)
|
endTime = betDate.formatToTime1(),
|
||||||
Spacer(modifier = Modifier.height(35.dp))
|
setEndDateDialog = setEndDateDialog,
|
||||||
QuestionTabPrivacySection(
|
setRegisterDateDialog = setRegisterDateDialog,
|
||||||
isPublic = isPublic,
|
setRegisterTimeDialog = setRegisterTimeDialog,
|
||||||
setIsPublic = setIsPublic,
|
setEndTimeDialog = setEndTimeDialog,
|
||||||
nbFriends = nbFriends,
|
interactionSource = interactionSource,
|
||||||
selectedFriends = selectedFriends,
|
)
|
||||||
interactionSource = interactionSource
|
}
|
||||||
|
item {
|
||||||
|
QuestionTabPrivacySection(
|
||||||
|
isPrivate = isPrivate,
|
||||||
|
setIsPrivate = setIsPrivate,
|
||||||
|
friends = friends,
|
||||||
|
selectedFriends = selectedFriends,
|
||||||
|
toggleFriend = toggleFriend,
|
||||||
|
interactionSource = interactionSource
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BetCreationScreenQuestionTabPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
BetCreationScreenQuestionTab(
|
||||||
|
friends = emptyList(),
|
||||||
|
betTheme = "Elly",
|
||||||
|
betThemeError = null,
|
||||||
|
setBetTheme = { },
|
||||||
|
betPhrase = "Trinh",
|
||||||
|
betPhraseError = null,
|
||||||
|
setBetPhrase = { },
|
||||||
|
isPrivate = true,
|
||||||
|
setIsPrivate = { },
|
||||||
|
registerDate = ZonedDateTime.now(),
|
||||||
|
registerDateError = null,
|
||||||
|
betDate = ZonedDateTime.now(),
|
||||||
|
betDateError = null,
|
||||||
|
selectedFriends = mutableListOf(),
|
||||||
|
setRegisterDateDialog = { },
|
||||||
|
setEndDateDialog = { },
|
||||||
|
setRegisterTimeDialog = { },
|
||||||
|
setEndTimeDialog = { },
|
||||||
|
toggleFriend = { },
|
||||||
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.betStatus.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
import fr.iut.alldev.allin.ui.core.AllInTextIcon
|
||||||
|
import fr.iut.alldev.allin.ui.core.IconPosition
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SimpleDetailsLine(
|
||||||
|
icon: Painter,
|
||||||
|
text: String,
|
||||||
|
isWin: Boolean
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
AllInTextIcon(
|
||||||
|
text = text,
|
||||||
|
color = if (isWin) {
|
||||||
|
AllInColorToken.allInBarViolet
|
||||||
|
} else {
|
||||||
|
AllInColorToken.allInBlue
|
||||||
|
},
|
||||||
|
icon = icon,
|
||||||
|
position = IconPosition.TRAILING,
|
||||||
|
size = 15,
|
||||||
|
iconSize = 15
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SimpleDetailsLineWinPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
SimpleDetailsLine(
|
||||||
|
icon = AllInTheme.icons.allCoins(),
|
||||||
|
text = "550",
|
||||||
|
isWin = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SimpleDetailsLinePreview() {
|
||||||
|
AllInTheme {
|
||||||
|
SimpleDetailsLine(
|
||||||
|
icon = AllInTheme.icons.allCoins(),
|
||||||
|
text = "550",
|
||||||
|
isWin = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.betStatus.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.AbsoluteRoundedCornerShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.data.ext.toPercentageString
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SimpleStatBar(
|
||||||
|
percentage: Float,
|
||||||
|
response: String,
|
||||||
|
isWin: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(modifier) {
|
||||||
|
Text(
|
||||||
|
text = response,
|
||||||
|
color = if (isWin) {
|
||||||
|
AllInColorToken.allInBarPink
|
||||||
|
} else {
|
||||||
|
AllInColorToken.allInBlue
|
||||||
|
},
|
||||||
|
style = AllInTheme.typography.sm2,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
fontSize = 18.sp
|
||||||
|
)
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(20.dp)
|
||||||
|
.let { itModifier ->
|
||||||
|
if (percentage != 0f && !percentage.isNaN()) itModifier.weight(percentage)
|
||||||
|
else itModifier
|
||||||
|
}
|
||||||
|
.clip(
|
||||||
|
AbsoluteRoundedCornerShape(
|
||||||
|
topLeftPercent = 50,
|
||||||
|
bottomLeftPercent = 50,
|
||||||
|
topRightPercent = 0,
|
||||||
|
bottomRightPercent = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
if (isWin) {
|
||||||
|
AllInColorToken.allInBar2ndGradient
|
||||||
|
} else {
|
||||||
|
AllInColorToken.allInBar1stGradient
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.fire_solid),
|
||||||
|
tint = if (isWin) {
|
||||||
|
AllInColorToken.allInBarViolet
|
||||||
|
} else {
|
||||||
|
AllInColorToken.allInBarPurple
|
||||||
|
},
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.offset((-7).dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.let { itModifier ->
|
||||||
|
if (percentage != 1f && !percentage.isNaN()) itModifier.weight(1 - percentage)
|
||||||
|
else itModifier
|
||||||
|
},
|
||||||
|
text = percentage.toPercentageString(),
|
||||||
|
style = AllInTheme.typography.h1.copy(
|
||||||
|
fontSize = if (isWin) 24.sp else 16.sp
|
||||||
|
),
|
||||||
|
color = if (isWin) {
|
||||||
|
AllInColorToken.allInBarViolet
|
||||||
|
} else {
|
||||||
|
AllInColorToken.allInBarPurple
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SimpleStatBarBarWinPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
SimpleStatBar(
|
||||||
|
percentage = .8f,
|
||||||
|
response = "Answer",
|
||||||
|
isWin = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SimpleStatBarBarPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
SimpleStatBar(
|
||||||
|
percentage = .4f,
|
||||||
|
response = "Answer",
|
||||||
|
isWin = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.core.bet
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AllInEmptyView(
|
||||||
|
image: Painter,
|
||||||
|
text: String,
|
||||||
|
subtext: String?,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.alpha(.35f)
|
||||||
|
.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = image,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(.5f)
|
||||||
|
.aspectRatio(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = AllInTheme.typography.h2,
|
||||||
|
color = AllInTheme.colors.onMainSurface,
|
||||||
|
fontSize = 16.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
subtext?.let {
|
||||||
|
Text(
|
||||||
|
text = subtext,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = AllInTheme.typography.p1,
|
||||||
|
color = AllInTheme.colors.onBackground2,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun AllInEmptyViewPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
AllInEmptyView(
|
||||||
|
text = "C'est un peu vide par ici",
|
||||||
|
subtext = "Ajoutez des amis pour les afficher dans le classement, et voir qui est le meilleur !",
|
||||||
|
image = painterResource(id = R.drawable.eyes),
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.core.topbar
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AllInTopBar(
|
||||||
|
onMenuClicked: () -> Unit,
|
||||||
|
coinAmount: Int,
|
||||||
|
) {
|
||||||
|
TopAppBar(
|
||||||
|
modifier = Modifier.background(AllInColorToken.allInMainGradient),
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
),
|
||||||
|
title = { },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = onMenuClicked,
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painterResource(id = R.drawable.allin_menu),
|
||||||
|
modifier = Modifier.size(30.dp),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = AllInTheme.icons.logo(),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = AllInColorToken.white,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
AllInTopBarCoinCounter(
|
||||||
|
amount = coinAmount,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
.offset(x = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun AllInTopBarPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
AllInTopBar(onMenuClicked = { }, coinAmount = 541)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.core.topbar
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AllInTopBarCoinCounter(
|
||||||
|
amount: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
backgroundColor: Color = AllInColorToken.white,
|
||||||
|
textColor: Color = AllInColorToken.allInDark,
|
||||||
|
iconColor: Color = AllInColorToken.allInBlue,
|
||||||
|
) {
|
||||||
|
var oldAmount by remember { mutableIntStateOf(amount) }
|
||||||
|
LaunchedEffect(amount) {
|
||||||
|
oldAmount = amount
|
||||||
|
}
|
||||||
|
|
||||||
|
val countString = remember(amount) { amount.toString() }
|
||||||
|
val oldCountString = remember(oldAmount) { oldAmount.toString() }
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier.wrapContentSize(),
|
||||||
|
shape = RoundedCornerShape(topStartPercent = 50, bottomStartPercent = 50)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(backgroundColor)
|
||||||
|
.padding(horizontal = 13.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(7.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
for (i in countString.indices) {
|
||||||
|
val oldChar = oldCountString.getOrNull(i)
|
||||||
|
val newChar = countString[i]
|
||||||
|
val char = if (oldChar == newChar) {
|
||||||
|
oldCountString[i]
|
||||||
|
} else {
|
||||||
|
countString[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = char,
|
||||||
|
transitionSpec = {
|
||||||
|
val delayMillis = (countString.indices.count() - i) * 50
|
||||||
|
if (oldAmount <= amount) {
|
||||||
|
(slideInVertically(tween(delayMillis)) { it } togetherWith
|
||||||
|
slideOutVertically(tween(delayMillis)) { -it })
|
||||||
|
} else {
|
||||||
|
(slideInVertically(tween(delayMillis)) { -it } togetherWith
|
||||||
|
slideOutVertically(tween(delayMillis)) { it })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = ""
|
||||||
|
) { char ->
|
||||||
|
Text(
|
||||||
|
text = char.toString(),
|
||||||
|
style = AllInTheme.typography.h1,
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
softWrap = false,
|
||||||
|
modifier = Modifier.padding(vertical = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
painter = AllInTheme.icons.allCoins(),
|
||||||
|
tint = iconColor,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.padding(vertical = 5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun AllInTopBarCoinCounterPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
AllInTopBarCoinCounter(547)
|
||||||
|
}
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
package fr.iut.alldev.allin.ui.core.topbar
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import fr.iut.alldev.allin.theme.AllInTheme
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AllInTopBarCoinCounter(
|
|
||||||
amount: Int,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
backgroundColor: Color = AllInTheme.colors.white,
|
|
||||||
textColor: Color = AllInTheme.colors.allInDark,
|
|
||||||
iconColor: Color = AllInTheme.colors.allInBlue,
|
|
||||||
) {
|
|
||||||
Card(
|
|
||||||
modifier = modifier.wrapContentSize(),
|
|
||||||
shape = RoundedCornerShape(topStartPercent = 50, bottomStartPercent = 50)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(backgroundColor)
|
|
||||||
.padding(horizontal = 13.dp, vertical = 5.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(7.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = amount.toString(),
|
|
||||||
color = textColor,
|
|
||||||
style = AllInTheme.typography.h1,
|
|
||||||
fontSize = 20.sp
|
|
||||||
)
|
|
||||||
Icon(
|
|
||||||
painter = AllInTheme.icons.allCoins(),
|
|
||||||
tint = iconColor,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun AllInTopBarCoinCounterPreview() {
|
|
||||||
AllInTheme {
|
|
||||||
AllInTopBarCoinCounter(547)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
package fr.iut.alldev.allin.ui.core.topbar
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import fr.iut.alldev.allin.R
|
|
||||||
import fr.iut.alldev.allin.theme.AllInTheme
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AllInTopBar(
|
|
||||||
onMenuClicked: () -> Unit,
|
|
||||||
coinAmount: Int,
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(86.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(brush = AllInTheme.colors.allInMainGradient)
|
|
||||||
) {
|
|
||||||
IconButton(
|
|
||||||
onClick = onMenuClicked,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 19.dp)
|
|
||||||
.align(Alignment.CenterStart)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painterResource(id = R.drawable.allin_menu),
|
|
||||||
modifier = Modifier.size(30.dp),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = Color.White
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.allin),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = AllInTheme.colors.white,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(40.dp)
|
|
||||||
.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
AllInTopBarCoinCounter(
|
|
||||||
amount = coinAmount,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterEnd)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
private fun AllInTopBarPreview() {
|
|
||||||
AllInTheme {
|
|
||||||
AllInTopBar(onMenuClicked = { }, coinAmount = 541)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,219 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.dailyReward
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.RepeatMode
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
|
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DailyRewardScreen(
|
||||||
|
amount: Int,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
var hasOpened by remember { mutableStateOf(false) }
|
||||||
|
DailyRewardScreenContent(
|
||||||
|
amount = amount,
|
||||||
|
hasOpened = hasOpened
|
||||||
|
) {
|
||||||
|
if (hasOpened) {
|
||||||
|
onDismiss()
|
||||||
|
} else {
|
||||||
|
hasOpened = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DailyRewardScreenContent(
|
||||||
|
amount: Int,
|
||||||
|
hasOpened: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val infiniteTransition = rememberInfiniteTransition(label = "")
|
||||||
|
val rotation by infiniteTransition.animateFloat(
|
||||||
|
initialValue = 3f,
|
||||||
|
targetValue = -3f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(900),
|
||||||
|
repeatMode = RepeatMode.Reverse
|
||||||
|
), label = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
val scale by infiniteTransition.animateFloat(
|
||||||
|
initialValue = 1f,
|
||||||
|
targetValue = .95f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(900),
|
||||||
|
repeatMode = RepeatMode.Reverse
|
||||||
|
), label = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(
|
||||||
|
Brush.verticalGradient(
|
||||||
|
0f to AllInColorToken.black.copy(alpha = .71f),
|
||||||
|
1f to AllInColorToken.black.copy(alpha = .97f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.clickable(
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = null,
|
||||||
|
onClick = onClick
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.daily_reward_title),
|
||||||
|
style = AllInTheme.typography.h1,
|
||||||
|
fontSize = 20.sp,
|
||||||
|
color = AllInColorToken.white,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(horizontal = 48.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.padding(horizontal = 48.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
|
visible = !hasOpened,
|
||||||
|
enter = fadeIn() + scaleIn(),
|
||||||
|
exit = fadeOut() + scaleOut(targetScale = 2f)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.daily_reward_1),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.rotate(rotation)
|
||||||
|
.scale(scale)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
|
visible = hasOpened,
|
||||||
|
enter = fadeIn() + scaleIn(),
|
||||||
|
exit = fadeOut() + scaleOut(targetScale = 2f)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.BottomCenter
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.daily_reward_2),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.rotate(rotation)
|
||||||
|
.scale(scale)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.rotate(-rotation)
|
||||||
|
.scale(scale)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "+$amount",
|
||||||
|
style = AllInTheme.typography.h1,
|
||||||
|
fontSize = 70.sp,
|
||||||
|
color = AllInColorToken.white
|
||||||
|
)
|
||||||
|
Icon(
|
||||||
|
painter = AllInTheme.icons.allCoins(),
|
||||||
|
tint = AllInColorToken.white,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(48.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.daily_reward_subtitle),
|
||||||
|
style = AllInTheme.typography.l1,
|
||||||
|
color = AllInColorToken.white,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(horizontal = 48.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun DailyRewardScreenPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
DailyRewardScreenContent(
|
||||||
|
amount = 125,
|
||||||
|
hasOpened = false,
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun DailyRewardScreenStep2Preview() {
|
||||||
|
AllInTheme {
|
||||||
|
DailyRewardScreenContent(
|
||||||
|
amount = 125,
|
||||||
|
hasOpened = true,
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.friends
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import fr.iut.alldev.allin.data.model.FriendStatus
|
||||||
|
import fr.iut.alldev.allin.ui.friends.components.FriendsScreenContent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FriendsScreen(
|
||||||
|
viewModel: FriendsScreenViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val search by viewModel.search.collectAsStateWithLifecycle()
|
||||||
|
val addTabState by viewModel.addTabState.collectAsStateWithLifecycle()
|
||||||
|
val requestsTabState by viewModel.requestsTabState.collectAsStateWithLifecycle()
|
||||||
|
val refreshing by viewModel.refreshing.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val focus = LocalFocusManager.current
|
||||||
|
|
||||||
|
FriendsScreenContent(
|
||||||
|
addTabState = addTabState,
|
||||||
|
search = search,
|
||||||
|
setSearch = { viewModel.setSearch(it) },
|
||||||
|
onToggleDeleteFriend = {
|
||||||
|
if (it.friendStatus == FriendStatus.NOT_FRIEND) {
|
||||||
|
viewModel.addFriend(it.username)
|
||||||
|
} else {
|
||||||
|
viewModel.removeFriend(it.username)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
requestsTabState = requestsTabState,
|
||||||
|
acceptRequest = { viewModel.addFriend(it.username) },
|
||||||
|
declineRequest = { viewModel.removeFriend(it.username) },
|
||||||
|
refresh = {
|
||||||
|
focus.clearFocus()
|
||||||
|
viewModel.refreshAll()
|
||||||
|
},
|
||||||
|
isRefreshing = refreshing
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,189 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.friends
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import fr.iut.alldev.allin.data.model.FriendStatus
|
||||||
|
import fr.iut.alldev.allin.data.model.User
|
||||||
|
import fr.iut.alldev.allin.data.repository.FriendRepository
|
||||||
|
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class FriendsScreenViewModel @Inject constructor(
|
||||||
|
private val friendRepository: FriendRepository,
|
||||||
|
private val keystoreManager: AllInKeystoreManager
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _search by lazy { MutableStateFlow("") }
|
||||||
|
val search get() = _search.asStateFlow()
|
||||||
|
|
||||||
|
private val _addTabState by lazy { MutableStateFlow<AddTabState>(AddTabState.Loading) }
|
||||||
|
val addTabState get() = _addTabState.asStateFlow()
|
||||||
|
|
||||||
|
private val _requestsTabState by lazy { MutableStateFlow<RequestsTabState>(RequestsTabState.Loading) }
|
||||||
|
val requestsTabState get() = _requestsTabState.asStateFlow()
|
||||||
|
|
||||||
|
private val _refreshing by lazy { MutableStateFlow(false) }
|
||||||
|
val refreshing by lazy { _refreshing.asStateFlow() }
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
_addTabState.emit(loadFriends())
|
||||||
|
_requestsTabState.emit(loadRequests())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
_search
|
||||||
|
.debounce(1.seconds)
|
||||||
|
.collect { itSearch ->
|
||||||
|
try {
|
||||||
|
_addTabState.emit(
|
||||||
|
if (itSearch.isNotBlank()) {
|
||||||
|
loadSearch(itSearch)
|
||||||
|
} else {
|
||||||
|
loadFriends()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshAll() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
_refreshing.emit(true)
|
||||||
|
_addTabState.emit(
|
||||||
|
_search.value.let { itSearch ->
|
||||||
|
if (itSearch.isNotBlank()) {
|
||||||
|
loadSearch(itSearch)
|
||||||
|
} else {
|
||||||
|
loadFriends()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
_requestsTabState.emit(loadRequests())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
_refreshing.emit(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadFriends() =
|
||||||
|
AddTabState.Loaded(
|
||||||
|
users = friendRepository.getFriends(
|
||||||
|
token = keystoreManager.getTokenOrEmpty()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadSearch(search: String) =
|
||||||
|
AddTabState.Loaded(
|
||||||
|
users = friendRepository.searchNew(
|
||||||
|
token = keystoreManager.getTokenOrEmpty(),
|
||||||
|
search = search
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun loadRequests() =
|
||||||
|
RequestsTabState.Loaded(
|
||||||
|
users = friendRepository.getFriendRequests(
|
||||||
|
token = keystoreManager.getTokenOrEmpty()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun setSearch(search: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_search.emit(search)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addFriend(username: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
friendRepository.add(
|
||||||
|
token = keystoreManager.getTokenOrEmpty(),
|
||||||
|
username = username
|
||||||
|
)
|
||||||
|
changeFriendStatus(username, FriendStatus.REQUESTED)
|
||||||
|
removeRequest(username)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFriend(username: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
friendRepository.remove(
|
||||||
|
token = keystoreManager.getTokenOrEmpty(),
|
||||||
|
username = username
|
||||||
|
)
|
||||||
|
changeFriendStatus(username, FriendStatus.NOT_FRIEND)
|
||||||
|
removeRequest(username)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun changeFriendStatus(username: String, newStatus: FriendStatus) {
|
||||||
|
(_addTabState.value as? AddTabState.Loaded)?.let { friends ->
|
||||||
|
val usrIdx = friends.users.indexOfFirst { it.username == username }
|
||||||
|
val newList = if (usrIdx == -1 && newStatus == FriendStatus.REQUESTED) {
|
||||||
|
(_requestsTabState.value as? RequestsTabState.Loaded)?.let { requests ->
|
||||||
|
requests.users.find { it.username == username }
|
||||||
|
}?.let {
|
||||||
|
friends.users + it.copy(friendStatus = FriendStatus.FRIEND)
|
||||||
|
} ?: friends.users
|
||||||
|
} else {
|
||||||
|
friends.users.toMutableList().apply {
|
||||||
|
replaceAll {
|
||||||
|
if (it.username == username) {
|
||||||
|
it.copy(friendStatus = newStatus)
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_addTabState.emit(
|
||||||
|
friends.copy(
|
||||||
|
users = newList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private suspend fun removeRequest(username: String) {
|
||||||
|
(_requestsTabState.value as? RequestsTabState.Loaded)?.let { requests ->
|
||||||
|
requests.users.find { it.username == username }?.let {
|
||||||
|
_requestsTabState.emit(
|
||||||
|
requests.copy(users = requests.users - it)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface AddTabState {
|
||||||
|
data object Loading : AddTabState
|
||||||
|
data class Loaded(val users: List<User>) : AddTabState
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface RequestsTabState {
|
||||||
|
data object Loading : RequestsTabState
|
||||||
|
data class Loaded(val users: List<User>) : RequestsTabState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.friends.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.input.pointer.PointerEventPass
|
||||||
|
import androidx.compose.ui.input.pointer.PointerInputChange
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.data.model.User
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
import fr.iut.alldev.allin.ui.core.AllInLoading
|
||||||
|
import fr.iut.alldev.allin.ui.core.AllInSections
|
||||||
|
import fr.iut.alldev.allin.ui.core.SectionElement
|
||||||
|
import fr.iut.alldev.allin.ui.friends.FriendsScreenViewModel
|
||||||
|
import fr.iut.alldev.allin.ui.friends.tabs.FriendsScreenAddTab
|
||||||
|
import fr.iut.alldev.allin.ui.friends.tabs.FriendsScreenRequestsTab
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FriendsScreenContent(
|
||||||
|
addTabState: FriendsScreenViewModel.AddTabState,
|
||||||
|
search: String,
|
||||||
|
isRefreshing: Boolean,
|
||||||
|
onToggleDeleteFriend: (User) -> Unit,
|
||||||
|
setSearch: (String) -> Unit,
|
||||||
|
requestsTabState: FriendsScreenViewModel.RequestsTabState,
|
||||||
|
acceptRequest: (User) -> Unit,
|
||||||
|
declineRequest: (User) -> Unit,
|
||||||
|
refresh: () -> Unit
|
||||||
|
|
||||||
|
) {
|
||||||
|
val focus = LocalFocusManager.current
|
||||||
|
val pullRefreshState = rememberPullRefreshState(isRefreshing, refresh)
|
||||||
|
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.pullRefresh(pullRefreshState)
|
||||||
|
.padding(top = with(LocalDensity.current) {
|
||||||
|
progressAnimation.toDp()
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
Column(Modifier.fillMaxSize()) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.friends_title),
|
||||||
|
style = AllInTheme.typography.h1,
|
||||||
|
color = AllInColorToken.allInGrey,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 11.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
AllInSections(
|
||||||
|
modifier = Modifier.let {
|
||||||
|
if (isRefreshing) {
|
||||||
|
it
|
||||||
|
.alpha(.5f)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
scope.launch {
|
||||||
|
awaitPointerEventScope {
|
||||||
|
while (true) {
|
||||||
|
awaitPointerEvent(pass = PointerEventPass.Initial)
|
||||||
|
.changes
|
||||||
|
.forEach(PointerInputChange::consume)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else it
|
||||||
|
},
|
||||||
|
onLoadSection = { focus.clearFocus() },
|
||||||
|
sections = listOf(
|
||||||
|
SectionElement(stringResource(id = R.string.friends_add_tab)) {
|
||||||
|
when (addTabState) {
|
||||||
|
is FriendsScreenViewModel.AddTabState.Loaded -> {
|
||||||
|
FriendsScreenAddTab(
|
||||||
|
friends = addTabState.users,
|
||||||
|
search = search,
|
||||||
|
setSearch = setSearch,
|
||||||
|
onToggleDeleteFriend = onToggleDeleteFriend
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FriendsScreenViewModel.AddTabState.Loading -> {
|
||||||
|
AllInLoading(visible = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SectionElement(
|
||||||
|
stringResource(
|
||||||
|
id = R.string.friends_requests_tab,
|
||||||
|
(requestsTabState as? FriendsScreenViewModel.RequestsTabState.Loaded)
|
||||||
|
?.users?.size ?: 0
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
when (requestsTabState) {
|
||||||
|
is FriendsScreenViewModel.RequestsTabState.Loaded -> {
|
||||||
|
FriendsScreenRequestsTab(
|
||||||
|
requests = requestsTabState.users,
|
||||||
|
acceptRequest = acceptRequest,
|
||||||
|
declineRequest = declineRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
FriendsScreenViewModel.RequestsTabState.Loading -> {
|
||||||
|
AllInLoading(visible = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter),
|
||||||
|
refreshing = isRefreshing,
|
||||||
|
state = pullRefreshState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun FriendsScreenContentPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
FriendsScreenContent(
|
||||||
|
addTabState = FriendsScreenViewModel.AddTabState.Loaded(emptyList()),
|
||||||
|
search = "",
|
||||||
|
setSearch = {},
|
||||||
|
onToggleDeleteFriend = {},
|
||||||
|
requestsTabState = FriendsScreenViewModel.RequestsTabState.Loaded(emptyList()),
|
||||||
|
acceptRequest = {},
|
||||||
|
declineRequest = {},
|
||||||
|
refresh = {},
|
||||||
|
isRefreshing = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
package fr.iut.alldev.allin.ui.friends.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import fr.iut.alldev.allin.R
|
||||||
|
import fr.iut.alldev.allin.data.model.FriendStatus
|
||||||
|
import fr.iut.alldev.allin.ext.asFallbackProfileUsername
|
||||||
|
import fr.iut.alldev.allin.theme.AllInColorToken
|
||||||
|
import fr.iut.alldev.allin.theme.AllInTheme
|
||||||
|
import fr.iut.alldev.allin.ui.core.AllInButton
|
||||||
|
import fr.iut.alldev.allin.ui.core.ProfilePicture
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FriendsScreenLine(
|
||||||
|
username: String,
|
||||||
|
image: String?,
|
||||||
|
status: FriendStatus,
|
||||||
|
toggleIsFriend: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
ProfilePicture(
|
||||||
|
image = image,
|
||||||
|
fallback = username.asFallbackProfileUsername(),
|
||||||
|
size = 50.dp
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = username,
|
||||||
|
color = AllInTheme.colors.onMainSurface,
|
||||||
|
style = AllInTheme.typography.sm2,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
AllInButton(
|
||||||
|
color = when (status) {
|
||||||
|
FriendStatus.FRIEND -> AllInTheme.colors.background
|
||||||
|
FriendStatus.NOT_FRIEND -> AllInColorToken.allInPurple
|
||||||
|
FriendStatus.REQUESTED -> AllInTheme.colors.border
|
||||||
|
},
|
||||||
|
text = when (status) {
|
||||||
|
FriendStatus.FRIEND -> {
|
||||||
|
stringResource(id = R.string.generic_delete)
|
||||||
|
}
|
||||||
|
FriendStatus.NOT_FRIEND -> {
|
||||||
|
stringResource(id = R.string.generic_add)
|
||||||
|
}
|
||||||
|
FriendStatus.REQUESTED -> {
|
||||||
|
stringResource(id = R.string.friends_request_sent)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
textColor = when (status) {
|
||||||
|
FriendStatus.FRIEND -> AllInTheme.colors.onBackground
|
||||||
|
FriendStatus.NOT_FRIEND -> AllInColorToken.white
|
||||||
|
FriendStatus.REQUESTED -> AllInTheme.colors.onBackground2
|
||||||
|
},
|
||||||
|
isSmall = true,
|
||||||
|
textStyle = AllInTheme.typography.sm2,
|
||||||
|
onClick = toggleIsFriend,
|
||||||
|
modifier = Modifier.weight(.8f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun FriendsScreenLinePreview() {
|
||||||
|
AllInTheme {
|
||||||
|
FriendsScreenLine(
|
||||||
|
image = null,
|
||||||
|
username = "Random",
|
||||||
|
status = FriendStatus.NOT_FRIEND,
|
||||||
|
toggleIsFriend = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun FriendsScreenLineRequestedPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
FriendsScreenLine(
|
||||||
|
image = null,
|
||||||
|
username = "Random",
|
||||||
|
status = FriendStatus.REQUESTED,
|
||||||
|
toggleIsFriend = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun FriendsScreenLineIsFriendPreview() {
|
||||||
|
AllInTheme {
|
||||||
|
FriendsScreenLine(
|
||||||
|
image = null,
|
||||||
|
username = "Random",
|
||||||
|
status = FriendStatus.FRIEND,
|
||||||
|
toggleIsFriend = { }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue