Skip to content

Commit

Permalink
feat: support browsing events by area
Browse files Browse the repository at this point in the history
  • Loading branch information
lydavid committed Mar 21, 2024
1 parent 3498afd commit c4007af
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ly.david.musicsearch.data.database

import ly.david.musicsearch.core.models.image.ImageUrlDao
import ly.david.musicsearch.data.database.dao.AreaDao
import ly.david.musicsearch.data.database.dao.AreaEventDao
import ly.david.musicsearch.data.database.dao.AreaPlaceDao
import ly.david.musicsearch.data.database.dao.ArtistCreditDao
import ly.david.musicsearch.data.database.dao.ArtistDao
Expand Down Expand Up @@ -40,6 +41,7 @@ import org.koin.dsl.module

val databaseDaoModule = module {
single { AreaDao(get()) }
single { AreaEventDao(get(), get()) }
single { AreaPlaceDao(get(), get()) }
single { ArtistCreditDao(get()) }
single { ArtistDao(get()) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package ly.david.musicsearch.data.database.dao

import app.cash.paging.PagingSource
import app.cash.sqldelight.coroutines.asFlow
import app.cash.sqldelight.coroutines.mapToOne
import app.cash.sqldelight.paging3.QueryPagingSource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import ly.david.musicsearch.core.coroutines.CoroutineDispatchers
import ly.david.musicsearch.core.models.listitem.EventListItemModel
import ly.david.musicsearch.data.database.Database
import ly.david.musicsearch.data.database.mapper.mapToEventListItemModel
import lydavidmusicsearchdatadatabase.Area_event

class AreaEventDao(
database: Database,
private val coroutineDispatchers: CoroutineDispatchers,
) : EntityDao {
override val transacter = database.area_eventQueries

fun insert(
areaId: String,
eventId: String,
) {
transacter.insert(
Area_event(
area_id = areaId,
event_id = eventId,
),
)
}

fun insertAll(
areaAndEventIds: List<Pair<String, String>>,
) {
transacter.transaction {
areaAndEventIds.forEach { (areaId, eventId) ->
insert(
areaId = areaId,
eventId = eventId,
)
}
}
}

fun deleteEventsByArea(areaId: String) {
transacter.deleteEventsByArea(areaId)
}

fun getNumberOfEventsByArea(areaId: String): Flow<Int> =
transacter.getNumberOfEventsByArea(
areaId = areaId,
query = "%%",
)
.asFlow()
.mapToOne(coroutineDispatchers.io)
.map { it.toInt() }

fun getEventsByArea(
areaId: String,
query: String,
): PagingSource<Int, EventListItemModel> = QueryPagingSource(
countQuery = transacter.getNumberOfEventsByArea(
areaId = areaId,
query = "%$query%",
),
transacter = transacter,
context = coroutineDispatchers.io,
queryProvider = { limit, offset ->
transacter.getEventsByArea(
areaId = areaId,
query = "%$query%",
limit = limit,
offset = offset,
mapper = ::mapToEventListItemModel,
)
},
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
CREATE TABLE IF NOT EXISTS area_event (
area_id TEXT NOT NULL,
event_id TEXT NOT NULL,

PRIMARY KEY (area_id, event_id)
);

insert:
INSERT OR IGNORE INTO area_event
VALUES ?;

deleteEventsByArea:
DELETE FROM event WHERE id IN (
SELECT e.id
FROM area a
INNER JOIN area_event ae ON a.id = ae.area_id
INNER JOIN event e ON e.id = ae.event_id
WHERE a.id = :areaId
);

getNumberOfEventsByArea:
SELECT IFNULL(
(
SELECT COUNT(*)
FROM area a
INNER JOIN area_event ae ON a.id = ae.area_id
INNER JOIN event e ON e.id = ae.event_id
WHERE a.id = :areaId
AND (e.name LIKE :query OR e.disambiguation LIKE :query OR e.type LIKE :query)
),
0
) AS count;

getEventsByArea:
SELECT
e.id,
e.name,
e.disambiguation,
e.type,
e.time,
e.cancelled,
e.begin,
e.end,
e.ended
FROM area a
INNER JOIN area_event ae ON a.id = ae.area_id
INNER JOIN event e ON e.id = ae.event_id
WHERE a.id = :areaId
AND (e.name LIKE :query OR e.disambiguation LIKE :query OR e.type LIKE :query)
ORDER BY e.begin, e.end, e.name
LIMIT :limit OFFSET :offset;
6 changes: 6 additions & 0 deletions data/database/src/commonMain/sqldelight/migrations/1.sqm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS area_event (
area_id TEXT NOT NULL,
event_id TEXT NOT NULL,

PRIMARY KEY (area_id, event_id)
);
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ package ly.david.musicsearch.data.repository.event
import androidx.paging.PagingData
import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import ly.david.musicsearch.data.musicbrainz.models.core.EventMusicBrainzModel
import ly.david.musicsearch.data.musicbrainz.api.BrowseEventsResponse
import ly.david.musicsearch.data.musicbrainz.api.MusicBrainzApi
import ly.david.musicsearch.core.models.ListFilters
import ly.david.musicsearch.core.models.listitem.EventListItemModel
import ly.david.musicsearch.core.models.network.MusicBrainzEntity
import ly.david.musicsearch.data.database.dao.AreaEventDao
import ly.david.musicsearch.data.database.dao.BrowseEntityCountDao
import ly.david.musicsearch.data.database.dao.CollectionEntityDao
import ly.david.musicsearch.data.database.dao.EventDao
import ly.david.musicsearch.data.database.dao.EventPlaceDao
import ly.david.musicsearch.data.musicbrainz.api.BrowseEventsResponse
import ly.david.musicsearch.data.musicbrainz.api.MusicBrainzApi
import ly.david.musicsearch.data.musicbrainz.models.core.EventMusicBrainzModel
import ly.david.musicsearch.data.repository.base.BrowseEntitiesByEntity
import ly.david.musicsearch.domain.event.EventsByEntityRepository

class EventsByEntityRepositoryImpl(
private val areaEventDao: AreaEventDao,
private val browseEntityCountDao: BrowseEntityCountDao,
private val collectionEntityDao: CollectionEntityDao,
private val eventDao: EventDao,
Expand Down Expand Up @@ -51,6 +53,10 @@ class EventsByEntityRepositoryImpl(
)

when (entity) {
MusicBrainzEntity.AREA -> {
areaEventDao.deleteEventsByArea(entityId)
}

MusicBrainzEntity.COLLECTION -> {
collectionEntityDao.deleteAllFromCollection(entityId)
}
Expand All @@ -70,6 +76,13 @@ class EventsByEntityRepositoryImpl(
listFilters: ListFilters,
): PagingSource<Int, EventListItemModel> {
return when (entity) {
MusicBrainzEntity.AREA -> {
areaEventDao.getEventsByArea(
areaId = entityId,
query = listFilters.query,
)
}

MusicBrainzEntity.COLLECTION -> {
collectionEntityDao.getEventsByCollection(
collectionId = entityId,
Expand Down Expand Up @@ -107,6 +120,14 @@ class EventsByEntityRepositoryImpl(
) {
eventDao.insertAll(musicBrainzModels)
when (entity) {
MusicBrainzEntity.AREA -> {
areaEventDao.insertAll(
areaAndEventIds = musicBrainzModels.map { event ->
entityId to event.id
},
)
}

MusicBrainzEntity.COLLECTION -> {
collectionEntityDao.insertAll(
collectionId = entityId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue
import com.slack.circuit.runtime.presenter.Presenter
import kotlinx.collections.immutable.toImmutableList
import ly.david.musicsearch.core.models.network.MusicBrainzEntity
import ly.david.musicsearch.data.database.dao.AreaEventDao
import ly.david.musicsearch.data.database.dao.AreaPlaceDao
import ly.david.musicsearch.data.database.dao.ReleaseCountryDao
import ly.david.musicsearch.domain.browse.usecase.ObserveBrowseEntityCount
Expand All @@ -18,30 +19,46 @@ internal class AreaStatsPresenter(
private val getCountOfEachRelationshipTypeUseCase: GetCountOfEachRelationshipTypeUseCase,
private val observeBrowseEntityCount: ObserveBrowseEntityCount,
private val releaseCountryDao: ReleaseCountryDao,
private val areaEventDao: AreaEventDao,
private val areaPlaceDao: AreaPlaceDao,
) : Presenter<StatsUiState> {

@Composable
override fun present(): StatsUiState {
val relationTypeCounts
by getCountOfEachRelationshipTypeUseCase(screen.id).collectAsState(listOf())

val browseEventCount
by observeBrowseEntityCount(
entityId = screen.id,
entity = MusicBrainzEntity.EVENT,
).collectAsState(null)
val localEvents
by areaEventDao.getNumberOfEventsByArea(screen.id).collectAsState(0)

val browseReleaseCount
by observeBrowseEntityCount(
entityId = screen.id,
entity = MusicBrainzEntity.RELEASE,
).collectAsState(null)
val localReleases
by releaseCountryDao.getNumberOfReleasesByCountry(screen.id).collectAsState(0)

val browsePlaceCount
by observeBrowseEntityCount(
entityId = screen.id,
entity = MusicBrainzEntity.PLACE,
).collectAsState(null)
val localPlaces
by areaPlaceDao.getNumberOfPlacesByArea(screen.id).collectAsState(0)

val stats = Stats(
totalRelations = relationTypeCounts.sumOf { it.count },
relationTypeCounts = relationTypeCounts.toImmutableList(),
eventStats = EventStats(
totalRemote = browseEventCount?.remoteCount,
totalLocal = localEvents,
),
releaseStats = ReleaseStats(
totalRemote = browseReleaseCount?.remoteCount,
totalLocal = localReleases,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ val statsFeatureModule = module {
getCountOfEachRelationshipTypeUseCase = get(),
observeBrowseEntityCount = get(),
releaseCountryDao = get(),
areaEventDao = get(),
areaPlaceDao = get(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ val detailsFeatureModule = module {
navigator = navigator,
repository = get(),
incrementLookupHistory = get(),
eventsByEntityPresenter = get(),
placesByEntityPresenter = get(),
releasesByEntityPresenter = get(),
relationsPresenter = get(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import ly.david.musicsearch.core.models.history.LookupHistory
import ly.david.musicsearch.data.common.network.RecoverableNetworkException
import ly.david.musicsearch.domain.area.AreaRepository
import ly.david.musicsearch.domain.history.usecase.IncrementLookupHistory
import ly.david.ui.common.screen.DetailsScreen
import ly.david.ui.common.event.EventsByEntityPresenter
import ly.david.ui.common.event.EventsByEntityUiEvent
import ly.david.ui.common.place.PlacesByEntityPresenter
import ly.david.ui.common.place.PlacesByEntityUiEvent
import ly.david.ui.common.relation.RelationsPresenter
import ly.david.ui.common.relation.RelationsUiEvent
import ly.david.ui.common.release.ReleasesByEntityPresenter
import ly.david.ui.common.release.ReleasesByEntityUiEvent
import ly.david.ui.common.screen.DetailsScreen

// TODO: not only do we need every XByEntityPresenter, we also need every XRepository...
// all XByEntityPresenter could be moved to another nested Presenter, which will half our injections
Expand All @@ -34,6 +36,7 @@ internal class AreaPresenter(
private val navigator: Navigator,
private val repository: AreaRepository,
private val incrementLookupHistory: IncrementLookupHistory,
private val eventsByEntityPresenter: EventsByEntityPresenter,
private val releasesByEntityPresenter: ReleasesByEntityPresenter,
private val placesByEntityPresenter: PlacesByEntityPresenter,
private val relationsPresenter: RelationsPresenter,
Expand All @@ -53,6 +56,8 @@ internal class AreaPresenter(
var selectedTab by rememberSaveable { mutableStateOf(AreaTab.DETAILS) }
var forceRefreshDetails by rememberSaveable { mutableStateOf(false) }

val eventsByEntityUiState = eventsByEntityPresenter.present()
val eventsEventSink = eventsByEntityUiState.eventSink
val releasesByEntityUiState = releasesByEntityPresenter.present()
val releasesEventSink = releasesByEntityUiState.eventSink
val placesByEntityUiState = placesByEntityPresenter.present()
Expand Down Expand Up @@ -108,6 +113,16 @@ internal class AreaPresenter(
relationsEventSink(RelationsUiEvent.UpdateQuery(query))
}

AreaTab.EVENTS -> {
eventsEventSink(
EventsByEntityUiEvent.Get(
byEntityId = screen.id,
byEntity = screen.entity,
),
)
eventsEventSink(EventsByEntityUiEvent.UpdateQuery(query))
}

AreaTab.RELEASES -> {
releasesEventSink(
ReleasesByEntityUiEvent.Get(
Expand Down Expand Up @@ -173,6 +188,7 @@ internal class AreaPresenter(
tabs = tabs,
selectedTab = selectedTab,
query = query,
eventsByEntityUiState = eventsByEntityUiState,
placesByEntityUiState = placesByEntityUiState,
releasesByEntityUiState = releasesByEntityUiState,
relationsUiState = relationsUiState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ly.david.ui.common.topappbar.Tab
internal enum class AreaTab(val tab: Tab) {
DETAILS(Tab.DETAILS),
RELATIONSHIPS(Tab.RELATIONSHIPS),
EVENTS(Tab.EVENTS),
RELEASES(Tab.RELEASES),
PLACES(Tab.PLACES),
STATS(Tab.STATS),
Expand Down
Loading

0 comments on commit c4007af

Please sign in to comment.