Add database support for multiple servers (schools)

This commit is contained in:
Matte23
2020-10-25 22:30:50 +01:00
parent 109c56d111
commit 4c09e91e99
20 changed files with 168 additions and 63 deletions

View File

@@ -37,6 +37,7 @@ class AlarmBroadcastReceiver : BroadcastReceiver() {
companion object { companion object {
const val CHANNEL_ID = "net.underdesk.circolapp.REMINDER" const val CHANNEL_ID = "net.underdesk.circolapp.REMINDER"
const val CIRCULAR_ID = "circular_id" const val CIRCULAR_ID = "circular_id"
const val SCHOOL_ID = "school_id"
} }
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
@@ -47,6 +48,10 @@ class AlarmBroadcastReceiver : BroadcastReceiver() {
intent.getLongExtra( intent.getLongExtra(
CIRCULAR_ID, CIRCULAR_ID,
0 0
),
intent.getIntExtra(
SCHOOL_ID,
0
) )
) )
createNotification( createNotification(

View File

@@ -24,7 +24,7 @@ import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
@Database(entities = [Circular::class], version = 1, exportSchema = false) @Database(entities = [Circular::class], version = 2, exportSchema = false)
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun circularDao(): CircularDao abstract fun circularDao(): CircularDao
@@ -42,7 +42,7 @@ abstract class AppDatabase : RoomDatabase() {
context, context,
AppDatabase::class.java, AppDatabase::class.java,
DATABASE_NAME DATABASE_NAME
).build().also { instance = it } ).fallbackToDestructiveMigration().build().also { instance = it }
} }
} }
} }

View File

