Skip to content

Commit

Permalink
Adding feed feature files. (#581)
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamMc331 authored Dec 1, 2024
1 parent a3ed71b commit 76e18c6
Show file tree
Hide file tree
Showing 24 changed files with 1,820 additions and 0 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
junit = { module = "junit:junit", version.ref = "junit" }
koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" }
koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
Expand Down
3 changes: 3 additions & 0 deletions shared/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ kotlin {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.materialIconsExtended)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.cketti.codepoints)
implementation(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.kotlinx.datetime)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.core)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.adammcneilly.pocketleague.shared.app.domain.usecases

import com.adammcneilly.pocketleague.shared.app.core.datetime.DateTimeFormatter
import com.adammcneilly.pocketleague.shared.app.core.datetime.TimeProvider
import com.adammcneilly.pocketleague.shared.app.core.displaymodels.EventGroupDisplayModel
import com.adammcneilly.pocketleague.shared.app.core.displaymodels.EventSummaryDisplayModel
import com.adammcneilly.pocketleague.shared.app.core.locale.LocaleHelper
import com.adammcneilly.pocketleague.shared.app.data.event.EventListRequest
import com.adammcneilly.pocketleague.shared.app.data.event.EventRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

/**
* Return an observable type of events that are currently happening on today's date.
*/
class GetOngoingEventsUseCase(
private val dateTimeFormatter: DateTimeFormatter,
private val eventRepository: EventRepository,
private val localeHelper: LocaleHelper,
private val timeProvider: TimeProvider,
) {
/**
* @see [GetOngoingEventsUseCase]
*/
fun invoke(): Flow<List<EventGroupDisplayModel>> {
val request = EventListRequest.OnDate(
dateUtc = timeProvider.now(),
)

return eventRepository
.getEvents(request)
.map { eventList ->
eventList.map { event ->
EventSummaryDisplayModel(
event = event,
dateTimeFormatter = dateTimeFormatter,
localeHelper = localeHelper,
)
}
}
.map(EventGroupDisplayModel.Companion::mapFromEventList)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.adammcneilly.pocketleague.shared.app.domain.usecases

import com.adammcneilly.pocketleague.shared.app.core.datetime.DateTimeFormatter
import com.adammcneilly.pocketleague.shared.app.core.datetime.TimeProvider
import com.adammcneilly.pocketleague.shared.app.core.displaymodels.MatchDetailDisplayModel
import com.adammcneilly.pocketleague.shared.app.data.match.MatchListRequest
import com.adammcneilly.pocketleague.shared.app.data.match.MatchRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

/**
* Return an observable type of matches for the past week.
*/
class GetPastWeeksMatchesUseCase(
private val dateTimeFormatter: DateTimeFormatter,
private val matchRepository: MatchRepository,
private val timeProvider: TimeProvider,
) {
/**
* @see [GetPastWeeksMatchesUseCase].
*/
fun invoke(): Flow<List<MatchDetailDisplayModel>> {
val request = MatchListRequest.DateRange(
startDateUTC = timeProvider.daysAgo(DAYS_PER_WEEK),
endDateUTC = timeProvider.now(),
)

return matchRepository
.getMatches(request)
.map { matchList ->
matchList.map { match ->
MatchDetailDisplayModel(
match = match,
dateTimeFormatter = dateTimeFormatter,
timeProvider = timeProvider,
)
}
}
}

companion object {
private const val DAYS_PER_WEEK = 7
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.adammcneilly.pocketleague.shared.app.domain.usecases

import com.adammcneilly.pocketleague.shared.app.core.datetime.DateTimeFormatter
import com.adammcneilly.pocketleague.shared.app.core.datetime.TimeProvider
import com.adammcneilly.pocketleague.shared.app.core.displaymodels.EventGroupDisplayModel
import com.adammcneilly.pocketleague.shared.app.core.displaymodels.EventSummaryDisplayModel
import com.adammcneilly.pocketleague.shared.app.core.locale.LocaleHelper
import com.adammcneilly.pocketleague.shared.app.data.event.EventListRequest
import com.adammcneilly.pocketleague.shared.app.data.event.EventRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

/**
* Return an observable type of events that are upcoming from today's date.
*/
class GetUpcomingEventsUseCase(
private val dateTimeFormatter: DateTimeFormatter,
private val eventRepository: EventRepository,
private val localeHelper: LocaleHelper,
private val timeProvider: TimeProvider,
) {
/**
* @see [GetUpcomingEventsUseCase]
*/
fun invoke(): Flow<List<EventGroupDisplayModel>> {
val request = EventListRequest.AfterDate(
dateUtc = timeProvider.now(),
)

return eventRepository
.getEvents(request)
.map { eventList ->
eventList.map { event ->
EventSummaryDisplayModel(
event = event,
dateTimeFormatter = dateTimeFormatter,
localeHelper = localeHelper,
)
}
}
.map(EventGroupDisplayModel.Companion::mapFromEventList)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package com.adammcneilly.pocketleague.shared.app.feature.feed

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.adammcneilly.pocketleague.shared.app.core.displaymodels.EventGroupDisplayModel
import com.adammcneilly.pocketleague.shared.app.core.displaymodels.MatchDetailDisplayModel
import com.adammcneilly.pocketleague.shared.app.ui.event.EventSummaryListCard
import com.adammcneilly.pocketleague.shared.app.ui.event.LanEventSummaryCard
import com.adammcneilly.pocketleague.shared.app.ui.match.MatchCarousel
import com.adammcneilly.pocketleague.shared.app.ui.modifiers.screenHorizontalPadding
import com.adammcneilly.pocketleague.shared.app.ui.theme.PocketLeagueTheme

/**
* The main list of events and matches to show within the feed screen
* that is the landing page when opening the app.
*/
@Composable
fun FeedContent(
recentMatches: List<MatchDetailDisplayModel>,
ongoingEvents: List<EventGroupDisplayModel>,
upcomingEvents: List<EventGroupDisplayModel>,
onMatchClicked: (String) -> Unit,
onEventClicked: (String) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(
vertical = PocketLeagueTheme.sizes.screenPadding,
),
verticalArrangement = Arrangement.spacedBy(PocketLeagueTheme.sizes.listItemSpacing),
) {
recentMatchesHeader()

recentMatchesCarousel(recentMatches, onMatchClicked)

happeningNowHeader()

eventGroupList(ongoingEvents, onEventClicked)

upcomingHeader()

eventGroupList(upcomingEvents, onEventClicked)
}
}

private fun LazyListScope.upcomingHeader() {
item {
FeedSectionHeader(
text = "Upcoming",
)
}
}

private fun LazyListScope.eventGroupList(
events: List<EventGroupDisplayModel>,
onEventClicked: (String) -> Unit,
) {
events.forEach { group ->
item {
FeedEventGroup(
group,
onEventClicked,
)
}
}
}

private fun LazyListScope.happeningNowHeader() {
item {
FeedSectionHeader(
text = "Happening Now",
)
}
}

private fun LazyListScope.recentMatchesCarousel(
recentMatches: List<MatchDetailDisplayModel>,
onMatchClicked: (String) -> Unit,
) {
item {
MatchCarousel(
matches = recentMatches,
contentPadding = PaddingValues(
horizontal = PocketLeagueTheme.sizes.screenPadding,
),
onMatchClicked = onMatchClicked,
)
}
}

private fun LazyListScope.recentMatchesHeader() {
item {
FeedSectionHeader(
text = "Recent Matches",
)
}
}

@Composable
private fun FeedSectionHeader(
text: String,
modifier: Modifier = Modifier,
) {
Text(
text = text,
style = MaterialTheme.typography.headlineSmall,
modifier = modifier
.screenHorizontalPadding(),
)
}

/**
* For a given [displayModel], determine how to render
* that collection for our [FeedContent].
*/
@Composable
private fun FeedEventGroup(
displayModel: EventGroupDisplayModel,
onEventClicked: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val groupModifier = modifier
.screenHorizontalPadding()

when (displayModel) {
is EventGroupDisplayModel.Regionals -> {
EventSummaryListCard(
events = displayModel.events,
onEventClicked = onEventClicked,
modifier = groupModifier,
)
}

is EventGroupDisplayModel.Major -> {
LanEventSummaryCard(
event = displayModel.event,
onEventClicked = onEventClicked,
modifier = groupModifier,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.adammcneilly.pocketleague.shared.app.feature.feed

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import org.koin.compose.viewmodel.koinViewModel

/**
* Top level container for the Feed screen inside the application. This is a stateful
* wrapper around [FeedContent].
*/
@Composable
fun FeedScreen(
modifier: Modifier = Modifier,
viewModel: FeedViewModel = koinViewModel(),
) {
val state = viewModel.state.collectAsState()

FeedContent(
recentMatches = state.value.recentMatches,
ongoingEvents = state.value.ongoingEvents,
upcomingEvents = state.value.upcomingEvents,
onMatchClicked = { /*TODO*/ },
onEventClicked = { /*TODO*/ },
modifier = modifier,
)
}
Loading

0 comments on commit 76e18c6

Please sign in to comment.