Use live paged data for AccountActivity

This commit is contained in:
Ricki Hirner 2019-04-03 17:38:51 +02:00
parent 6af50dbc43
commit cdec545568
8 changed files with 251 additions and 169 deletions

View file

@ -99,7 +99,6 @@ class DavService: android.app.Service() {
interface RefreshingStatusListener {
fun onDavRefreshStatusChanged(id: Long, refreshing: Boolean)
fun onDavRefreshFinished(id: Long)
}
private val binder = InfoBinder()
@ -147,13 +146,12 @@ class DavService: android.app.Service() {
val service = db.serviceDao().getById(serviceId) ?: throw IllegalArgumentException("Service not found")
val account = Account(service.accountName, getString(R.string.account_type))
val oldHomeSets = homeSetDao.getByService(serviceId)
val oldCollections = collectionDao.getByService(serviceId)
val homeSets = oldHomeSets.toMutableList()
val homeSets = homeSetDao.getByService(serviceId)
.map { it.url }
.toMutableSet()
val collections = mutableMapOf<HttpUrl, Collection>()
oldCollections.forEach {
collectionDao.getByService(serviceId).forEach {
collections[it.url] = it
}
@ -205,7 +203,7 @@ class DavService: android.app.Service() {
response[AddressbookHomeSet::class.java]?.let { homeSet ->
for (href in homeSet.hrefs)
dav.location.resolve(href)?.let {
homeSets += HomeSet(0, serviceId, UrlUtils.withTrailingSlash(it))
homeSets += UrlUtils.withTrailingSlash(it)
}
}
@ -224,7 +222,7 @@ class DavService: android.app.Service() {
response[CalendarHomeSet::class.java]?.let { homeSet ->
for (href in homeSet.hrefs)
dav.location.resolve(href)?.let {
homeSets += HomeSet(0, serviceId, UrlUtils.withTrailingSlash(it))
homeSets += UrlUtils.withTrailingSlash(it)
}
}
@ -247,7 +245,7 @@ class DavService: android.app.Service() {
@Transaction
fun saveResults() {
homeSetDao.deleteByService(serviceId)
homeSetDao.insert(homeSets.onEach { it.serviceId = serviceId })
homeSetDao.insert(homeSets.map { HomeSet(0, serviceId, it) })
collectionDao.deleteByService(serviceId)
val records = collections.values.toList()
@ -283,7 +281,7 @@ class DavService: android.app.Service() {
// now refresh collections (taken from home sets)
val itHomeSets = homeSets.iterator()
while (itHomeSets.hasNext()) {
val homeSetUrl = itHomeSets.next().url
val homeSetUrl = itHomeSets.next()
Logger.log.fine("Listing home set $homeSetUrl")
try {
@ -364,7 +362,6 @@ class DavService: android.app.Service() {
runningRefresh.remove(serviceId)
refreshingStatusListeners.mapNotNull { it.get() }.forEach {
it.onDavRefreshStatusChanged(serviceId, false)
it.onDavRefreshFinished(serviceId)
}
}

View file

@ -189,10 +189,10 @@ class HttpClient private constructor(
certificateAlias?.let { alias ->
val context = requireNotNull(context)
// get client certificate and private key
// get provider certificate and private key
val certs = KeyChain.getCertificateChain(context, alias) ?: return@let
val key = KeyChain.getPrivateKey(context, alias) ?: return@let
logger.fine("Using client certificate $alias for authentication (chain length: ${certs.size})")
logger.fine("Using provider certificate $alias for authentication (chain length: ${certs.size})")
// create Android KeyStore (performs key operations without revealing secret data to DAVx5)
val keyStore = KeyStore.getInstance("AndroidKeyStore")
@ -217,7 +217,7 @@ class HttpClient private constructor(
}
}
} catch (e: Exception) {
logger.log(Level.SEVERE, "Couldn't set up client certificate authentication", e)
logger.log(Level.SEVERE, "Couldn't set up provider certificate authentication", e)
}
orig.sslSocketFactory(CertTlsSocketFactory(keyManager, trustManager), trustManager)

View file

@ -30,7 +30,7 @@ abstract class AppDatabase: RoomDatabase() {
fun getInstance(context: Context): AppDatabase {
INSTANCE?.let { return it }
val db = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "services.db")
val db = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "services.database")
.fallbackToDestructiveMigrationFrom(1, 2)
.fallbackToDestructiveMigrationOnDowngrade()
.addMigrations(Migration5_6)

View file

@ -140,9 +140,6 @@ data class Collection(
@Ignore
var confirmed: Boolean = false
@Ignore
var uiEnabled: Boolean = true
fun title() = displayName ?: DavUtils.lastSegmentOfUrl(url)

View file

@ -1,5 +1,6 @@
package at.bitfire.davdroid.model
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
@ -11,6 +12,9 @@ interface CollectionDao {
@Query("SELECT * FROM collection WHERE serviceId=:serviceId")
fun getByService(serviceId: Long): List<Collection>
@Query("SELECT * FROM collection WHERE serviceId=:serviceId AND type=:type")
fun observeByServiceAndType(serviceId: Long, type: String): LiveData<List<Collection>>
@Query("SELECT * FROM collection WHERE serviceId=:serviceId AND supportsVEVENT AND sync")
fun getSyncCalendars(serviceId: Long): List<Collection>

View file

@ -1,5 +1,6 @@
package at.bitfire.davdroid.model
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
@ -8,6 +9,9 @@ interface HomeSetDao {
@Query("SELECT * FROM homeset WHERE serviceId=:serviceId")
fun getByService(serviceId: Long): List<HomeSet>
@Query("SELECT * FROM homeset WHERE serviceId=:serviceId")
fun observeByService(serviceId: Long): LiveData<List<HomeSet>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertOrReplace(homeSet: HomeSet): Long

View file

@ -1,6 +1,10 @@
package at.bitfire.davdroid.model
import androidx.room.*
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface ServiceDao {
@ -8,6 +12,9 @@ interface ServiceDao {
@Query("SELECT * FROM service WHERE accountName=:accountName")
fun getByAccount(accountName: String): List<Service>
@Query("SELECT * FROM service WHERE accountName=:accountName")
fun observeByAccount(accountName: String): LiveData<List<Service>>
@Query("SELECT * FROM service WHERE accountName=:accountName AND type=:type")
fun getByAccountAndType(accountName: String, type: String): Service?

View file

@ -14,6 +14,7 @@ import android.accounts.AccountManager
import android.app.Application
import android.content.*
import android.content.pm.PackageManager
import android.database.ContentObserver
import android.net.Uri
import android.os.*
import android.provider.CalendarContract
@ -22,21 +23,20 @@ import android.provider.Settings
import android.view.*
import android.widget.*
import androidx.annotation.MainThread
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.*
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import at.bitfire.davdroid.*
import at.bitfire.davdroid.R
import at.bitfire.davdroid.databinding.ActivityAccountBinding
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.model.AppDatabase
@ -46,8 +46,10 @@ import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.resource.LocalTaskList
import at.bitfire.ical4android.TaskProvider
import com.google.android.material.snackbar.Snackbar
import java.util.*
import java.util.concurrent.Executors
import java.util.logging.Level
import kotlin.concurrent.thread
class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener {
@ -70,8 +72,9 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
binding.model = model
// account may be a DAVx5 address book account -> use main account in this case
model.initialize(LocalAddressBook.mainAccount(this,
requireNotNull(intent.getParcelableExtra(EXTRA_ACCOUNT))))
val account = LocalAddressBook.mainAccount(this,
requireNotNull(intent.getParcelableExtra(EXTRA_ACCOUNT)))
model.initialize(account)
title = model.account.name
val icMenu = AppCompatResources.getDrawable(this, R.drawable.ic_menu_light)
@ -118,6 +121,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
}
model.requiredPermissions.observe(this, Observer { permissions ->
Logger.log.fine("Required permissions: $permissions")
val askPermissions = permissions.filter { ActivityCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }
if (askPermissions.isNotEmpty())
ActivityCompat.requestPermissions(this, askPermissions.toTypedArray(), 0)
@ -236,15 +240,10 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
popup.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.force_read_only -> {
info.uiEnabled = false
//info.uiEnabled = false
adapter.notifyItemChanged(position)
val nowForceReadOnly = !info.forceReadOnly
model.updateCollectionReadOnly(info, nowForceReadOnly) {
info.forceReadOnly = nowForceReadOnly
info.uiEnabled = true
adapter.notifyItemChanged(position)
}
model.updateCollectionReadOnly(info, !info.forceReadOnly)
}
R.id.delete_collection ->
DeleteCollectionFragment.newInstance(model.account, info.id).show(supportFragmentManager, null)
@ -260,7 +259,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
}
fun reload() {
model.reload()
// TODO handle new permissions
}
@ -290,25 +289,11 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
val info = model.addressBooks.value?.get(position) ?: return
val v = holder.itemView
if (info.uiEnabled)
v.setOnClickListener {
info.uiEnabled = false
notifyItemChanged(position)
val nowChecked = !info.sync
model.updateCollectionSelected(info, nowChecked) {
info.sync = nowChecked
v.findViewById<CheckBox>(R.id.checked).isChecked = nowChecked
info.uiEnabled = true
notifyItemChanged(position)
}
}
else
v.setOnClickListener(null)
v.setOnClickListener {
model.updateCollectionSelected(info, !info.sync)
}
v.findViewById<CheckBox>(R.id.checked).apply {
isEnabled = info.uiEnabled
isChecked = info.sync
}
@ -328,12 +313,9 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
if (!info.privWriteContent || info.forceReadOnly) View.VISIBLE else View.GONE
v.findViewById<ImageView>(R.id.action_overflow).apply {
if (info.uiEnabled)
setOnClickListener { view ->
activity.onActionOverflowListener(view, info, this@AddressBookAdapter, position)
}
else
setOnClickListener(null)
setOnClickListener { view ->
activity.onActionOverflowListener(view, info, this@AddressBookAdapter, position)
}
}
}
@ -365,7 +347,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
val info = data().value?.get(position) ?: return
val v = holder.itemView
val enabled = (info.sync || info.supportsVEVENT != false || info.supportsVTODO != false) && info.uiEnabled
val enabled = info.sync || info.supportsVEVENT != false || info.supportsVTODO != false
if (enabled)
v.setOnClickListener {
onClickListener(it, position, info)
@ -410,28 +392,14 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
if (info.type == Collection.TYPE_WEBCAL)
overflow.visibility = View.INVISIBLE
else {
if (info.uiEnabled)
overflow.setOnClickListener { view ->
activity.onActionOverflowListener(view, info, this@CalendarAdapter, position)
}
else
overflow.setOnClickListener(null)
overflow.setOnClickListener { view ->
activity.onActionOverflowListener(view, info, this@CalendarAdapter, position)
}
}
}
open fun onClickListener(parent: View, position: Int, info: Collection)
{
info.uiEnabled = false
notifyItemChanged(position)
val nowChecked = !info.sync
model.updateCollectionSelected(info, nowChecked) {
info.sync = nowChecked
parent.findViewById<CheckBox>(R.id.checked).isChecked = nowChecked
info.uiEnabled = true
notifyItemChanged(position)
}
open fun onClickListener(parent: View, position: Int, info: Collection) {
model.updateCollectionSelected(info, !info.sync)
}
}
@ -473,16 +441,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
}
} else {
// unsubscribe from Webcal feed
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_CALENDAR) == PackageManager.PERMISSION_GRANTED)
activity.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)?.let { provider ->
try {
provider.delete(CalendarContract.Calendars.CONTENT_URI, "${CalendarContract.Calendars.NAME}=?", arrayOf(info.source))
activity.reload()
} finally {
provider.closeCompat()
}
}
// TODO model.webcals.unsubscribe(info)
}
}
@ -530,24 +489,56 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
val context: Application
): AndroidViewModel(context), SyncStatusObserver, DavService.RefreshingStatusListener {
private var initialized = false
lateinit var account: Account
val accountName = MutableLiveData<String>()
val cardDavServiceId = MutableLiveData<Long>()
val cardDavRefreshing = MutableLiveData<Boolean>()
val addressBooks = MutableLiveData<List<Collection>>()
val hasAddressBookHomeSets = MutableLiveData<Boolean>()
val subscribedWebcals = MutableLiveData<Set<String>>()
var calendarProvider: ContentProviderClient? = null
var calendarObserver: ContentObserver? = null
val calDavServiceId = MutableLiveData<Long>()
val calDavRefreshing = MutableLiveData<Boolean>()
val calendars = MutableLiveData<List<Collection>>()
val hasCalendarHomeSets = MutableLiveData<Boolean>()
val database = AppDatabase.getInstance(context)
val services: LiveData<List<Service>> = Transformations.switchMap(accountName) { name ->
database.serviceDao().observeByAccount(name)
}
val webcals = MutableLiveData<List<Collection>>()
private fun transformServiceToHasHomesets(liveServiceId: LiveData<Long>): LiveData<Boolean> =
Transformations.switchMap(liveServiceId) { serviceId ->
serviceId?.let {
Transformations.map(database.homeSetDao().observeByService(serviceId)) {
it.isNotEmpty()
}
}
}
val requiredPermissions = MutableLiveData<List<String>>()
private fun transformServiceToCollections(liveServiceId: LiveData<Long>, type: String): LiveData<List<Collection>> =
Transformations.switchMap(liveServiceId) { serviceId ->
serviceId?.let {
database.collectionDao().observeByServiceAndType(serviceId, type)
}
}
val cardDavServiceId: LiveData<Long> = Transformations.map(services) { services ->
services.firstOrNull { it.type == Service.TYPE_CARDDAV }?.id
}
val hasAddressBookHomeSets: LiveData<Boolean> = transformServiceToHasHomesets(cardDavServiceId)
val addressBooks = transformServiceToCollections(cardDavServiceId, Collection.TYPE_ADDRESSBOOK)
val calDavServiceId: LiveData<Long> = Transformations.map(services) { services ->
services.firstOrNull { it.type == Service.TYPE_CALDAV }?.id
}
val hasCalendarHomeSets = transformServiceToHasHomesets(calDavServiceId)
val calendars = transformServiceToCollections(calDavServiceId, Collection.TYPE_CALENDAR)
val webcals = WebcalSource(
subscribedWebcals,
transformServiceToCollections(calDavServiceId, Collection.TYPE_WEBCAL)
)
val requiredPermissions = PermissionCalculator(context, addressBooks, calDavServiceId, webcals)
var syncStatusListener: Any? = null
val cardDavRefreshing = MutableLiveData<Boolean>()
val calDavRefreshing = MutableLiveData<Boolean>()
@Volatile
private var davService: DavService.InfoBinder? = null
@ -555,11 +546,42 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
private val executor = Executors.newSingleThreadExecutor()
@MainThread
fun initialize(account: Account) {
if (initialized)
if (accountName.value != null)
return
this.account = account
accountName.value = account.name
thread {
initializeAsync()
}
}
@WorkerThread
private fun initializeAsync() {
if (checkSelfPermission(context, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED) {
calendarProvider = context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)
object: ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
calendarProvider?.query(CalendarContract.Calendars.CONTENT_URI, arrayOf(CalendarContract.Calendars.NAME),
"${CalendarContract.Calendars.NAME} LIKE 'http%'", null, null)?.use { cursor ->
val result = mutableSetOf<String>()
while (cursor.moveToNext())
result += cursor.getString(0)
subscribedWebcals.postValue(result)
}
}
}.let { observer ->
calendarObserver = observer
context.contentResolver.registerContentObserver(CalendarContract.Calendars.CONTENT_URI, false, observer)
observer.onChange(true)
}
}
// observe Account sync status
syncStatusListener = ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, this)
@ -578,72 +600,19 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
if (context.bindService(Intent(context, DavService::class.java), svcConn, Context.BIND_AUTO_CREATE))
davServiceConn = svcConn
reload()
updateRefreshing()
}
override fun onCleared() {
calendarObserver?.let { context.contentResolver.unregisterContentObserver(it) }
calendarProvider?.closeCompat()
davServiceConn?.let { context.unbindService(it) }
ContentResolver.removeStatusChangeListener(syncStatusListener)
}
fun reload() {
executor.submit {
val neededPermissions = mutableListOf<String>()
val db = AppDatabase.getInstance(context)
val collectionDao = db.collectionDao()
for (service in db.serviceDao().getByAccount(account.name)) {
when (service.type) {
Service.TYPE_CARDDAV -> {
cardDavServiceId.postValue(service.id)
val collections = collectionDao.getByService(service.id)
if (collections.isNotEmpty())
neededPermissions.addAll(arrayOf(
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
))
addressBooks.postValue(collections)
}
Service.TYPE_CALDAV -> {
calDavServiceId.postValue(service.id)
val collections = collectionDao.getByService(service.id)
if (collections.isNotEmpty()) {
neededPermissions.addAll(arrayOf(
Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR
))
if (LocalTaskList.tasksProviderAvailable(context)) {
neededPermissions.addAll(arrayOf(
TaskProvider.PERMISSION_READ_TASKS,
TaskProvider.PERMISSION_WRITE_TASKS
))
}
calendars.postValue(collections.filter { it.type == Collection.TYPE_CALENDAR })
val webcalCollections = collections.filter { it.type == Collection.TYPE_WEBCAL }
if (webcalCollections.isNotEmpty() && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_GRANTED)
context.contentResolver.acquireContentProviderClient(CalendarContract.AUTHORITY)?.let { provider ->
try {
for (info in webcalCollections) {
provider.query(CalendarContract.Calendars.CONTENT_URI, null,
"${CalendarContract.Calendars.NAME}=?", arrayOf(info.source), null)?.use { cursor ->
if (cursor.moveToNext())
info.sync = true
}
}
} finally {
provider.closeCompat()
}
}
webcals.postValue(webcalCollections)
}
}
}
}
requiredPermissions.postValue(neededPermissions)
}
fun onPermissionsUpdated() {
// TODO
}
override fun onStatusChanged(which: Int) {
@ -655,11 +624,6 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
updateRefreshing()
}
override fun onDavRefreshFinished(id: Long) {
if (id == cardDavServiceId.value || id == calDavServiceId.value)
reload()
}
private fun updateRefreshing() {
// address books authority for current account syncing?
var contactsSyncActive = ContentResolver.isSyncActive(account, context.getString(R.string.address_books_authority))
@ -671,7 +635,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
if (account == addressBook.mainAccount)
if (ContentResolver.isSyncActive(addrBookAccount, ContactsContract.AUTHORITY))
contactsSyncActive = true
} catch(e: Exception) {
} catch (e: Exception) {
}
}
val svcCardDavRefreshing = cardDavServiceId.value?.let { id -> davService?.isRefreshing(id) } ?: false
@ -683,22 +647,131 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop
calDavRefreshing.postValue(calendarSyncActive || tasksSyncActive || svcCalDavRefreshing)
}
fun updateCollectionSelected(info: Collection, selected: Boolean, @UiThread onSuccess: () -> Unit) {
fun updateCollectionSelected(info: Collection, selected: Boolean) {
executor.submit {
info.sync = true
info.sync = selected
AppDatabase.getInstance(context).collectionDao().update(info)
Handler(Looper.getMainLooper()).post(onSuccess)
}
}
fun updateCollectionReadOnly(info: Collection, readOnly: Boolean, @UiThread onSuccess: () -> Unit) {
fun updateCollectionReadOnly(info: Collection, readOnly: Boolean) {
executor.submit {
info.forceReadOnly = true
info.forceReadOnly = readOnly
AppDatabase.getInstance(context).collectionDao().update(info)
Handler(Looper.getMainLooper()).post(onSuccess)
}
}
}
class PermissionCalculator(
context: Context,
addressBooks: LiveData<List<Collection>>,
calDavServiceId: LiveData<Long>,
webcals: LiveData<List<Collection>>
): MediatorLiveData<Set<String>>() {
companion object {
val contactPermissions = arrayOf(
Manifest.permission.READ_CONTACTS,
Manifest.permission.WRITE_CONTACTS
)
val calendarPermissions = arrayOf(
Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR
)
val taskPermissions = arrayOf(
TaskProvider.PERMISSION_READ_TASKS,
TaskProvider.PERMISSION_WRITE_TASKS
)
}
private val permissions = mutableSetOf<String>()
init {
addSource(addressBooks) { collections ->
val oldPermissions = HashSet(permissions)
if (collections.any { it.sync })
permissions.addAll(contactPermissions)
else
permissions.removeAll(contactPermissions)
if (permissions != oldPermissions)
value = permissions
}
addSource(calDavServiceId) { serviceId ->
val oldPermissions = HashSet(permissions)
permissions.removeAll(calendarPermissions)
if (serviceId != null) {
permissions.addAll(calendarPermissions)
if (LocalTaskList.tasksProviderAvailable(context))
permissions.addAll(taskPermissions)
}
if (permissions != oldPermissions)
value = permissions
}
addSource(webcals) { collections ->
val oldPermissions = HashSet(permissions)
if (collections.isNotEmpty())
// we need calendar permissions to see which Webcals are subscribed by ICSx5
permissions.addAll(calendarPermissions)
if (permissions != oldPermissions)
value = permissions
}
}
}
class WebcalSource(
subscribedWebcals: LiveData<Set<String>>,
webcals: LiveData<List<Collection>>
): MediatorLiveData<List<Collection>>() {
private var activeSubscriptions: Set<String>? = null
private var unprocessedWebcals: List<Collection>? = null
init {
addSource(webcals) {
unprocessedWebcals = it
processWebcals()
}
addSource(subscribedWebcals) {
activeSubscriptions = it
processWebcals()
}
}
private fun processWebcals() {
val result = unprocessedWebcals?.map { webcal ->
webcal.sync = activeSubscriptions?.contains(webcal.source.toString()) ?: false
webcal
} ?: return
value = result
}
// TODO
/*fun unsubscribe(collection: Collection) {
thread {
// delete subscription
if (model.provider?.delete(CalendarContract.Calendars.CONTENT_URI, "${CalendarContract.Calendars.NAME}=?", arrayOf(collection.source)) == 1) {
// update LiveData
value?.let { webcals ->
for (webcal in webcals)
if (webcal.source == collection.source)
webcal.sync = false
postValue(webcals)
}
}
}
}*/
}
}