@@ -20,13 +20,13 @@ package net.underdesk.circolapp.data
import android.os.Parcelable import android.os.Parcelable
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
@Parcelize @Parcelize
@Entity(tableName = "circulars") @Entity(tableName = "circulars", primaryKeys = ["id", "school"])
data class Circular( data class Circular(
@PrimaryKey val id: Long, val id: Long,
val school: Int,
val name: String, val name: String,
val url: String, val url: String,
val date: String, val date: String,

View File

@@ -23,29 +23,29 @@ import androidx.room.*
@Dao @Dao
interface CircularDao { interface CircularDao {
@Query("SELECT * FROM circulars ORDER BY id DESC") @Query("SELECT * FROM circulars WHERE school is :school ORDER BY id DESC")
fun getCirculars(): List<Circular> fun getCirculars(school: Int): List<Circular>
@Query("SELECT * FROM circulars ORDER BY id DESC") @Query("SELECT * FROM circulars WHERE school is :school ORDER BY id DESC")
fun getLiveCirculars(): LiveData<List<Circular>> fun getLiveCirculars(school: Int): LiveData<List<Circular>>
@Query("SELECT * FROM circulars WHERE name LIKE :query ORDER BY id DESC") @Query("SELECT * FROM circulars WHERE school is :school AND name LIKE :query ORDER BY id DESC")
fun searchCirculars(query: String): LiveData<List<Circular>> fun searchCirculars(query: String, school: Int): LiveData<List<Circular>>
@Query("SELECT * FROM circulars WHERE id = :id ORDER BY id DESC") @Query("SELECT * FROM circulars WHERE school is :school AND id = :id ORDER BY id DESC")
fun getCircular(id: Long): Circular fun getCircular(id: Long, school: Int): Circular
@Query("SELECT * FROM circulars WHERE favourite ORDER BY id DESC") @Query("SELECT * FROM circulars WHERE school is :school AND favourite ORDER BY id DESC")
fun getFavourites(): LiveData<List<Circular>> fun getFavourites(school: Int): LiveData<List<Circular>>
@Query("SELECT * FROM circulars WHERE favourite AND name LIKE :query ORDER BY id DESC") @Query("SELECT * FROM circulars WHERE school is :school AND favourite AND name LIKE :query ORDER BY id DESC")
fun searchFavourites(query: String): LiveData<List<Circular>> fun searchFavourites(query: String, school: Int): LiveData<List<Circular>>
@Query("SELECT * FROM circulars WHERE reminder ORDER BY id DESC") @Query("SELECT * FROM circulars WHERE school is :school AND reminder ORDER BY id DESC")
fun getReminders(): LiveData<List<Circular>> fun getReminders(school: Int): LiveData<List<Circular>>
@Query("SELECT * FROM circulars WHERE reminder AND name LIKE :query ORDER BY id DESC") @Query("SELECT * FROM circulars WHERE school is :school AND reminder AND name LIKE :query ORDER BY id DESC")
fun searchReminders(query: String): LiveData<List<Circular>> fun searchReminders(query: String, school: Int): LiveData<List<Circular>>
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
fun insertAll(circulars: List<Circular>) fun insertAll(circulars: List<Circular>)

View File

@@ -16,7 +16,7 @@ class CircularRepository(
if (result.second == ServerAPI.Companion.Result.ERROR) if (result.second == ServerAPI.Companion.Result.ERROR)
return@withContext Pair(emptyList(), false) return@withContext Pair(emptyList(), false)
val oldCirculars = circularDao.getCirculars() val oldCirculars = circularDao.getCirculars(serverAPI.serverID())
val newCirculars = result.first val newCirculars = result.first
if (newCirculars.size != oldCirculars.size) { if (newCirculars.size != oldCirculars.size) {

View File

@@ -48,7 +48,8 @@ class CircularLetterFragment :
CircularRepository.getInstance( CircularRepository.getInstance(
AppDatabase.getInstance(requireContext()).circularDao(), AppDatabase.getInstance(requireContext()).circularDao(),
ServerAPI.getInstance(requireContext()) ServerAPI.getInstance(requireContext())
) ),
requireActivity().application
) )
} }

View File

@@ -42,7 +42,8 @@ class FavouritesFragment : Fragment(), MainActivity.SearchCallback {
CircularRepository.getInstance( CircularRepository.getInstance(
AppDatabase.getInstance(requireContext()).circularDao(), AppDatabase.getInstance(requireContext()).circularDao(),
ServerAPI.getInstance(requireContext()) ServerAPI.getInstance(requireContext())
) ),
requireActivity().application
) )
} }

View File

@@ -112,7 +112,8 @@ class NewReminderFragment : DialogFragment() {
context, context,
circular.id.toInt(), circular.id.toInt(),
Intent(context, AlarmBroadcastReceiver::class.java) Intent(context, AlarmBroadcastReceiver::class.java)
.putExtra(AlarmBroadcastReceiver.CIRCULAR_ID, circular.id), .putExtra(AlarmBroadcastReceiver.CIRCULAR_ID, circular.id)
.putExtra(AlarmBroadcastReceiver.SCHOOL_ID, circular.school),
0 0
) )

View File

@@ -42,7 +42,8 @@ class RemindersFragment : Fragment(), MainActivity.SearchCallback {
CircularRepository.getInstance( CircularRepository.getInstance(
AppDatabase.getInstance(requireContext()).circularDao(), AppDatabase.getInstance(requireContext()).circularDao(),
ServerAPI.getInstance(requireContext()) ServerAPI.getInstance(requireContext())
) ),
requireActivity().application
) )
} }

View File

@@ -21,6 +21,7 @@ package net.underdesk.circolapp.server
import net.underdesk.circolapp.data.Circular import net.underdesk.circolapp.data.Circular
abstract class Server { abstract class Server {
abstract val serverID: Int
abstract suspend fun getCircularsFromServer(): Pair<List<Circular>, ServerAPI.Companion.Result> abstract suspend fun getCircularsFromServer(): Pair<List<Circular>, ServerAPI.Companion.Result>
abstract suspend fun newCircularsAvailable(): Pair<Boolean, ServerAPI.Companion.Result> abstract suspend fun newCircularsAvailable(): Pair<Boolean, ServerAPI.Companion.Result>
} }

