diff --git a/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt b/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt index 3924a2d..b2afe9b 100644 --- a/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt +++ b/app/src/main/java/com/example/mobile_labs/api/MyServerService.kt @@ -46,6 +46,11 @@ interface MyServerService { @Path("id") id: Int, ): PersonRemote + @GET("events/{id}") + suspend fun getEvent( + @Path("id") id: Int, + ): EventRemote + companion object { private const val BASE_URL = "http://10.0.2.2:26000/" diff --git a/app/src/main/java/com/example/mobile_labs/api/events/EventRemoteMediator.kt b/app/src/main/java/com/example/mobile_labs/api/events/EventRemoteMediator.kt new file mode 100644 index 0000000..f15351a --- /dev/null +++ b/app/src/main/java/com/example/mobile_labs/api/events/EventRemoteMediator.kt @@ -0,0 +1,112 @@ +package com.example.mobile_labs.api.events + +import android.util.Log +import androidx.paging.ExperimentalPagingApi +import androidx.paging.LoadType +import androidx.paging.PagingState +import androidx.paging.RemoteMediator +import androidx.room.withTransaction +import com.example.mobile_labs.api.MyServerService +import com.example.mobile_labs.api.models.toEvent +import com.example.mobile_labs.api.performance.RestPerformanceRepository +import com.example.mobile_labs.database.AppDatabase +import com.example.mobile_labs.database.event.model.Event +import com.example.mobile_labs.database.event.model.EventWithPerformance +import com.example.mobile_labs.database.event.repository.OfflineEventRepository +import com.example.mobile_labs.database.remotekeys.model.RemoteKeyType +import com.example.mobile_labs.database.remotekeys.model.RemoteKeys +import com.example.mobile_labs.database.remotekeys.repository.OfflineRemoteKeyRepository +import retrofit2.HttpException +import java.io.IOException + +@OptIn(ExperimentalPagingApi::class) +class EventRemoteMediator( + private val service: MyServerService, + private val dbEventRepository: OfflineEventRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val performanceRestRepository: RestPerformanceRepository, + private val database: AppDatabase +) : RemoteMediator() { + + override suspend fun initialize(): InitializeAction { + return InitializeAction.LAUNCH_INITIAL_REFRESH + } + + override suspend fun load( + loadType: LoadType, + state: PagingState + ): MediatorResult { + val page = when (loadType) { + LoadType.REFRESH -> { + val remoteKeys = getRemoteKeyClosestToCurrentPosition(state) + remoteKeys?.nextKey?.minus(1) ?: 1 + } + + LoadType.PREPEND -> { + val remoteKeys = getRemoteKeyForFirstItem(state) + remoteKeys?.prevKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + + LoadType.APPEND -> { + val remoteKeys = getRemoteKeyForLastItem(state) + remoteKeys?.nextKey + ?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null) + } + } + + try { + val events = service.getEvents(page, state.config.pageSize).map { it.toEvent() } + val endOfPaginationReached = events.isEmpty() + database.withTransaction { + if (loadType == LoadType.REFRESH) { + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.EVENT) + dbEventRepository.clearEvents() + } + val prevKey = if (page == 1) null else page - 1 + val nextKey = if (endOfPaginationReached) null else page + 1 + val keys = events.map { + RemoteKeys( + entityId = it.uid !!, + type = RemoteKeyType.EVENT, + prevKey = prevKey, + nextKey = nextKey + ) + } + performanceRestRepository.getAllPerformances() + dbRemoteKeyRepository.createRemoteKeys(keys) + dbEventRepository.insertEvents(events) + } + return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached) + } catch (exception: IOException) { + return MediatorResult.Error(exception) + } catch (exception: HttpException) { + return MediatorResult.Error(exception) + } + } + + private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { + return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() + ?.let { event -> + dbRemoteKeyRepository.getAllRemoteKeys(event.event.uid !!, RemoteKeyType.EVENT) + } + } + + private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { + return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() + ?.let { event -> + dbRemoteKeyRepository.getAllRemoteKeys(event.event.uid !!, RemoteKeyType.EVENT) + } + } + + private suspend fun getRemoteKeyClosestToCurrentPosition( + state: PagingState + ): RemoteKeys? { + return state.anchorPosition?.let { position -> + state.closestItemToPosition(position)?.event?.uid?.let { eventUid -> + dbRemoteKeyRepository.getAllRemoteKeys(eventUid, RemoteKeyType.EVENT) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/api/events/RestEventRepository.kt b/app/src/main/java/com/example/mobile_labs/api/events/RestEventRepository.kt new file mode 100644 index 0000000..9168cde --- /dev/null +++ b/app/src/main/java/com/example/mobile_labs/api/events/RestEventRepository.kt @@ -0,0 +1,49 @@ +package com.example.mobile_labs.api.events + +import androidx.paging.ExperimentalPagingApi +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.example.mobile_labs.api.MyServerService +import com.example.mobile_labs.api.models.toPerformanceWithPeople +import com.example.mobile_labs.api.performance.RestPerformanceRepository +import com.example.mobile_labs.common.AppDataContainer +import com.example.mobile_labs.common.EventRepository +import com.example.mobile_labs.common.PerformanceRepository +import com.example.mobile_labs.database.AppDatabase +import com.example.mobile_labs.database.event.model.Event +import com.example.mobile_labs.database.event.model.EventWithPerformance +import com.example.mobile_labs.database.event.repository.OfflineEventRepository +import com.example.mobile_labs.database.performance.model.Performance +import com.example.mobile_labs.database.performance.model.PerformanceWithPeople +import com.example.mobile_labs.database.remotekeys.repository.OfflineRemoteKeyRepository +import kotlinx.coroutines.flow.Flow + +class RestEventRepository( + private val service: MyServerService, + private val dbEventRepository: OfflineEventRepository, + private val dbRemoteKeyRepository: OfflineRemoteKeyRepository, + private val performanceRestRepository: RestPerformanceRepository, + private val database: AppDatabase +) : EventRepository { + override fun getAllWithPerformance(): Flow> { + + val pagingSourceFactory = { dbEventRepository.getAllEventsPagingSource() } + + @OptIn(ExperimentalPagingApi::class) + return Pager( + config = PagingConfig( + pageSize = AppDataContainer.LIMIT, + enablePlaceholders = false + ), + remoteMediator = EventRemoteMediator( + service, + dbEventRepository, + dbRemoteKeyRepository, + performanceRestRepository, + database, + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/api/models/EventRemote.kt b/app/src/main/java/com/example/mobile_labs/api/models/EventRemote.kt index 3320fac..600ec3e 100644 --- a/app/src/main/java/com/example/mobile_labs/api/models/EventRemote.kt +++ b/app/src/main/java/com/example/mobile_labs/api/models/EventRemote.kt @@ -1,6 +1,8 @@ package com.example.mobile_labs.api.models +import com.example.mobile_labs.api.MyServerService import com.example.mobile_labs.database.event.model.Event +import com.example.mobile_labs.database.event.model.EventWithPerformance import kotlinx.serialization.Serializable import java.time.LocalDate @@ -15,4 +17,9 @@ fun EventRemote.toEvent(): Event = Event( id, LocalDate.parse(date), performanceId +) + +suspend fun EventRemote.toEventWithPerformance(service: MyServerService): EventWithPerformance = EventWithPerformance( + service.getEvent(id).toEvent(), + service.getPerformance(performanceId).toPerformance() ) \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/api/people/PeopleRemoteMediator.kt b/app/src/main/java/com/example/mobile_labs/api/people/PeopleRemoteMediator.kt index 115a67c..a746f56 100644 --- a/app/src/main/java/com/example/mobile_labs/api/people/PeopleRemoteMediator.kt +++ b/app/src/main/java/com/example/mobile_labs/api/people/PeopleRemoteMediator.kt @@ -1,10 +1,12 @@ package com.example.mobile_labs.api.people +import android.content.Context import androidx.paging.ExperimentalPagingApi import androidx.paging.LoadType import androidx.paging.PagingState import androidx.paging.RemoteMediator import androidx.room.withTransaction +import com.example.mobile_labs.TheatreApplication import com.example.mobile_labs.api.MyServerService import com.example.mobile_labs.api.models.toPerson import com.example.mobile_labs.database.AppDatabase diff --git a/app/src/main/java/com/example/mobile_labs/api/performance/PerformanceRemoteMediator.kt b/app/src/main/java/com/example/mobile_labs/api/performance/PerformanceRemoteMediator.kt index 9b1eaa1..458c09a 100644 --- a/app/src/main/java/com/example/mobile_labs/api/performance/PerformanceRemoteMediator.kt +++ b/app/src/main/java/com/example/mobile_labs/api/performance/PerformanceRemoteMediator.kt @@ -60,7 +60,7 @@ class PerformanceRemoteMediator( val endOfPaginationReached = performances.isEmpty() database.withTransaction { if (loadType == LoadType.REFRESH) { - dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERSON) + dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERFORMANCE) dbPerformanceRepository.clearPerformances() } val prevKey = if (page == 1) null else page - 1 @@ -68,7 +68,7 @@ class PerformanceRemoteMediator( val keys = performances.map { RemoteKeys( entityId = it.performance_uid !!, - type = RemoteKeyType.PERSON, + type = RemoteKeyType.PERFORMANCE, prevKey = prevKey, nextKey = nextKey ) @@ -88,14 +88,14 @@ class PerformanceRemoteMediator( private suspend fun getRemoteKeyForLastItem(state: PagingState): RemoteKeys? { return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() ?.let { performance -> - dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERSON) + dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERFORMANCE) } } private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? { return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() ?.let { performance -> - dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERSON) + dbRemoteKeyRepository.getAllRemoteKeys(performance.performance_uid !!, RemoteKeyType.PERFORMANCE) } } @@ -104,7 +104,7 @@ class PerformanceRemoteMediator( ): RemoteKeys? { return state.anchorPosition?.let { position -> state.closestItemToPosition(position)?.performance_uid?.let { performanceUid -> - dbRemoteKeyRepository.getAllRemoteKeys(performanceUid, RemoteKeyType.PERSON) + dbRemoteKeyRepository.getAllRemoteKeys(performanceUid, RemoteKeyType.PERFORMANCE) } } } diff --git a/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt b/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt index 6bf6d2c..9eafc96 100644 --- a/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt +++ b/app/src/main/java/com/example/mobile_labs/common/AppContainer.kt @@ -2,24 +2,23 @@ package com.example.mobile_labs.common import android.content.Context import com.example.mobile_labs.api.MyServerService +import com.example.mobile_labs.api.events.RestEventRepository import com.example.mobile_labs.api.people.RestPersonRepository import com.example.mobile_labs.api.performance.RestPerformanceRepository -import com.example.mobile_labs.database.event.repository.EventRepository import com.example.mobile_labs.database.event.repository.OfflineEventRepository import com.example.mobile_labs.database.performance.repository.OfflinePerformanceRepository import com.example.mobile_labs.database.person.repository.OfflinePersonRepository import com.example.mobile_labs.database.AppDatabase import com.example.mobile_labs.database.remotekeys.repository.OfflineRemoteKeyRepository -import com.example.mobile_labs.database.remotekeys.repository.RemoteKeyRepository interface AppContainer { val personRestRepository: RestPersonRepository - val eventRepository: EventRepository + val eventRestRepository: RestEventRepository val performanceRestRepository: RestPerformanceRepository } -class AppDataContainer(private val context: Context) : AppContainer { - override val eventRepository: EventRepository by lazy { +class AppDataContainer(val context: Context) : AppContainer { + val eventRepository: OfflineEventRepository by lazy { OfflineEventRepository(AppDatabase.getInstance(context).eventDao()) } @@ -53,6 +52,16 @@ class AppDataContainer(private val context: Context) : AppContainer { ) } + override val eventRestRepository: RestEventRepository by lazy { + RestEventRepository( + MyServerService.getInstance(), + eventRepository, + remoteKeyRepository, + performanceRestRepository, + AppDatabase.getInstance(context) + ) + } + companion object { const val TIMEOUT = 5000L const val LIMIT = 3 diff --git a/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt b/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt index 6322cd4..4e1f6fe 100644 --- a/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/mobile_labs/common/AppViewModelProvider.kt @@ -17,7 +17,7 @@ object AppViewModelProvider { PeopleListViewModel(theatreApplication().container.personRestRepository) } initializer { - EventListViewModel(theatreApplication().container.eventRepository) + EventListViewModel(theatreApplication().container.eventRestRepository) } initializer { PerformanceListViewModel(theatreApplication().container.performanceRestRepository) diff --git a/app/src/main/java/com/example/mobile_labs/database/event/repository/EventRepository.kt b/app/src/main/java/com/example/mobile_labs/common/EventRepository.kt similarity index 52% rename from app/src/main/java/com/example/mobile_labs/database/event/repository/EventRepository.kt rename to app/src/main/java/com/example/mobile_labs/common/EventRepository.kt index cbb03cc..3f3f026 100644 --- a/app/src/main/java/com/example/mobile_labs/database/event/repository/EventRepository.kt +++ b/app/src/main/java/com/example/mobile_labs/common/EventRepository.kt @@ -1,4 +1,4 @@ -package com.example.mobile_labs.database.event.repository +package com.example.mobile_labs.common import androidx.paging.PagingData import com.example.mobile_labs.database.event.model.Event @@ -6,10 +6,5 @@ import com.example.mobile_labs.database.event.model.EventWithPerformance import kotlinx.coroutines.flow.Flow interface EventRepository { - fun getAll(): Flow> fun getAllWithPerformance(): Flow> - fun getEvent(uid: Int): Flow - suspend fun insertEvent(event: Event) - suspend fun updateEvent(event: Event) - suspend fun deleteEvent(event: Event) } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/database/event/dao/EventDao.kt b/app/src/main/java/com/example/mobile_labs/database/event/dao/EventDao.kt index c5440f1..31ee127 100644 --- a/app/src/main/java/com/example/mobile_labs/database/event/dao/EventDao.kt +++ b/app/src/main/java/com/example/mobile_labs/database/event/dao/EventDao.kt @@ -26,11 +26,14 @@ interface EventDao { fun getByUid(uid: Int): Flow @Insert - suspend fun insert(event: Event) + suspend fun insert(vararg event: Event) @Update suspend fun update(event: Event) @Delete suspend fun delete(event: Event) + + @Query("DELETE FROM events") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/database/event/model/Event.kt b/app/src/main/java/com/example/mobile_labs/database/event/model/Event.kt index f89d212..b09a2ac 100644 --- a/app/src/main/java/com/example/mobile_labs/database/event/model/Event.kt +++ b/app/src/main/java/com/example/mobile_labs/database/event/model/Event.kt @@ -18,8 +18,8 @@ import java.time.LocalDate entity = Performance::class, parentColumns = ["performance_uid"], childColumns = ["performance_id"], - onDelete = ForeignKey.RESTRICT, - onUpdate = ForeignKey.RESTRICT, + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE, ), ]) data class Event( diff --git a/app/src/main/java/com/example/mobile_labs/database/event/repository/OfflineEventRepository.kt b/app/src/main/java/com/example/mobile_labs/database/event/repository/OfflineEventRepository.kt index 2839f9c..c2ac42a 100644 --- a/app/src/main/java/com/example/mobile_labs/database/event/repository/OfflineEventRepository.kt +++ b/app/src/main/java/com/example/mobile_labs/database/event/repository/OfflineEventRepository.kt @@ -3,14 +3,17 @@ package com.example.mobile_labs.database.event.repository import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import androidx.paging.PagingSource import com.example.mobile_labs.common.AppDataContainer +import com.example.mobile_labs.common.EventRepository import com.example.mobile_labs.database.event.dao.EventDao import com.example.mobile_labs.database.event.model.Event import com.example.mobile_labs.database.event.model.EventWithPerformance +import com.example.mobile_labs.database.performance.model.Performance import kotlinx.coroutines.flow.Flow class OfflineEventRepository(private val eventDao: EventDao) : EventRepository { - override fun getAll(): Flow> = eventDao.getAll(); + fun getAll(): Flow> = eventDao.getAll(); override fun getAllWithPerformance(): Flow> = Pager( config = PagingConfig( pageSize = AppDataContainer.LIMIT, @@ -19,11 +22,18 @@ class OfflineEventRepository(private val eventDao: EventDao) : EventRepository { pagingSourceFactory = eventDao::getAllWithPerformance ).flow - override fun getEvent(uid: Int): Flow = eventDao.getByUid(uid); + fun getEvent(uid: Int): Flow = eventDao.getByUid(uid); - override suspend fun insertEvent(event: Event) = eventDao.insert(event); + suspend fun insertEvent(event: Event) = eventDao.insert(event); - override suspend fun updateEvent(event: Event) = eventDao.update(event); + suspend fun updateEvent(event: Event) = eventDao.update(event); - override suspend fun deleteEvent(event: Event) = eventDao.delete(event); + suspend fun deleteEvent(event: Event) = eventDao.delete(event); + + suspend fun clearEvents() = eventDao.deleteAll() + + suspend fun insertEvents(events: List) = + eventDao.insert(*events.toTypedArray()) + + fun getAllEventsPagingSource(): PagingSource = eventDao.getAllWithPerformance() } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/database/performance/dao/PerformancePersonDao.kt b/app/src/main/java/com/example/mobile_labs/database/performance/dao/PerformancePersonDao.kt index 5384177..db3b97d 100644 --- a/app/src/main/java/com/example/mobile_labs/database/performance/dao/PerformancePersonDao.kt +++ b/app/src/main/java/com/example/mobile_labs/database/performance/dao/PerformancePersonDao.kt @@ -23,4 +23,7 @@ interface PerformancePersonDao { @Delete suspend fun delete(performancePersonCrossRef: PerformancePersonCrossRef) + + @Query("DELETE FROM performance_person") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/com/example/mobile_labs/database/performance/model/Performance.kt b/app/src/main/java/com/example/mobile_labs/database/performance/model/Performance.kt index fa4fc2e..00716fd 100644 --- a/app/src/main/java/com/example/mobile_labs/database/performance/model/Performance.kt +++ b/app/src/main/java/com/example/mobile_labs/database/performance/model/Performance.kt @@ -12,15 +12,15 @@ import java.io.Serializable entity = Person::class, parentColumns = ["uid"], childColumns = ["director_id"], - onDelete = ForeignKey.RESTRICT, - onUpdate = ForeignKey.RESTRICT, + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE, ), ForeignKey( entity = Person::class, parentColumns = ["uid"], childColumns = ["author_id"], - onDelete = ForeignKey.RESTRICT, - onUpdate = ForeignKey.RESTRICT, + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.CASCADE, ), ] ) diff --git a/app/src/main/java/com/example/mobile_labs/ui/event/list/EventListViewModel.kt b/app/src/main/java/com/example/mobile_labs/ui/event/list/EventListViewModel.kt index b7ef76b..294fbad 100644 --- a/app/src/main/java/com/example/mobile_labs/ui/event/list/EventListViewModel.kt +++ b/app/src/main/java/com/example/mobile_labs/ui/event/list/EventListViewModel.kt @@ -2,7 +2,7 @@ package com.example.mobile_labs.ui.event.list import androidx.lifecycle.ViewModel import androidx.paging.PagingData -import com.example.mobile_labs.database.event.repository.EventRepository +import com.example.mobile_labs.common.EventRepository import com.example.mobile_labs.database.event.model.EventWithPerformance import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/com/example/mobile_labs/ui/navigation/MainNavbar.kt b/app/src/main/java/com/example/mobile_labs/ui/navigation/MainNavbar.kt index 5ce5fb1..ee84fbe 100644 --- a/app/src/main/java/com/example/mobile_labs/ui/navigation/MainNavbar.kt +++ b/app/src/main/java/com/example/mobile_labs/ui/navigation/MainNavbar.kt @@ -104,7 +104,7 @@ fun Navhost( ) { NavHost( navController, - startDestination = Screen.Schedule.route, + startDestination = Screen.About.route, modifier.padding(innerPadding) ) { composable(Screen.Schedule.route) { EventList(navController) }