Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#67) screen with list of timers implemented #98

Merged
merged 3 commits into from
Dec 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package io.timemates.app.authorization.dependencies.screens

import io.timemates.app.authorization.dependencies.AuthorizationDataModule
import io.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationReducer
import io.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine
import org.koin.core.annotation.Factory
import org.koin.core.annotation.Module

@Module(includes = [AuthorizationDataModule::class])
@Module
class InitialAuthorizationModule {
@Factory
fun stateMachine(): InitialAuthorizationStateMachine {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ class NewAccountInfoModule {
),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import org.koin.core.annotation.Singleton
class TimerCreationModule {

@Singleton
fun timerCreationMiddleware(): TimerCreationModule = TimerCreationModule()
fun timerCreationMiddleware(): TimerCreationMiddleware = TimerCreationMiddleware()

@Factory
fun timerCreationUseCase(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.timemates.app.timers.dependencies.screens

import io.timemates.app.timers.dependencies.TimersDataModule
import io.timemates.app.timers.ui.timers_list.mvi.TimersListMiddleware
import io.timemates.app.timers.ui.timers_list.mvi.TimersListReducer
import io.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine
import io.timemates.app.users.repositories.TimersRepository
import io.timemates.app.users.usecases.GetUserTimersUseCase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.koin.core.annotation.Factory
import org.koin.core.annotation.Module
import org.koin.core.annotation.Singleton

@Module(includes = [TimersDataModule::class])
class TimersListModule {

@Singleton
fun timersListMiddleware(): TimersListMiddleware = TimersListMiddleware()

@Factory
fun getUserTimersUseCase(
timersRepository: TimersRepository,
): GetUserTimersUseCase = GetUserTimersUseCase(timersRepository)

@Factory
fun stateMachine(
getUserTimersUseCase: GetUserTimersUseCase,
timersListMiddleware: TimersListMiddleware,
): TimersListStateMachine {
return TimersListStateMachine(
reducer = TimersListReducer(
getUserTimersUseCase = getUserTimersUseCase,
coroutineScope = CoroutineScope(Dispatchers.IO),
),
middleware = timersListMiddleware,
)
}
}
5 changes: 5 additions & 0 deletions feature/timers/presentation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ dependencies {
commonMainImplementation(projects.feature.timers.domain)

commonTestImplementation(projects.foundation.random)

commonMainImplementation(libs.moko.resources.compose)
commonMainImplementation(libs.moko.resources)

commonMainImplementation(libs.kotlinx.datetime)
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.timemates.app.timers.ui

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.foundation.BorderStroke
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.Spacer
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.foundation.shape.RoundedCornerShape
import androidx.compose.material3.OutlinedCard
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.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import io.timemates.app.style.system.theme.AppTheme

@Composable
fun PlaceholderTimerItem(){
OutlinedCard(
modifier = Modifier.fillMaxWidth(),
border = BorderStroke(1.dp, AppTheme.colors.secondary),
) {
Row(
modifier = Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column{
Box(
modifier = Modifier
.fillMaxWidth(0.4f)
.height(18.dp)
.clip(RoundedCornerShape(16.dp))
.shimmerEffect(),
)

Spacer(modifier = Modifier.padding(4.dp))

Box(
modifier = Modifier
.fillMaxWidth(0.6f)
.height(16.dp)
.clip(RoundedCornerShape(16.dp))
.shimmerEffect(),
)
}
Box(modifier = Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.clip(RoundedCornerShape(16.dp))
.shimmerEffect()
.size(20.dp),
)
}
}
}
}

private fun Modifier.shimmerEffect(): Modifier = composed {
var size by remember {
mutableStateOf(IntSize.Zero)
}
val transition = rememberInfiniteTransition()
val startOffsetX by transition.animateFloat(
initialValue = -2 * size.width.toFloat(),
targetValue = 2 * size.width.toFloat(),
animationSpec = infiniteRepeatable(
animation = tween(1000)
),
)

background(
brush = Brush.linearGradient(
colors = listOf(
AppTheme.colors.secondary,
AppTheme.colors.secondaryVariant,
AppTheme.colors.secondary,
),
start = Offset(startOffsetX, 0f),
end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat()),
),
).onGloballyPositioned {
size = it.size
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package io.timemates.app.timers.ui

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
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.unit.Dp
import androidx.compose.ui.unit.dp
import io.timemates.app.localization.compose.LocalStrings
import io.timemates.app.style.system.theme.AppTheme
import io.timemates.sdk.timers.types.Timer
import io.timemates.sdk.timers.types.Timer.State.ConfirmationWaiting
import io.timemates.sdk.timers.types.Timer.State.Inactive
import io.timemates.sdk.timers.types.Timer.State.Paused
import io.timemates.sdk.timers.types.Timer.State.Rest
import io.timemates.sdk.timers.types.Timer.State.Running
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.minus
import kotlinx.datetime.toLocalDateTime

@Composable
fun TimerItem(
timer: Timer,
onClick: () -> Unit,
) {
OutlinedCard(
modifier = Modifier
.fillMaxWidth()
.clickable(
onClick = onClick
),
border = BorderStroke(1.dp, AppTheme.colors.secondary),
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier.padding(12.dp),
) {
Row(
modifier = Modifier,
) {
Text(
text = timer.description.string,
modifier = Modifier,
style = MaterialTheme.typography.titleMedium,
)
OnlineIndicator(
modifier = Modifier.padding(3.dp),
isOnline = timer.state is Running,
)
}
Text(
text = timerItemSubtext(timer),
modifier = Modifier,
style = MaterialTheme.typography.bodyMedium,
)
}
Box(
modifier = Modifier.fillMaxWidth(),
) {
Icon(
imageVector = Icons.Filled.ArrowForward,
contentDescription = "Navigate To Timer",
modifier = Modifier
.padding(12.dp)
.align(Alignment.CenterEnd),
tint = Color.Gray,
)
}
}
}
}

@Stable
@Composable
private fun timerItemSubtext(timer: Timer): String {
return when(timer.state) {
is Running, is Rest -> LocalStrings.current.runningTimerDescription(timer.membersCount.int)
is Paused, is Inactive -> LocalStrings.current.inactiveTimerDescription(daysSinceInactive(timer.state.publishTime))
is ConfirmationWaiting -> LocalStrings.current.confirmationWaitingTimerDescription
}
}

@Stable
private fun daysSinceInactive(pausedInstant: Instant): Int {
val currentInstant = Clock.System.now()
return (currentInstant.toLocalDateTime(TimeZone.UTC).date.minus(pausedInstant.toLocalDateTime(TimeZone.UTC).date)).days
}

@Composable
private fun OnlineIndicator(
modifier: Modifier = Modifier,
isOnline: Boolean,
indicatorSize: Dp = 6.dp,
onlineColor: Color = AppTheme.colors.positive,
) {
Box(
modifier = modifier
.size(indicatorSize)
.background(
shape = CircleShape,
brush = Brush.radialGradient(
colors = if (isOnline) listOf(onlineColor, onlineColor) else listOf(Color.Transparent, Color.Transparent),
radius = 60f
),
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ import kotlin.time.DurationUnit
@Composable
fun TimerSettingsScreen(
stateMachine: StateMachine<State, Event, Effect>,
saveChanges: () -> Unit,
navigateToTimersScreen: () -> Unit,
) {
val state by stateMachine.state.collectAsState()
Expand All @@ -69,8 +68,7 @@ fun TimerSettingsScreen(
is Effect.Failure ->
snackbarData.showSnackbar(message = strings.unknownFailure)

Effect.Success -> saveChanges()
Effect.NavigateToTimersScreen -> navigateToTimersScreen()
Effect.NavigateToTimersScreen, Effect.Success -> navigateToTimersScreen()
}
}
}
Expand Down
Loading
Loading