View File

@@ -27,6 +27,8 @@ import net.underdesk.circolapp.server.porporato.PorporatoServer
class ServerAPI( class ServerAPI(
private var server: Server private var server: Server
) { ) {
fun serverID(): Int = server.serverID
suspend fun getCircularsFromServer(): Pair<List<Circular>, Result> { suspend fun getCircularsFromServer(): Pair<List<Circular>, Result> {
val newCircularsAvailable = server.newCircularsAvailable() val newCircularsAvailable = server.newCircularsAvailable()
@@ -52,6 +54,10 @@ class ServerAPI(
SUCCESS, ERROR SUCCESS, ERROR
} }
fun getServerId(server: Servers): Int {
return Servers.values().indexOf(server)
}
fun getServerName(server: Servers) = when (server) { fun getServerName(server: Servers) = when (server) {
Servers.CURIE -> "Liceo scientifico Maria Curie" Servers.CURIE -> "Liceo scientifico Maria Curie"
Servers.PORPORATO -> "Liceo G.F. Porporato" Servers.PORPORATO -> "Liceo G.F. Porporato"

View File

@@ -18,6 +18,8 @@ class CurieServer : Server() {
private val responseAdapter = moshi.adapter(Response::class.java) private val responseAdapter = moshi.adapter(Response::class.java)
private val client = OkHttpClient() private val client = OkHttpClient()
override val serverID = ServerAPI.getServerId(ServerAPI.Companion.Servers.CURIE)
override suspend fun getCircularsFromServer(): Pair<List<Circular>, ServerAPI.Companion.Result> { override suspend fun getCircularsFromServer(): Pair<List<Circular>, ServerAPI.Companion.Result> {
return try { return try {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
@@ -85,9 +87,9 @@ class CurieServer : Server() {
.removePrefix("_") .removePrefix("_")
.removePrefix(" ") .removePrefix(" ")
Circular(id.toLong(), title, url, matcherDate.group(1) ?: "") Circular(id.toLong(), serverID, title, url, matcherDate.group(1) ?: "")
} else { } else {
Circular(id.toLong(), title, url, "") Circular(id.toLong(), serverID, title, url, "")
} }
} }

View File

@@ -30,6 +30,8 @@ class PorporatoServer : Server() {
"https://www.liceoporporato.edu.it/ARCHIVIO/PR/VP/circolari.php?dirname=CIRCOLARIP/- CIRCOLARI 2020-21/-12-Agosto/" "https://www.liceoporporato.edu.it/ARCHIVIO/PR/VP/circolari.php?dirname=CIRCOLARIP/- CIRCOLARI 2020-21/-12-Agosto/"
) )
override val serverID = ServerAPI.getServerId(ServerAPI.Companion.Servers.PORPORATO)
override suspend fun getCircularsFromServer(): Pair<List<Circular>, ServerAPI.Companion.Result> { override suspend fun getCircularsFromServer(): Pair<List<Circular>, ServerAPI.Companion.Result> {
return try { return try {
val list = arrayListOf<Circular>() val list = arrayListOf<Circular>()
@@ -150,9 +152,9 @@ class PorporatoServer : Server() {
title = title.removeRange(matcherDate.start(), matcherDate.end()) title = title.removeRange(matcherDate.start(), matcherDate.end())
.removeSuffix(" (pubb.: )") .removeSuffix(" (pubb.: )")
Circular(id, title, fullUrl, matcherDate.group(1)?.replace("-", "/") ?: "") Circular(id, serverID, title, fullUrl, matcherDate.group(1)?.replace("-", "/") ?: "")
} else { } else {
Circular(id, title, fullUrl, "") Circular(id, serverID, title, fullUrl, "")
} }
} }
} }

View File

@@ -0,0 +1,12 @@
package net.underdesk.circolapp.utils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
// Taken from https://stackoverflow.com/questions/49493772/mediatorlivedata-or-switchmap-transformation-with-multiple-parameters
class DoubleTrigger<A, B>(a: LiveData<A>, b: LiveData<B>) : MediatorLiveData<Pair<A?, B?>>() {
init {
addSource(a) { value = it to b.value }
addSource(b) { value = a.value to it }
}
}

View File

@@ -18,24 +18,40 @@
package net.underdesk.circolapp.viewmodels package net.underdesk.circolapp.viewmodels
import android.app.Application
import android.content.SharedPreferences
import androidx.lifecycle.* import androidx.lifecycle.*
import androidx.preference.PreferenceManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.underdesk.circolapp.data.Circular import net.underdesk.circolapp.data.Circular
import net.underdesk.circolapp.data.CircularRepository import net.underdesk.circolapp.data.CircularRepository
import net.underdesk.circolapp.utils.DoubleTrigger
class CircularLetterViewModel internal constructor( class CircularLetterViewModel internal constructor(
private val circularRepository: CircularRepository private val circularRepository: CircularRepository,
) : ViewModel() { application: Application
) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener {
private val preferenceManager = PreferenceManager.getDefaultSharedPreferences(application)
private val schoolID =
MutableLiveData(preferenceManager.getString("school", "0")?.toInt() ?: 0)
init { init {
updateCirculars() updateCirculars()
preferenceManager.registerOnSharedPreferenceChangeListener(this)
} }
val query = MutableLiveData("") val query = MutableLiveData("")
val circulars: LiveData<List<Circular>> = Transformations.switchMap(query) { input -> val circulars: LiveData<List<Circular>> =
if (input == null || input == "") { Transformations.switchMap(DoubleTrigger(query, schoolID)) { input ->
circularRepository.circularDao.getLiveCirculars() if (input.first == null || input.first == "") {
circularRepository.circularDao.getLiveCirculars(input.second ?: 0)
} else { } else {
circularRepository.circularDao.searchCirculars("%$input%") circularRepository.circularDao.searchCirculars(
"%${input.first}%",
input.second ?: 0
)
} }
} }
@@ -57,4 +73,9 @@ class CircularLetterViewModel internal constructor(
} }
} }
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == "school")
schoolID.postValue(preferenceManager.getString("school", "0")?.toInt() ?: 0)
}
} }

