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"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Allin_DEBUG</string>
|
||||
<string name="app_name" translatable="false">Allin DEBUG</string>
|
||||
</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
|
||||
|
||||
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.collectAsState
|
||||
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 fr.iut.alldev.allin.R
|
||||
import fr.iut.alldev.allin.data.ext.formatToMediumDateNoYear
|
||||
import fr.iut.alldev.allin.data.ext.formatToTime
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.iut.alldev.allin.data.model.bet.Bet
|
||||
import fr.iut.alldev.allin.theme.AllInTheme
|
||||
import fr.iut.alldev.allin.ui.bet.components.BetScreenCard
|
||||
import fr.iut.alldev.allin.ui.bet.components.BetScreenPopularCard
|
||||
import fr.iut.alldev.allin.ui.core.AllInChip
|
||||
import fr.iut.alldev.allin.ui.bet.components.BetScreenLoadedContent
|
||||
import fr.iut.alldev.allin.ui.core.AllInLoading
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun BetScreen(
|
||||
viewModel: BetViewModel = hiltViewModel(),
|
||||
selectBet: (Bet, Boolean) -> Unit,
|
||||
) {
|
||||
|
||||
val bets by viewModel.bets.collectAsState()
|
||||
|
||||
val horizontalPadding = 23.dp
|
||||
|
||||
val refreshing by viewModel.isRefreshing.collectAsState()
|
||||
val pullRefreshState = rememberPullRefreshState(refreshing, { viewModel.refresh() })
|
||||
|
||||
val progressAnimation by animateFloatAsState(pullRefreshState.progress * 15, label = "")
|
||||
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.pullRefresh(pullRefreshState)
|
||||
.padding(top = with(LocalDensity.current) {
|
||||
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)
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val filters by viewModel.filters.collectAsStateWithLifecycle()
|
||||
val isRefreshing by viewModel.refreshing.collectAsStateWithLifecycle()
|
||||
|
||||
when (val s = state) {
|
||||
is BetViewModel.State.Loaded -> {
|
||||
BetScreenLoadedContent(
|
||||
popularBet = s.popularBet,
|
||||
filters = filters,
|
||||
bets = s.bets,
|
||||
isRefreshing = isRefreshing,
|
||||
selectBet = selectBet,
|
||||
toggleFilter = { viewModel.toggleFilter(it) },
|
||||
refreshData = { viewModel.refreshData() }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val items = listOf(
|
||||
R.string.Public,
|
||||
R.string.Invitation,
|
||||
R.string.Current,
|
||||
R.string.Finished
|
||||
)
|
||||
BetViewModel.State.Loading -> {
|
||||
AllInLoading(visible = true)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.QuestionTabPrivacySection
|
||||
import fr.iut.alldev.allin.ui.betCreation.tabs.sections.QuestionTabThemePhraseSection
|
||||
import java.time.ZonedDateTime
|
||||
import fr.iut.alldev.allin.data.ext.formatToTime as formatToTime1
|
||||
|
||||
@Composable
|
||||
fun BetCreationScreenQuestionTab(
|
||||
modifier: Modifier = Modifier,
|
||||
nbFriends: Int,
|
||||
friends: List<User>,
|
||||
betTheme: String,
|
||||
betThemeError: String?,
|
||||
setBetTheme: (String) -> Unit,
|
||||
betPhrase: String,
|
||||
betPhraseError: String?,
|
||||
setBetPhrase: (String) -> Unit,
|
||||
isPublic: Boolean,
|
||||
setIsPublic: (Boolean) -> Unit,
|
||||
isPrivate: Boolean,
|
||||
setIsPrivate: (Boolean) -> Unit,
|
||||
registerDate: ZonedDateTime,
|
||||
registerDateError: String?,
|
||||
betDate: ZonedDateTime,
|
||||
betDateError: String?,
|
||||
selectedFriends: MutableList<Int>,
|
||||
selectedFriends: List<String>,
|
||||
setRegisterDateDialog: (Boolean) -> Unit,
|
||||
setEndDateDialog: (Boolean) -> Unit,
|
||||
setRegisterTimeDialog: (Boolean) -> Unit,
|
||||
setEndTimeDialog: (Boolean) -> Unit,
|
||||
toggleFriend: (String) -> Unit,
|
||||
interactionSource: MutableInteractionSource
|
||||
) {
|
||||
Column(modifier) {
|
||||
QuestionTabThemePhraseSection(
|
||||
betTheme = betTheme,
|
||||
betThemeError = betThemeError,
|
||||
setBetTheme = setBetTheme,
|
||||
betPhrase = betPhrase,
|
||||
betPhraseError = betPhraseError,
|
||||
setBetPhrase = setBetPhrase,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
Spacer(modifier = Modifier.height(35.dp))
|
||||
QuestionTabDateTimeSection(
|
||||
registerDate = registerDate.formatToMediumDate(),
|
||||
registerTime = registerDate.formatToTime(),
|
||||
registerDateError = registerDateError,
|
||||
betDateError = betDateError,
|
||||
endDate = betDate.formatToMediumDate(),
|
||||
endTime = betDate.formatToTime(),
|
||||
setEndDateDialog = setEndDateDialog,
|
||||
setRegisterDateDialog = setRegisterDateDialog,
|
||||
setRegisterTimeDialog = setRegisterTimeDialog,
|
||||
setEndTimeDialog = setEndTimeDialog,
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(35.dp))
|
||||
QuestionTabPrivacySection(
|
||||
isPublic = isPublic,
|
||||
setIsPublic = setIsPublic,
|
||||
nbFriends = nbFriends,
|
||||
selectedFriends = selectedFriends,
|
||||
interactionSource = interactionSource
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = PaddingValues(start = 20.dp, end = 20.dp, bottom = 120.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(35.dp)
|
||||
) {
|
||||
item {
|
||||
QuestionTabThemePhraseSection(
|
||||
betTheme = betTheme,
|
||||
betThemeError = betThemeError,
|
||||
setBetTheme = setBetTheme,
|
||||
betPhrase = betPhrase,
|
||||
betPhraseError = betPhraseError,
|
||||
setBetPhrase = setBetPhrase,
|
||||
interactionSource = interactionSource
|
||||
)
|
||||
}
|
||||
item {
|
||||
QuestionTabDateTimeSection(
|
||||
registerDate = registerDate.formatToMediumDate(),
|
||||
registerTime = registerDate.formatToTime1(),
|
||||
registerDateError = registerDateError,
|
||||
betDateError = betDateError,
|
||||
endDate = betDate.formatToMediumDate(),
|
||||
endTime = betDate.formatToTime1(),
|
||||
setEndDateDialog = setEndDateDialog,
|
||||
setRegisterDateDialog = setRegisterDateDialog,
|
||||
setRegisterTimeDialog = setRegisterTimeDialog,
|
||||
setEndTimeDialog = setEndTimeDialog,
|
||||
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