diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index ac823c7..383f5de 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -7,11 +7,11 @@
-
+
-
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 66803e2..9815ed3 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -2,6 +2,7 @@ plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
+ id("org.jetbrains.kotlin.plugin.serialization")
}
android {
@@ -78,6 +79,13 @@ dependencies {
implementation("androidx.paging:paging-compose:3.2.1")
+ // retrofit
+ val retrofitVersion = "2.9.0"
+ implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
+ implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
+ implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
+
//Tests
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7f574bd..f6fa257 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,6 +15,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.Mobile_Labs"
android:name=".TheatreApplication"
+ android:usesCleartextTraffic="true"
tools:targetApi="31">
+
+ @GET("performances")
+ suspend fun getPerformances(
+ @Query("_page") page: Int,
+ @Query("_limit") limit: Int,
+ ): List
+
+ @GET("events")
+ suspend fun getEvents(
+ @Query("_page") page: Int,
+ @Query("_limit") limit: Int,
+ ): List
+
+ @GET("performances/{id}")
+ suspend fun getPerformance(
+ @Path("id") id: Int,
+ ): PerformanceRemote
+
+ companion object {
+ private const val BASE_URL = "http://10.0.2.2:26000/"
+
+ @Volatile
+ private var INSTANCE: MyServerService? = null
+
+ fun getInstance(): MyServerService {
+ return INSTANCE ?: synchronized(this) {
+ val logger = HttpLoggingInterceptor()
+ logger.level = HttpLoggingInterceptor.Level.BASIC
+ val client = OkHttpClient.Builder()
+ .addInterceptor(logger)
+ .build()
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(client)
+ .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
+ .build()
+ .create(MyServerService::class.java)
+ .also { INSTANCE = it }
+ }
+ }
+ }
+}
\ 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
new file mode 100644
index 0000000..3320fac
--- /dev/null
+++ b/app/src/main/java/com/example/mobile_labs/api/models/EventRemote.kt
@@ -0,0 +1,18 @@
+package com.example.mobile_labs.api.models
+
+import com.example.mobile_labs.database.event.model.Event
+import kotlinx.serialization.Serializable
+import java.time.LocalDate
+
+@Serializable
+data class EventRemote(
+ val id: Int = 0,
+ val date: String = "",
+ val performanceId: Int = 0,
+)
+
+fun EventRemote.toEvent(): Event = Event(
+ id,
+ LocalDate.parse(date),
+ performanceId
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt b/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt
new file mode 100644
index 0000000..5e1728f
--- /dev/null
+++ b/app/src/main/java/com/example/mobile_labs/api/models/PerformanceRemote.kt
@@ -0,0 +1,26 @@
+package com.example.mobile_labs.api.models
+
+import com.example.mobile_labs.database.performance.model.Performance
+import com.example.mobile_labs.database.person.model.Person
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class PerformanceRemote(
+ val id: Int = 0,
+ val title: String = "",
+ val description: String = "",
+ val authorId: Int = 0,
+ val directorId: Int = 0,
+ val imageURL: String = "",
+ val previewImageURL: String = ""
+)
+
+fun PerformanceRemote.toPerformance(): Performance = Performance(
+ id,
+ title,
+ description,
+ authorId,
+ directorId,
+ imageURL,
+ previewImageURL
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/mobile_labs/api/models/PersonRemote.kt b/app/src/main/java/com/example/mobile_labs/api/models/PersonRemote.kt
new file mode 100644
index 0000000..fa4da4b
--- /dev/null
+++ b/app/src/main/java/com/example/mobile_labs/api/models/PersonRemote.kt
@@ -0,0 +1,21 @@
+package com.example.mobile_labs.api.models
+
+import com.example.mobile_labs.database.event.model.Event
+import com.example.mobile_labs.database.person.model.Person
+import kotlinx.serialization.Serializable
+import java.time.LocalDate
+
+@Serializable
+data class PersonRemote(
+ val id: Int = 0,
+ val last_name: String = "",
+ val first_name: String = "",
+ val imageURL: String = ""
+)
+
+fun PersonRemote.toPerson(): Person = Person(
+ id,
+ last_name,
+ first_name,
+ imageURL
+)
\ 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
new file mode 100644
index 0000000..115a67c
--- /dev/null
+++ b/app/src/main/java/com/example/mobile_labs/api/people/PeopleRemoteMediator.kt
@@ -0,0 +1,107 @@
+package com.example.mobile_labs.api.people
+
+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.toPerson
+import com.example.mobile_labs.database.AppDatabase
+import com.example.mobile_labs.database.person.model.Person
+import com.example.mobile_labs.database.person.repository.OfflinePersonRepository
+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 PeopleRemoteMediator(
+ private val service: MyServerService,
+ private val dbPersonRepository: OfflinePersonRepository,
+ private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
+ 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 people = service.getPeople(page, state.config.pageSize).map { it.toPerson() }
+ val endOfPaginationReached = people.isEmpty()
+ database.withTransaction {
+ if (loadType == LoadType.REFRESH) {
+ dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.PERSON)
+ dbPersonRepository.clearPeople()
+ }
+ val prevKey = if (page == 1) null else page - 1
+ val nextKey = if (endOfPaginationReached) null else page + 1
+ val keys = people.map {
+ RemoteKeys(
+ entityId = it.uid !!,
+ type = RemoteKeyType.PERSON,
+ prevKey = prevKey,
+ nextKey = nextKey
+ )
+ }
+ dbRemoteKeyRepository.createRemoteKeys(keys)
+ dbPersonRepository.insertPeople(people)
+ }
+ 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 { person ->
+ dbRemoteKeyRepository.getAllRemoteKeys(person.uid !!, RemoteKeyType.PERSON)
+ }
+ }
+
+ private suspend fun getRemoteKeyForFirstItem(state: PagingState): RemoteKeys? {
+ return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
+ ?.let { person ->
+ dbRemoteKeyRepository.getAllRemoteKeys(person.uid !!, RemoteKeyType.PERSON)
+ }
+ }
+
+ private suspend fun getRemoteKeyClosestToCurrentPosition(
+ state: PagingState
+ ): RemoteKeys? {
+ return state.anchorPosition?.let { position ->
+ state.closestItemToPosition(position)?.uid?.let { personUid ->
+ dbRemoteKeyRepository.getAllRemoteKeys(personUid, RemoteKeyType.PERSON)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/mobile_labs/api/people/RestPersonRepository.kt b/app/src/main/java/com/example/mobile_labs/api/people/RestPersonRepository.kt
new file mode 100644
index 0000000..e7a3729
--- /dev/null
+++ b/app/src/main/java/com/example/mobile_labs/api/people/RestPersonRepository.kt
@@ -0,0 +1,42 @@
+package com.example.mobile_labs.api.people
+
+import android.util.Log
+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.common.AppDataContainer
+import com.example.mobile_labs.common.PersonRepository
+import com.example.mobile_labs.database.AppDatabase
+import com.example.mobile_labs.database.person.model.Person
+import com.example.mobile_labs.database.person.repository.OfflinePersonRepository
+import com.example.mobile_labs.database.remotekeys.repository.OfflineRemoteKeyRepository
+import kotlinx.coroutines.flow.Flow
+
+class RestPersonRepository(
+ private val service: MyServerService,
+ private val dbPersonRepository: OfflinePersonRepository,
+ private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
+ private val database: AppDatabase
+) : PersonRepository {
+ override fun getAllPeople(): Flow> {
+
+ val pagingSourceFactory = { dbPersonRepository.getAllPeoplePagingSource() }
+
+ @OptIn(ExperimentalPagingApi::class)
+ return Pager(
+ config = PagingConfig(
+ pageSize = AppDataContainer.LIMIT,
+ enablePlaceholders = false
+ ),
+ remoteMediator = PeopleRemoteMediator(
+ service,
+ dbPersonRepository,
+ dbRemoteKeyRepository,
+ database,
+ ),
+ pagingSourceFactory = pagingSourceFactory
+ ).flow
+ }
+}
\ No newline at end of file
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 872b32c..ddf580e 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
@@ -1,14 +1,18 @@
package com.example.mobile_labs.common
import android.content.Context
+import com.example.mobile_labs.api.MyServerService
+import com.example.mobile_labs.api.people.RestPersonRepository
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 personRepository: PersonRepository
+ val personRestRepository: RestPersonRepository
val eventRepository: EventRepository
val performanceRepository: PerformanceRepository
}
@@ -22,9 +26,21 @@ class AppDataContainer(private val context: Context) : AppContainer {
OfflinePerformanceRepository(AppDatabase.getInstance(context).performanceDao())
}
- override val personRepository: PersonRepository by lazy {
+ val personRepository: OfflinePersonRepository by lazy {
OfflinePersonRepository(AppDatabase.getInstance(context).personDao())
}
+ val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
+ OfflineRemoteKeyRepository(AppDatabase.getInstance(context).remoteKeysDao())
+ }
+
+ override val personRestRepository: RestPersonRepository by lazy {
+ RestPersonRepository(
+ MyServerService.getInstance(),
+ personRepository,
+ remoteKeyRepository,
+ AppDatabase.getInstance(context)
+ )
+ }
companion object {
const val TIMEOUT = 5000L
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 6ddee02..a66a63a 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
@@ -14,7 +14,7 @@ import com.example.mobile_labs.ui.person.list.PeopleListViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
- PeopleListViewModel(theatreApplication().container.personRepository)
+ PeopleListViewModel(theatreApplication().container.personRestRepository)
}
initializer {
EventListViewModel(theatreApplication().container.eventRepository)
diff --git a/app/src/main/java/com/example/mobile_labs/common/PersonRepository.kt b/app/src/main/java/com/example/mobile_labs/common/PersonRepository.kt
index 8a6f5b1..764fa8f 100644
--- a/app/src/main/java/com/example/mobile_labs/common/PersonRepository.kt
+++ b/app/src/main/java/com/example/mobile_labs/common/PersonRepository.kt
@@ -6,8 +6,4 @@ import kotlinx.coroutines.flow.Flow
interface PersonRepository {
fun getAllPeople(): Flow>
- fun getPerson(uid: Int): Flow
- suspend fun insertPerson(person: Person)
- suspend fun updatePerson(person: Person)
- suspend fun deletePerson(person: Person)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/mobile_labs/database/person/dao/PersonDao.kt b/app/src/main/java/com/example/mobile_labs/database/person/dao/PersonDao.kt
index 462f81f..feb4a40 100644
--- a/app/src/main/java/com/example/mobile_labs/database/person/dao/PersonDao.kt
+++ b/app/src/main/java/com/example/mobile_labs/database/person/dao/PersonDao.kt
@@ -18,11 +18,14 @@ interface PersonDao {
fun getByUid(uid: Int): Flow
@Insert
- suspend fun insert(person: Person)
+ suspend fun insert(vararg person: Person)
@Update
suspend fun update(person: Person)
@Delete
suspend fun delete(person: Person)
+
+ @Query("DELETE FROM people")
+ suspend fun deleteAll()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/mobile_labs/database/person/repository/OfflinePersonRepository.kt b/app/src/main/java/com/example/mobile_labs/database/person/repository/OfflinePersonRepository.kt
index 2f45a3d..4fc1d8b 100644
--- a/app/src/main/java/com/example/mobile_labs/database/person/repository/OfflinePersonRepository.kt
+++ b/app/src/main/java/com/example/mobile_labs/database/person/repository/OfflinePersonRepository.kt
@@ -3,6 +3,7 @@ package com.example.mobile_labs.database.person.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
+import androidx.paging.PagingSource
import com.example.mobile_labs.common.PersonRepository
import com.example.mobile_labs.common.AppDataContainer
import com.example.mobile_labs.database.person.dao.PersonDao
@@ -17,12 +18,18 @@ class OfflinePersonRepository(private val personDao: PersonDao) : PersonReposito
),
pagingSourceFactory = personDao::getAll
).flow;
- override fun getPerson(uid: Int): Flow = personDao.getByUid(uid);
+ fun getPerson(uid: Int): Flow = personDao.getByUid(uid);
- override suspend fun insertPerson(person: Person) = personDao.insert(person);
+ suspend fun insertPerson(person: Person) = personDao.insert(person);
- override suspend fun updatePerson(person: Person) = personDao.update(person);
+ suspend fun updatePerson(person: Person) = personDao.update(person);
- override suspend fun deletePerson(person: Person) = personDao.delete(person);
+ suspend fun deletePerson(person: Person) = personDao.delete(person);
+ suspend fun clearPeople() = personDao.deleteAll()
+
+ suspend fun insertPeople(people: List) =
+ personDao.insert(*people.toTypedArray())
+
+ fun getAllPeoplePagingSource(): PagingSource = personDao.getAll()
}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index bd1eb8a..04a426d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,4 +3,5 @@ plugins {
id("com.android.application") version "8.1.1" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id("com.google.devtools.ksp") version "1.8.20-1.0.11" apply false
+ id("org.jetbrains.kotlin.plugin.serialization") version "1.8.20" apply false
}
\ No newline at end of file