View File

@@ -1,15 +1,17 @@
package net.underdesk.circolapp.viewmodels package net.underdesk.circolapp.viewmodels
import android.app.Application
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import net.underdesk.circolapp.data.CircularRepository import net.underdesk.circolapp.data.CircularRepository
class CircularLetterViewModelFactory( class CircularLetterViewModelFactory(
private val circularRepository: CircularRepository private val circularRepository: CircularRepository,
) : ViewModelProvider.Factory { val application: Application
) : ViewModelProvider.AndroidViewModelFactory(application) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CircularLetterViewModel(circularRepository) as T return CircularLetterViewModel(circularRepository, application) as T
} }
} }

View File

@@ -18,22 +18,45 @@
package net.underdesk.circolapp.viewmodels package net.underdesk.circolapp.viewmodels
import android.app.Application
import android.content.SharedPreferences
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel import androidx.preference.PreferenceManager
import net.underdesk.circolapp.data.Circular import net.underdesk.circolapp.data.Circular
import net.underdesk.circolapp.data.CircularRepository import net.underdesk.circolapp.data.CircularRepository
import net.underdesk.circolapp.utils.DoubleTrigger
class FavouritesViewModel internal constructor( class FavouritesViewModel internal constructor(
private val circularRepository: CircularRepository private val circularRepository: CircularRepository,
) : ViewModel() { application: Application
) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener {
private val preferenceManager = PreferenceManager.getDefaultSharedPreferences(application)
private val schoolID =
MutableLiveData(preferenceManager.getString("school", "0")?.toInt() ?: 0)
init {
preferenceManager.registerOnSharedPreferenceChangeListener(this)
}
val query = MutableLiveData("") val query = MutableLiveData("")
val circulars: LiveData<List<Circular>> = Transformations.switchMap(query) { input -> val circulars: LiveData<List<Circular>> =
if (input == null || input == "") { Transformations.switchMap(DoubleTrigger(query, schoolID)) { input ->
circularRepository.circularDao.getFavourites() if (input.first == null || input.first == "") {
circularRepository.circularDao.getFavourites(input.second ?: 0)
} else { } else {
circularRepository.circularDao.searchFavourites("%$input%") circularRepository.circularDao.searchFavourites(
"%${input.first}%",
input.second ?: 0
)
} }
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == "school")
schoolID.postValue(preferenceManager.getString("school", "0")?.toInt() ?: 0)
}
} }

