Loading app/src/main/AndroidManifest.xml +8 −1 Original line number Diff line number Diff line Loading @@ -64,7 +64,11 @@ android:name=".ui.AppSettingsActivity" android:label="@string/app_settings" android:parentActivityName=".ui.AccountsActivity" android:exported="true"/> android:exported="true"> <intent-filter> <action android:name="android.intent.action.APPLICATION_PREFERENCES"/> </intent-filter> </activity> <activity android:name=".ui.setup.LoginActivity" Loading @@ -90,6 +94,9 @@ android:parentActivityName=".ui.AppSettingsActivity" android:exported="true" android:label="@string/debug_info_title"> <intent-filter> <action android:name="android.intent.action.BUG_REPORT"/> </intent-filter> </activity> <provider android:name="androidx.core.content.FileProvider" Loading app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.kt +92 −100 Original line number Diff line number Diff line Loading @@ -21,7 +21,10 @@ import android.provider.CalendarContract import android.provider.ContactsContract import android.provider.Settings import android.view.* import android.widget.* import android.widget.CheckBox import android.widget.ImageView import android.widget.PopupMenu import android.widget.TextView import androidx.annotation.MainThread import androidx.annotation.WorkerThread import androidx.appcompat.app.AlertDialog Loading @@ -32,7 +35,6 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat.checkSelfPermission import androidx.databinding.DataBindingUtil import androidx.lifecycle.* import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import at.bitfire.davdroid.* Loading @@ -46,7 +48,6 @@ 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 Loading @@ -56,12 +57,19 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop companion object { const val EXTRA_ACCOUNT = "account" const val REQUEST_CODE_RELOAD = 0 const val REQUEST_CODE_PERMISSIONS_UPDATED = 0 } private lateinit var model: Model private lateinit var binding: ActivityAccountBinding private val openAppSettings = { _: View -> val appSettings = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)) if (appSettings.resolveActivity(packageManager) != null) startActivityForResult(appSettings, REQUEST_CODE_PERMISSIONS_UPDATED) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Loading @@ -79,6 +87,19 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop val icMenu = AppCompatResources.getDrawable(this, R.drawable.ic_menu_light) // permissions model.askForContactsPermissions.observe(this, Observer { needsContactPermissions -> if (needsContactPermissions) ActivityCompat.requestPermissions(this, ContactsPermissionsCalculator.permissions, 0) }) binding.contactPermissions.setOnClickListener(openAppSettings) model.askForCalendarPermissions.observe(this, Observer { permissions -> if (permissions.isNotEmpty()) ActivityCompat.requestPermissions(this, permissions.toTypedArray(), 0) }) binding.calendarPermissions.setOnClickListener(openAppSettings) // CardDAV binding.carddavMenu.apply { overflowIcon = icMenu Loading Loading @@ -119,38 +140,15 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop layoutManager = LinearLayoutManager(this@AccountActivity) adapter = WebcalAdapter(this@AccountActivity, model) } 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) }) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { if (grantResults.any { it == PackageManager.PERMISSION_GRANTED }) // we've got additional permissions; load everything again // (especially Webcal subscriptions, whose status could not be determined without calendar permission) reload() else if (grantResults.any { it == PackageManager.PERMISSION_DENIED }) { if (permissions.map { ActivityCompat.shouldShowRequestPermissionRationale(this, it) }.any()) Snackbar .make(binding.root, R.string.account_missing_permissions, Snackbar.LENGTH_LONG) .setAction(R.string.account_missing_permissions_fix) { Toast.makeText(this, R.string.account_missing_permissions_explanation, Toast.LENGTH_LONG) .show() val settingsIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)) startActivityForResult(settingsIntent, REQUEST_CODE_RELOAD) } .show() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) = model.onPermissionsUpdated() override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_RELOAD) reload() if (requestCode == REQUEST_CODE_PERMISSIONS_UPDATED) model.onPermissionsUpdated() } override fun onCreateOptionsMenu(menu: Menu): Boolean { Loading Loading @@ -258,10 +256,6 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop true } fun reload() { // TODO handle new permissions } /* LIST ADAPTERS */ Loading Loading @@ -434,14 +428,14 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop val installIntent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=at.bitfire.icsdroid")) if (activity.packageManager.resolveActivity(installIntent, 0) != null) snack.setAction(R.string.account_install_icsx5) { activity.startActivityForResult(installIntent, REQUEST_CODE_RELOAD) activity.startActivityForResult(installIntent, REQUEST_CODE_PERMISSIONS_UPDATED) } snack.show() } } else { // unsubscribe from Webcal feed // TODO model.webcals.unsubscribe(info) model.unsubscribeWebcal(info) } } Loading Loading @@ -523,6 +517,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop val hasAddressBookHomeSets: LiveData<Boolean> = transformServiceToHasHomesets(cardDavServiceId) val addressBooks = transformServiceToCollections(cardDavServiceId, Collection.TYPE_ADDRESSBOOK) val askForContactsPermissions = ContactsPermissionsCalculator(context, addressBooks) val calDavServiceId: LiveData<Long> = Transformations.map(services) { services -> services.firstOrNull { it.type == Service.TYPE_CALDAV }?.id Loading @@ -533,8 +528,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop subscribedWebcals, transformServiceToCollections(calDavServiceId, Collection.TYPE_WEBCAL) ) val requiredPermissions = PermissionCalculator(context, addressBooks, calDavServiceId, webcals) val askForCalendarPermissions = CalendarPermissionsCalculator(context, calDavServiceId) var syncStatusListener: Any? = null val cardDavRefreshing = MutableLiveData<Boolean>() Loading Loading @@ -612,7 +606,8 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop } fun onPermissionsUpdated() { // TODO askForContactsPermissions.recalculate() askForCalendarPermissions.recalculate() } override fun onStatusChanged(which: Int) { Loading Loading @@ -647,6 +642,10 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop calDavRefreshing.postValue(calendarSyncActive || tasksSyncActive || svcCalDavRefreshing) } fun unsubscribeWebcal(collection: Collection) = calendarProvider?.delete(CalendarContract.Calendars.CONTENT_URI, "${CalendarContract.Calendars.NAME}=?", arrayOf(collection.source)) fun updateCollectionSelected(info: Collection, selected: Boolean) { executor.submit { info.sync = selected Loading @@ -663,18 +662,48 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop } class PermissionCalculator( context: Context, addressBooks: LiveData<List<Collection>>, calDavServiceId: LiveData<Long>, webcals: LiveData<List<Collection>> ): MediatorLiveData<Set<String>>() { class ContactsPermissionsCalculator( val context: Context, val addressBooks: LiveData<List<Collection>> ): MediatorLiveData<Boolean>() { companion object { val contactPermissions = arrayOf( val permissions = arrayOf( Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS ) } init { addSource(addressBooks) { recalculate(it) } } fun recalculate() = addressBooks.value?.let { recalculate(it) } private fun recalculate(addressbooks: List<Collection>) { // permissions required? val required = addressbooks.any { it.sync } val ask = if (required) // if permissions required: any (not yet) granted permission? permissions.any { ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED } else false if (value != ask) value = ask } } class CalendarPermissionsCalculator( val context: Context, val serviceId: LiveData<Long> ): MediatorLiveData<List<String>>() { companion object { val calendarPermissions = arrayOf( Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR Loading @@ -685,49 +714,28 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop ) } 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(serviceId) { recalculate() } } addSource(calDavServiceId) { serviceId -> val oldPermissions = HashSet(permissions) fun recalculate() { val permissions = mutableListOf<String>() permissions.removeAll(calendarPermissions) if (serviceId != null) { // As soon as there is a CalDAV service, we need calendar (and task) permissions permissions.addAll(calendarPermissions) if (LocalTaskList.tasksProviderAvailable(context)) permissions.addAll(taskPermissions) } if (permissions != oldPermissions) value = permissions } addSource(webcals) { collections -> val oldPermissions = HashSet(permissions) // only ask for permissions which are not granted yet val ask = permissions.filter { ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED } if (collections.isNotEmpty()) // we need calendar permissions to see which Webcals are subscribed by ICSx5 permissions.addAll(calendarPermissions) if (permissions != oldPermissions) value = permissions } } if (value != ask) value = ask } } class WebcalSource( subscribedWebcals: LiveData<Set<String>>, Loading Loading @@ -756,22 +764,6 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop 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) } } } }*/ } } app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.kt +1 −4 Original line number Diff line number Diff line Loading @@ -63,10 +63,7 @@ class DeleteCollectionFragment: DialogFragment() { binding.controls.visibility = View.GONE model.deleteCollection().observe(this, Observer { exception -> if (exception == null) // reload collection list (activity as? AccountActivity)?.reload() else if (exception != null) requireFragmentManager().beginTransaction() .add(ExceptionInfoFragment.newInstance(exception, model.account), null) .commit() Loading app/src/main/res/layout/activity_account.xml +34 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,23 @@ android:indeterminate="true" android:visibility="@{safeUnbox(model.cardDavRefreshing) ? View.VISIBLE : View.GONE}"/> <androidx.cardview.widget.CardView android:id="@+id/contact_permissions" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" app:cardElevation="8dp" app:contentPadding="8dp" android:visibility="@{safeUnbox(model.askForContactsPermissions) ? View.VISIBLE : View.GONE}" android:focusable="true"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_error_dark" android:drawablePadding="8dp" android:text="@string/account_contact_permissions_required"/> </androidx.cardview.widget.CardView> <androidx.recyclerview.widget.RecyclerView android:id="@+id/address_books" android:layout_width="match_parent" Loading Loading @@ -95,6 +112,23 @@ android:indeterminate="true" android:visibility="@{safeUnbox(model.calDavRefreshing) ? View.VISIBLE : View.GONE}"/> <androidx.cardview.widget.CardView android:id="@+id/calendar_permissions" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" app:cardElevation="8dp" app:contentPadding="8dp" android:visibility="@{model.askForCalendarPermissions.isEmpty() ? View.GONE : View.VISIBLE}" android:focusable="true"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_error_dark" android:drawablePadding="8dp" android:text="@string/account_calendar_permissions_required"/> </androidx.cardview.widget.CardView> <androidx.recyclerview.widget.RecyclerView android:id="@+id/calendars" android:layout_width="match_parent" Loading app/src/main/res/values/strings.xml +2 −3 Original line number Diff line number Diff line Loading @@ -116,9 +116,8 @@ <string name="app_settings_reset_hints_success">All hints will be shown again</string> <!-- AccountActivity --> <string name="account_missing_permissions">Permissions potentially missing</string> <string name="account_missing_permissions_fix">Fix</string> <string name="account_missing_permissions_explanation">Required permissions: contacts, calendars, (tasks)</string> <string name="account_contact_permissions_required">Contact permissions are required to synchronize address books.</string> <string name="account_calendar_permissions_required">Calendar/task permissions are required to synchronize with the CalDAV service.</string> <string name="account_synchronize_now">Synchronize now</string> <string name="account_synchronizing_now">Synchronizing now</string> <string name="account_settings">Account settings</string> Loading Loading
app/src/main/AndroidManifest.xml +8 −1 Original line number Diff line number Diff line Loading @@ -64,7 +64,11 @@ android:name=".ui.AppSettingsActivity" android:label="@string/app_settings" android:parentActivityName=".ui.AccountsActivity" android:exported="true"/> android:exported="true"> <intent-filter> <action android:name="android.intent.action.APPLICATION_PREFERENCES"/> </intent-filter> </activity> <activity android:name=".ui.setup.LoginActivity" Loading @@ -90,6 +94,9 @@ android:parentActivityName=".ui.AppSettingsActivity" android:exported="true" android:label="@string/debug_info_title"> <intent-filter> <action android:name="android.intent.action.BUG_REPORT"/> </intent-filter> </activity> <provider android:name="androidx.core.content.FileProvider" Loading
app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.kt +92 −100 Original line number Diff line number Diff line Loading @@ -21,7 +21,10 @@ import android.provider.CalendarContract import android.provider.ContactsContract import android.provider.Settings import android.view.* import android.widget.* import android.widget.CheckBox import android.widget.ImageView import android.widget.PopupMenu import android.widget.TextView import androidx.annotation.MainThread import androidx.annotation.WorkerThread import androidx.appcompat.app.AlertDialog Loading @@ -32,7 +35,6 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat.checkSelfPermission import androidx.databinding.DataBindingUtil import androidx.lifecycle.* import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import at.bitfire.davdroid.* Loading @@ -46,7 +48,6 @@ 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 Loading @@ -56,12 +57,19 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop companion object { const val EXTRA_ACCOUNT = "account" const val REQUEST_CODE_RELOAD = 0 const val REQUEST_CODE_PERMISSIONS_UPDATED = 0 } private lateinit var model: Model private lateinit var binding: ActivityAccountBinding private val openAppSettings = { _: View -> val appSettings = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)) if (appSettings.resolveActivity(packageManager) != null) startActivityForResult(appSettings, REQUEST_CODE_PERMISSIONS_UPDATED) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Loading @@ -79,6 +87,19 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop val icMenu = AppCompatResources.getDrawable(this, R.drawable.ic_menu_light) // permissions model.askForContactsPermissions.observe(this, Observer { needsContactPermissions -> if (needsContactPermissions) ActivityCompat.requestPermissions(this, ContactsPermissionsCalculator.permissions, 0) }) binding.contactPermissions.setOnClickListener(openAppSettings) model.askForCalendarPermissions.observe(this, Observer { permissions -> if (permissions.isNotEmpty()) ActivityCompat.requestPermissions(this, permissions.toTypedArray(), 0) }) binding.calendarPermissions.setOnClickListener(openAppSettings) // CardDAV binding.carddavMenu.apply { overflowIcon = icMenu Loading Loading @@ -119,38 +140,15 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop layoutManager = LinearLayoutManager(this@AccountActivity) adapter = WebcalAdapter(this@AccountActivity, model) } 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) }) } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { if (grantResults.any { it == PackageManager.PERMISSION_GRANTED }) // we've got additional permissions; load everything again // (especially Webcal subscriptions, whose status could not be determined without calendar permission) reload() else if (grantResults.any { it == PackageManager.PERMISSION_DENIED }) { if (permissions.map { ActivityCompat.shouldShowRequestPermissionRationale(this, it) }.any()) Snackbar .make(binding.root, R.string.account_missing_permissions, Snackbar.LENGTH_LONG) .setAction(R.string.account_missing_permissions_fix) { Toast.makeText(this, R.string.account_missing_permissions_explanation, Toast.LENGTH_LONG) .show() val settingsIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)) startActivityForResult(settingsIntent, REQUEST_CODE_RELOAD) } .show() } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) = model.onPermissionsUpdated() override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_RELOAD) reload() if (requestCode == REQUEST_CODE_PERMISSIONS_UPDATED) model.onPermissionsUpdated() } override fun onCreateOptionsMenu(menu: Menu): Boolean { Loading Loading @@ -258,10 +256,6 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop true } fun reload() { // TODO handle new permissions } /* LIST ADAPTERS */ Loading Loading @@ -434,14 +428,14 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop val installIntent = Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=at.bitfire.icsdroid")) if (activity.packageManager.resolveActivity(installIntent, 0) != null) snack.setAction(R.string.account_install_icsx5) { activity.startActivityForResult(installIntent, REQUEST_CODE_RELOAD) activity.startActivityForResult(installIntent, REQUEST_CODE_PERMISSIONS_UPDATED) } snack.show() } } else { // unsubscribe from Webcal feed // TODO model.webcals.unsubscribe(info) model.unsubscribeWebcal(info) } } Loading Loading @@ -523,6 +517,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop val hasAddressBookHomeSets: LiveData<Boolean> = transformServiceToHasHomesets(cardDavServiceId) val addressBooks = transformServiceToCollections(cardDavServiceId, Collection.TYPE_ADDRESSBOOK) val askForContactsPermissions = ContactsPermissionsCalculator(context, addressBooks) val calDavServiceId: LiveData<Long> = Transformations.map(services) { services -> services.firstOrNull { it.type == Service.TYPE_CALDAV }?.id Loading @@ -533,8 +528,7 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop subscribedWebcals, transformServiceToCollections(calDavServiceId, Collection.TYPE_WEBCAL) ) val requiredPermissions = PermissionCalculator(context, addressBooks, calDavServiceId, webcals) val askForCalendarPermissions = CalendarPermissionsCalculator(context, calDavServiceId) var syncStatusListener: Any? = null val cardDavRefreshing = MutableLiveData<Boolean>() Loading Loading @@ -612,7 +606,8 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop } fun onPermissionsUpdated() { // TODO askForContactsPermissions.recalculate() askForCalendarPermissions.recalculate() } override fun onStatusChanged(which: Int) { Loading Loading @@ -647,6 +642,10 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop calDavRefreshing.postValue(calendarSyncActive || tasksSyncActive || svcCalDavRefreshing) } fun unsubscribeWebcal(collection: Collection) = calendarProvider?.delete(CalendarContract.Calendars.CONTENT_URI, "${CalendarContract.Calendars.NAME}=?", arrayOf(collection.source)) fun updateCollectionSelected(info: Collection, selected: Boolean) { executor.submit { info.sync = selected Loading @@ -663,18 +662,48 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop } class PermissionCalculator( context: Context, addressBooks: LiveData<List<Collection>>, calDavServiceId: LiveData<Long>, webcals: LiveData<List<Collection>> ): MediatorLiveData<Set<String>>() { class ContactsPermissionsCalculator( val context: Context, val addressBooks: LiveData<List<Collection>> ): MediatorLiveData<Boolean>() { companion object { val contactPermissions = arrayOf( val permissions = arrayOf( Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS ) } init { addSource(addressBooks) { recalculate(it) } } fun recalculate() = addressBooks.value?.let { recalculate(it) } private fun recalculate(addressbooks: List<Collection>) { // permissions required? val required = addressbooks.any { it.sync } val ask = if (required) // if permissions required: any (not yet) granted permission? permissions.any { ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED } else false if (value != ask) value = ask } } class CalendarPermissionsCalculator( val context: Context, val serviceId: LiveData<Long> ): MediatorLiveData<List<String>>() { companion object { val calendarPermissions = arrayOf( Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR Loading @@ -685,49 +714,28 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop ) } 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(serviceId) { recalculate() } } addSource(calDavServiceId) { serviceId -> val oldPermissions = HashSet(permissions) fun recalculate() { val permissions = mutableListOf<String>() permissions.removeAll(calendarPermissions) if (serviceId != null) { // As soon as there is a CalDAV service, we need calendar (and task) permissions permissions.addAll(calendarPermissions) if (LocalTaskList.tasksProviderAvailable(context)) permissions.addAll(taskPermissions) } if (permissions != oldPermissions) value = permissions } addSource(webcals) { collections -> val oldPermissions = HashSet(permissions) // only ask for permissions which are not granted yet val ask = permissions.filter { ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED } if (collections.isNotEmpty()) // we need calendar permissions to see which Webcals are subscribed by ICSx5 permissions.addAll(calendarPermissions) if (permissions != oldPermissions) value = permissions } } if (value != ask) value = ask } } class WebcalSource( subscribedWebcals: LiveData<Set<String>>, Loading Loading @@ -756,22 +764,6 @@ class AccountActivity: AppCompatActivity(), Toolbar.OnMenuItemClickListener, Pop 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) } } } }*/ } }
app/src/main/java/at/bitfire/davdroid/ui/DeleteCollectionFragment.kt +1 −4 Original line number Diff line number Diff line Loading @@ -63,10 +63,7 @@ class DeleteCollectionFragment: DialogFragment() { binding.controls.visibility = View.GONE model.deleteCollection().observe(this, Observer { exception -> if (exception == null) // reload collection list (activity as? AccountActivity)?.reload() else if (exception != null) requireFragmentManager().beginTransaction() .add(ExceptionInfoFragment.newInstance(exception, model.account), null) .commit() Loading
app/src/main/res/layout/activity_account.xml +34 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,23 @@ android:indeterminate="true" android:visibility="@{safeUnbox(model.cardDavRefreshing) ? View.VISIBLE : View.GONE}"/> <androidx.cardview.widget.CardView android:id="@+id/contact_permissions" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" app:cardElevation="8dp" app:contentPadding="8dp" android:visibility="@{safeUnbox(model.askForContactsPermissions) ? View.VISIBLE : View.GONE}" android:focusable="true"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_error_dark" android:drawablePadding="8dp" android:text="@string/account_contact_permissions_required"/> </androidx.cardview.widget.CardView> <androidx.recyclerview.widget.RecyclerView android:id="@+id/address_books" android:layout_width="match_parent" Loading Loading @@ -95,6 +112,23 @@ android:indeterminate="true" android:visibility="@{safeUnbox(model.calDavRefreshing) ? View.VISIBLE : View.GONE}"/> <androidx.cardview.widget.CardView android:id="@+id/calendar_permissions" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" app:cardElevation="8dp" app:contentPadding="8dp" android:visibility="@{model.askForCalendarPermissions.isEmpty() ? View.GONE : View.VISIBLE}" android:focusable="true"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_error_dark" android:drawablePadding="8dp" android:text="@string/account_calendar_permissions_required"/> </androidx.cardview.widget.CardView> <androidx.recyclerview.widget.RecyclerView android:id="@+id/calendars" android:layout_width="match_parent" Loading
app/src/main/res/values/strings.xml +2 −3 Original line number Diff line number Diff line Loading @@ -116,9 +116,8 @@ <string name="app_settings_reset_hints_success">All hints will be shown again</string> <!-- AccountActivity --> <string name="account_missing_permissions">Permissions potentially missing</string> <string name="account_missing_permissions_fix">Fix</string> <string name="account_missing_permissions_explanation">Required permissions: contacts, calendars, (tasks)</string> <string name="account_contact_permissions_required">Contact permissions are required to synchronize address books.</string> <string name="account_calendar_permissions_required">Calendar/task permissions are required to synchronize with the CalDAV service.</string> <string name="account_synchronize_now">Synchronize now</string> <string name="account_synchronizing_now">Synchronizing now</string> <string name="account_settings">Account settings</string> Loading