View File

@@ -1,15 +1,17 @@
package net.underdesk.circolapp.viewmodels package net.underdesk.circolapp.viewmodels
import android.app.Application
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import net.underdesk.circolapp.data.CircularRepository import net.underdesk.circolapp.data.CircularRepository
class FavouritesViewModelFactory( class FavouritesViewModelFactory(
private val circularRepository: CircularRepository private val circularRepository: CircularRepository,
) : ViewModelProvider.Factory { val application: Application
) : ViewModelProvider.AndroidViewModelFactory(application) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return FavouritesViewModel(circularRepository) as T return FavouritesViewModel(circularRepository, application) as T
} }
} }

View File

@@ -18,22 +18,45 @@
package net.underdesk.circolapp.viewmodels package net.underdesk.circolapp.viewmodels
import android.app.Application
import android.content.SharedPreferences
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel import androidx.preference.PreferenceManager
import net.underdesk.circolapp.data.Circular import net.underdesk.circolapp.data.Circular
import net.underdesk.circolapp.data.CircularRepository import net.underdesk.circolapp.data.CircularRepository
import net.underdesk.circolapp.utils.DoubleTrigger
class RemindersViewModel internal constructor( class RemindersViewModel internal constructor(
private val circularRepository: CircularRepository private val circularRepository: CircularRepository,
) : ViewModel() { application: Application
) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener {
private val preferenceManager = PreferenceManager.getDefaultSharedPreferences(application)
private val schoolID =
MutableLiveData(preferenceManager.getString("school", "0")?.toInt() ?: 0)
init {
preferenceManager.registerOnSharedPreferenceChangeListener(this)
}
val query = MutableLiveData("") val query = MutableLiveData("")
val circulars: LiveData<List<Circular>> = Transformations.switchMap(query) { input -> val circulars: LiveData<List<Circular>> =
if (input == null || input == "") { Transformations.switchMap(DoubleTrigger(query, schoolID)) { input ->
circularRepository.circularDao.getReminders() if (input.first == null || input.first == "") {
circularRepository.circularDao.getReminders(input.second ?: 0)
} else { } else {
circularRepository.circularDao.searchReminders("%$input%") circularRepository.circularDao.searchReminders(
"%${input.first}%",
input.second ?: 0
)
} }
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == "school")
schoolID.postValue(preferenceManager.getString("school", "0")?.toInt() ?: 0)
}
} }

View File

@@ -1,15 +1,17 @@
package net.underdesk.circolapp.viewmodels package net.underdesk.circolapp.viewmodels
import android.app.Application
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import net.underdesk.circolapp.data.CircularRepository import net.underdesk.circolapp.data.CircularRepository
class RemindersViewModelFactory( class RemindersViewModelFactory(
private val circularRepository: CircularRepository private val circularRepository: CircularRepository,
) : ViewModelProvider.Factory { val application: Application
) : ViewModelProvider.AndroidViewModelFactory(application) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RemindersViewModel(circularRepository) as T return RemindersViewModel(circularRepository, application) as T
} }
} }