From 599c905610d7012a8bb68bb3c5d2aa478f5ec843 Mon Sep 17 00:00:00 2001 From: Arnau Mora Date: Mon, 16 Oct 2023 14:30:26 +0100 Subject: [PATCH 1/9] Replace deprecated menu overrides (#443) * Migrated to menu provider Signed-off-by: Arnau Mora * Removed override Signed-off-by: Arnau Mora * Cleanup Signed-off-by: Arnau Mora * Fixed menus Signed-off-by: Arnau Mora * Minor changes --------- Signed-off-by: Arnau Mora Co-authored-by: Ricki Hirner --- .../at/bitfire/davdroid/ui/AboutActivity.kt | 21 +++-- .../davdroid/ui/AccountListFragment.kt | 33 +++++--- .../bitfire/davdroid/ui/AccountsActivity.kt | 2 +- .../davdroid/ui/account/AccountActivity.kt | 34 ++++++-- .../ui/account/AddressBooksFragment.kt | 46 +++++++---- .../davdroid/ui/account/CalendarsFragment.kt | 47 +++++++---- .../ui/account/CollectionsFragment.kt | 81 ++++++++++--------- .../davdroid/ui/account/WebcalFragment.kt | 38 +++++++-- .../ui/webdav/AddWebdavMountActivity.kt | 25 ++++-- .../ui/webdav/WebdavMountsActivity.kt | 22 +++-- app/src/main/res/menu/activity_about.xml | 4 +- app/src/main/res/menu/activity_account.xml | 3 - app/src/main/res/menu/activity_accounts.xml | 1 - .../res/menu/activity_add_webdav_mount.xml | 3 +- .../main/res/menu/activity_webdav_mounts.xml | 3 +- 15 files changed, 246 insertions(+), 117 deletions(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/AboutActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/AboutActivity.kt index a2079164a..92a6f3cac 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/AboutActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/AboutActivity.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.core.text.HtmlCompat +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentPagerAdapter @@ -81,14 +82,24 @@ class AboutActivity: AppCompatActivity() { binding.viewpager.adapter = TabsAdapter(supportFragmentManager) binding.tabs.setupWithViewPager(binding.viewpager, false) - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.activity_about, menu) - return true + addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.activity_about, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem) = + when (menuItem.itemId) { + R.id.show_website -> { + showWebsite() + true + } + else -> false + } + }) } - fun showWebsite(item: MenuItem) { + fun showWebsite() { UiUtils.launchUri(this, App.homepageUrl(this)) } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountListFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountListFragment.kt index 13d6288f2..462d5e006 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountListFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountListFragment.kt @@ -20,10 +20,13 @@ import android.provider.Settings import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.annotation.AnyThread import androidx.core.content.ContextCompat +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.AndroidViewModel @@ -58,8 +61,6 @@ class AccountListFragment: Fragment() { private var syncStatusSnackbar: Snackbar? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - setHasOptionsMenu(true) - _binding = AccountListBinding.inflate(inflater, container, false) return binding.root } @@ -132,16 +133,28 @@ class AccountListFragment: Fragment() { accountAdapter.submitList(accounts) requireActivity().invalidateOptionsMenu() } - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = - inflater.inflate(R.menu.activity_accounts, menu) + requireActivity().addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.activity_accounts, menu) + } - override fun onPrepareOptionsMenu(menu: Menu) { - // Show "Sync all" only when there is at least one account - model.accounts.value?.let { accounts -> - menu.findItem(R.id.syncAll).setVisible(accounts.isNotEmpty()) - } + override fun onMenuItemSelected(menuItem: MenuItem) = + when (menuItem.itemId) { + R.id.syncAll -> { + (activity as AccountsActivity).syncAllAccounts() + true + } + else -> false + } + + override fun onPrepareMenu(menu: Menu) { + // Show "Sync all" only when there is at least one account + model.accounts.value?.let { accounts -> + menu.findItem(R.id.syncAll).setVisible(accounts.isNotEmpty()) + } + } + }) } override fun onResume() { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt index 808db922d..5bf41a3c4 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt @@ -109,7 +109,7 @@ class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSele private fun allAccounts() = AccountManager.get(this).getAccountsByType(getString(R.string.account_type)) - fun syncAllAccounts(item: MenuItem? = null) { + fun syncAllAccounts() { if (Build.VERSION.SDK_INT >= 25) getSystemService()?.reportShortcutUsed(UiUtils.SHORTCUT_SYNC_ALL) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt index 8154948e7..59adc5c56 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AccountActivity.kt @@ -13,10 +13,12 @@ import android.os.Bundle import android.os.Handler import android.os.Looper import android.view.Menu +import android.view.MenuInflater import android.view.MenuItem import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.TooltipCompat +import androidx.core.view.MenuProvider import androidx.fragment.app.FragmentActivity import androidx.lifecycle.* import androidx.viewpager2.adapter.FragmentStateAdapter @@ -105,27 +107,45 @@ class AccountActivity: AppCompatActivity() { SyncWorker.enqueueAllAuthorities(this, model.account) } } - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.activity_account, menu) - return true + addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.activity_account, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem) = + when (menuItem.itemId) { + R.id.settings -> { + openAccountSettings() + true + } + R.id.rename_account -> { + renameAccount() + true + } + R.id.delete_account -> { + deleteAccountDialog() + true + } + else -> false + } + }) } // menu actions - fun openAccountSettings(menuItem: MenuItem) { + fun openAccountSettings() { val intent = Intent(this, SettingsActivity::class.java) intent.putExtra(SettingsActivity.EXTRA_ACCOUNT, model.account) startActivity(intent, null) } - fun renameAccount(menuItem: MenuItem) { + fun renameAccount() { RenameAccountFragment.newInstance(model.account).show(supportFragmentManager, null) } - fun deleteAccount(menuItem: MenuItem) { + fun deleteAccountDialog() { MaterialAlertDialogBuilder(this) .setIcon(R.drawable.ic_error) .setTitle(R.string.account_delete_confirmation_title) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AddressBooksFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AddressBooksFragment.kt index 2a30b1304..67cda108b 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AddressBooksFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/AddressBooksFragment.kt @@ -5,7 +5,10 @@ package at.bitfire.davdroid.ui.account import android.content.Intent +import android.os.Bundle import android.view.* +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.FragmentManager import at.bitfire.davdroid.util.PermissionUtils import at.bitfire.davdroid.R @@ -22,26 +25,39 @@ class AddressBooksFragment: CollectionsFragment() { override val noCollectionsStringId = R.string.account_no_address_books - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = - inflater.inflate(R.menu.carddav_actions, menu) + private val menuProvider = object : CollectionsMenuProvider() { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.carddav_actions, menu) + } - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.create_address_book).isVisible = model.hasWriteableCollections.value ?: false - super.onPrepareOptionsMenu(menu) - } + override fun onPrepareMenu(menu: Menu) { + super.onPrepareMenu(menu) + menu.findItem(R.id.create_address_book).isVisible = model.hasWriteableCollections.value ?: false + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (super.onMenuItemSelected(menuItem)) + return true - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (super.onOptionsItemSelected(item)) - return true + if (menuItem.itemId == R.id.create_address_book) { + val intent = Intent(requireActivity(), CreateAddressBookActivity::class.java) + intent.putExtra(CreateAddressBookActivity.EXTRA_ACCOUNT, accountModel.account) + startActivity(intent) + return true + } - if (item.itemId == R.id.create_address_book) { - val intent = Intent(requireActivity(), CreateAddressBookActivity::class.java) - intent.putExtra(CreateAddressBookActivity.EXTRA_ACCOUNT, accountModel.account) - startActivity(intent) - return true + return false } + } + + override fun onResume() { + super.onResume() + requireActivity().addMenuProvider(menuProvider) + } - return false + override fun onPause() { + super.onPause() + requireActivity().removeMenuProvider(menuProvider) } override fun checkPermissions() { diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CalendarsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CalendarsFragment.kt index cbd7916dc..9e58b0bf9 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CalendarsFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CalendarsFragment.kt @@ -5,7 +5,10 @@ package at.bitfire.davdroid.ui.account import android.content.Intent +import android.os.Bundle import android.view.* +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.FragmentManager import at.bitfire.davdroid.Constants import at.bitfire.davdroid.util.PermissionUtils @@ -18,26 +21,40 @@ class CalendarsFragment: CollectionsFragment() { override val noCollectionsStringId = R.string.account_no_calendars - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = - inflater.inflate(R.menu.caldav_actions, menu) + private val menuProvider = object : CollectionsMenuProvider() { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.caldav_actions, menu) + } - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.create_calendar).isVisible = model.hasWriteableCollections.value ?: false - super.onPrepareOptionsMenu(menu) - } + override fun onPrepareMenu(menu: Menu) { + super.onPrepareMenu(menu) + menu.findItem(R.id.create_calendar).isVisible = model.hasWriteableCollections.value ?: false + } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (super.onOptionsItemSelected(item)) - return true + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (super.onMenuItemSelected(menuItem)) { + return true + } - if (item.itemId == R.id.create_calendar) { - val intent = Intent(requireActivity(), CreateCalendarActivity::class.java) - intent.putExtra(CreateCalendarActivity.EXTRA_ACCOUNT, accountModel.account) - startActivity(intent) - return true + if (menuItem.itemId == R.id.create_calendar) { + val intent = Intent(requireActivity(), CreateCalendarActivity::class.java) + intent.putExtra(CreateCalendarActivity.EXTRA_ACCOUNT, accountModel.account) + startActivity(intent) + return true + } + + return false } + } + + override fun onResume() { + super.onResume() + requireActivity().addMenuProvider(menuProvider) + } - return false + override fun onPause() { + super.onPause() + requireActivity().removeMenuProvider(menuProvider) } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CollectionsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CollectionsFragment.kt index e845ebc16..e94621f9c 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CollectionsFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/CollectionsFragment.kt @@ -13,6 +13,9 @@ import android.provider.CalendarContract import android.provider.ContactsContract import android.view.* import android.widget.PopupMenu +import androidx.annotation.CallSuper +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.activityViewModels @@ -66,11 +69,6 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - } - abstract val noCollectionsStringId: Int override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -86,17 +84,17 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - model.isRefreshing.observe(viewLifecycleOwner, Observer { nowRefreshing -> + model.isRefreshing.observe(viewLifecycleOwner) { nowRefreshing -> binding.swipeRefresh.isRefreshing = nowRefreshing - }) - model.hasWriteableCollections.observe(viewLifecycleOwner, Observer { + } + model.hasWriteableCollections.observe(viewLifecycleOwner) { requireActivity().invalidateOptionsMenu() - }) - model.collectionColors.observe(viewLifecycleOwner, Observer { colors: List -> + } + model.collectionColors.observe(viewLifecycleOwner) { colors: List -> val realColors = colors.filterNotNull() if (realColors.isNotEmpty()) binding.swipeRefresh.setColorSchemeColors(*realColors.toIntArray()) - }) + } binding.swipeRefresh.setOnRefreshListener(this) val updateProgress = Observer { @@ -128,11 +126,11 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList val adapter = createAdapter() binding.list.layoutManager = LinearLayoutManager(requireActivity()) binding.list.adapter = adapter - model.collections.observe(viewLifecycleOwner, Observer { data -> + model.collections.observe(viewLifecycleOwner) { data -> lifecycleScope.launch { adapter.submitData(data) } - }) + } adapter.addLoadStateListener { loadStates -> if (loadStates.refresh is LoadState.NotLoading) { if (adapter.itemCount > 0) { @@ -148,31 +146,6 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList binding.noCollections.setText(noCollectionsStringId) } - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.showOnlyPersonal).let { showOnlyPersonal -> - accountModel.showOnlyPersonal.value?.let { value -> - showOnlyPersonal.isChecked = value - } - accountModel.showOnlyPersonalWritable.value?.let { writable -> - showOnlyPersonal.isEnabled = writable - } - } - } - - override fun onOptionsItemSelected(item: MenuItem) = - when (item.itemId) { - R.id.refresh -> { - onRefresh() - true - } - R.id.showOnlyPersonal -> { - accountModel.toggleShowOnlyPersonal() - true - } - else -> - false - } - override fun onRefresh() { model.refresh() } @@ -223,6 +196,38 @@ abstract class CollectionsFragment: Fragment(), SwipeRefreshLayout.OnRefreshList } + abstract inner class CollectionsMenuProvider : MenuProvider { + abstract override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) + + @CallSuper + override fun onPrepareMenu(menu: Menu) { + menu.findItem(R.id.showOnlyPersonal).let { showOnlyPersonal -> + accountModel.showOnlyPersonal.value?.let { value -> + showOnlyPersonal.isChecked = value + } + accountModel.showOnlyPersonalWritable.value?.let { writable -> + showOnlyPersonal.isEnabled = writable + } + } + } + + @CallSuper + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + return when (menuItem.itemId) { + R.id.refresh -> { + onRefresh() + true + } + R.id.showOnlyPersonal -> { + accountModel.toggleShowOnlyPersonal() + true + } + else -> + false + } + } + } + class CollectionPopupListener( private val accountModel: AccountActivity.Model, private val item: Collection, diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/WebcalFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/WebcalFragment.kt index 56a1db3fd..a3501070d 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/WebcalFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/WebcalFragment.kt @@ -60,6 +60,32 @@ class WebcalFragment: CollectionsFragment() { } } + private val menuProvider = object : CollectionsMenuProvider() { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.caldav_actions, menu) + } + + override fun onPrepareMenu(menu: Menu) { + super.onPrepareMenu(menu) + menu.findItem(R.id.create_calendar).isVisible = false + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (super.onMenuItemSelected(menuItem)) { + return true + } + + if (menuItem.itemId == R.id.create_calendar) { + val intent = Intent(requireActivity(), CreateCalendarActivity::class.java) + intent.putExtra(CreateCalendarActivity.EXTRA_ACCOUNT, accountModel.account) + startActivity(intent) + return true + } + + return false + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -68,12 +94,14 @@ class WebcalFragment: CollectionsFragment() { }) } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) = - inflater.inflate(R.menu.caldav_actions, menu) + override fun onResume() { + super.onResume() + requireActivity().addMenuProvider(menuProvider) + } - override fun onPrepareOptionsMenu(menu: Menu) { - super.onPrepareOptionsMenu(menu) - menu.findItem(R.id.create_calendar).isVisible = false + override fun onPause() { + super.onPause() + requireActivity().removeMenuProvider(menuProvider) } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/webdav/AddWebdavMountActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/webdav/AddWebdavMountActivity.kt index 72f57a8c0..3b5a9adb4 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/webdav/AddWebdavMountActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/webdav/AddWebdavMountActivity.kt @@ -7,24 +7,26 @@ package at.bitfire.davdroid.ui.webdav import android.content.Context import android.os.Bundle import android.view.Menu +import android.view.MenuInflater import android.view.MenuItem import android.view.View import androidx.activity.viewModels import androidx.annotation.WorkerThread import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.MenuProvider import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.lifecycleScope import at.bitfire.dav4jvm.DavResource import at.bitfire.dav4jvm.UrlUtils import at.bitfire.davdroid.App -import at.bitfire.davdroid.network.HttpClient import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.ActivityAddWebdavMountBinding import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.db.WebDavMount import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.network.HttpClient import at.bitfire.davdroid.ui.UiUtils import at.bitfire.davdroid.webdav.CredentialsStore import at.bitfire.davdroid.webdav.DavDocumentsProvider @@ -66,14 +68,25 @@ class AddWebdavMountActivity: AppCompatActivity() { binding.addMount.setOnClickListener { validate() } - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.activity_add_webdav_mount, menu) - return true + addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.activity_add_webdav_mount, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + return when (menuItem.itemId) { + R.id.help -> { + onShowHelp() + true + } + else -> false + } + } + }) } - fun onShowHelp(item: MenuItem) { + fun onShowHelp() { UiUtils.launchUri(this, App.homepageUrl(this).buildUpon().appendPath("tested-with").build()) } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt index 3fdd02509..6d3a0ae86 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt @@ -15,6 +15,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.text.HtmlCompat +import androidx.core.view.MenuProvider import androidx.lifecycle.* import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager @@ -88,14 +89,25 @@ class WebdavMountsActivity: AppCompatActivity() { binding.add.setOnClickListener { startActivity(Intent(this, AddWebdavMountActivity::class.java)) } - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.activity_webdav_mounts, menu) - return true + addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.activity_webdav_mounts, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + return when (menuItem.itemId) { + R.id.help -> { + onShowHelp() + true + } + else -> false + } + } + }) } - fun onShowHelp(item: MenuItem) { + fun onShowHelp() { UiUtils.launchUri(this, helpUrl()) } diff --git a/app/src/main/res/menu/activity_about.xml b/app/src/main/res/menu/activity_about.xml index 74d68b07e..9a6fa9530 100644 --- a/app/src/main/res/menu/activity_about.xml +++ b/app/src/main/res/menu/activity_about.xml @@ -3,9 +3,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + app:showAsAction="ifRoom" /> \ No newline at end of file diff --git a/app/src/main/res/menu/activity_account.xml b/app/src/main/res/menu/activity_account.xml index 3ad391ccb..c64e8cfd5 100644 --- a/app/src/main/res/menu/activity_account.xml +++ b/app/src/main/res/menu/activity_account.xml @@ -7,17 +7,14 @@ diff --git a/app/src/main/res/menu/activity_accounts.xml b/app/src/main/res/menu/activity_accounts.xml index f173669d3..05a0244f5 100644 --- a/app/src/main/res/menu/activity_accounts.xml +++ b/app/src/main/res/menu/activity_accounts.xml @@ -5,7 +5,6 @@ \ No newline at end of file diff --git a/app/src/main/res/menu/activity_add_webdav_mount.xml b/app/src/main/res/menu/activity_add_webdav_mount.xml index 520e126f0..5c1f92216 100644 --- a/app/src/main/res/menu/activity_add_webdav_mount.xml +++ b/app/src/main/res/menu/activity_add_webdav_mount.xml @@ -6,7 +6,6 @@ android:id="@+id/help" android:icon="@drawable/ic_help" android:title="@string/help" - app:showAsAction="always" - android:onClick="onShowHelp" /> + app:showAsAction="always" /> \ No newline at end of file diff --git a/app/src/main/res/menu/activity_webdav_mounts.xml b/app/src/main/res/menu/activity_webdav_mounts.xml index 520e126f0..5c1f92216 100644 --- a/app/src/main/res/menu/activity_webdav_mounts.xml +++ b/app/src/main/res/menu/activity_webdav_mounts.xml @@ -6,7 +6,6 @@ android:id="@+id/help" android:icon="@drawable/ic_help" android:title="@string/help" - app:showAsAction="always" - android:onClick="onShowHelp" /> + app:showAsAction="always" /> \ No newline at end of file From 5ae70cb5d040b00208c01215f0cdd0000d9cabac Mon Sep 17 00:00:00 2001 From: Arnau Mora Date: Mon, 16 Oct 2023 14:31:17 +0100 Subject: [PATCH 2/9] BatteryOptimizationIntroFragment: use contract instead of onActivityResult (#444) * Using result launcher Signed-off-by: Arnau Mora * Minor re-ordering --------- Signed-off-by: Arnau Mora Co-authored-by: Ricki Hirner --- .../ui/intro/BatteryOptimizationsFragment.kt | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt index c6a8df5da..f58f08ba4 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt @@ -5,6 +5,7 @@ package at.bitfire.davdroid.ui.intro import android.annotation.SuppressLint +import android.app.Application import android.content.Context import android.content.Intent import android.net.Uri @@ -14,12 +15,13 @@ import android.os.PowerManager import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContract import androidx.core.content.getSystemService import androidx.databinding.ObservableBoolean import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import at.bitfire.davdroid.App import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.R @@ -34,7 +36,6 @@ import dagger.hilt.InstallIn import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.components.ActivityComponent import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.multibindings.IntoSet import org.apache.commons.text.WordUtils import java.util.* @@ -43,12 +44,13 @@ import javax.inject.Inject @AndroidEntryPoint class BatteryOptimizationsFragment: Fragment() { - companion object { - const val REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = 0 - } - val model by viewModels() + private val ignoreBatteryOptimizationsResultLauncher = + registerForActivityResult(IgnoreBatteryOptimizationsContract) { + model.checkWhitelisted() + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val binding = IntroBatteryOptimizationsBinding.inflate(inflater, container, false) @@ -58,10 +60,7 @@ class BatteryOptimizationsFragment: Fragment() { model.shouldBeWhitelisted.observe(viewLifecycleOwner) { shouldBeWhitelisted -> @SuppressLint("BatteryLife") if (shouldBeWhitelisted && !model.isWhitelisted.value!!) - startActivityForResult(Intent( - android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, - Uri.parse("package:" + BuildConfig.APPLICATION_ID) - ), REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + ignoreBatteryOptimizationsResultLauncher.launch(BuildConfig.APPLICATION_ID) } binding.batteryText.text = getString(R.string.intro_battery_text, getString(R.string.app_name)) @@ -78,12 +77,6 @@ class BatteryOptimizationsFragment: Fragment() { return binding.root } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) - model.checkWhitelisted() - } - override fun onResume() { super.onResume() model.checkWhitelisted() @@ -92,9 +85,9 @@ class BatteryOptimizationsFragment: Fragment() { @HiltViewModel class Model @Inject constructor( - @ApplicationContext val context: Context, + application: Application, val settings: SettingsManager - ): ViewModel() { + ): AndroidViewModel(application) { companion object { @@ -163,7 +156,7 @@ class BatteryOptimizationsFragment: Fragment() { } fun checkWhitelisted() { - val whitelisted = isWhitelisted(context) + val whitelisted = isWhitelisted(getApplication()) isWhitelisted.value = whitelisted shouldBeWhitelisted.value = whitelisted @@ -175,6 +168,21 @@ class BatteryOptimizationsFragment: Fragment() { } + @SuppressLint("BatteryLife") + object IgnoreBatteryOptimizationsContract: ActivityResultContract() { + override fun createIntent(context: Context, input: String): Intent { + return Intent( + android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:$input") + ) + } + + override fun parseResult(resultCode: Int, intent: Intent?): Unit? { + return null + } + } + + @Module @InstallIn(ActivityComponent::class) abstract class BatteryOptimizationsFragmentModule { @@ -187,17 +195,17 @@ class BatteryOptimizationsFragment: Fragment() { ): IntroFragmentFactory { override fun getOrder(context: Context) = - // show fragment when: - // 1. DAVx5 is not whitelisted yet and "don't show anymore" has not been clicked, and/or - // 2a. evil manufacturer AND - // 2b. "don't show anymore" has not been clicked - if ( - (!Model.isWhitelisted(context) && settingsManager.getBooleanOrNull(HINT_BATTERY_OPTIMIZATIONS) != false) || - (Model.manufacturerWarning && settingsManager.getBooleanOrNull(HINT_AUTOSTART_PERMISSION) != false) - ) - 100 - else - IntroFragmentFactory.DONT_SHOW + // show fragment when: + // 1. DAVx5 is not whitelisted yet and "don't show anymore" has not been clicked, and/or + // 2a. evil manufacturer AND + // 2b. "don't show anymore" has not been clicked + if ( + (!Model.isWhitelisted(context) && settingsManager.getBooleanOrNull(HINT_BATTERY_OPTIMIZATIONS) != false) || + (Model.manufacturerWarning && settingsManager.getBooleanOrNull(HINT_AUTOSTART_PERMISSION) != false) + ) + 100 + else + IntroFragmentFactory.DONT_SHOW override fun create() = BatteryOptimizationsFragment() } From 8ffed42eb936592ee84821eba1fbc87a3794cb83 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 17 Oct 2023 10:17:17 +0200 Subject: [PATCH 3/9] PermissionsIntroFragment: take jtx Board and tasks.org permissions into account (#450) --- .../at/bitfire/davdroid/ui/intro/PermissionsIntroFragment.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/PermissionsIntroFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/PermissionsIntroFragment.kt index 0588aefce..98df793fd 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/PermissionsIntroFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/PermissionsIntroFragment.kt @@ -27,7 +27,10 @@ class PermissionsIntroFragment : Fragment() { override fun getOrder(context: Context): Int { // show PermissionsFragment as intro fragment when no permissions are granted - val permissions = CONTACT_PERMISSIONS + CALENDAR_PERMISSIONS + TaskProvider.PERMISSIONS_OPENTASKS + val permissions = CONTACT_PERMISSIONS + CALENDAR_PERMISSIONS + + TaskProvider.PERMISSIONS_JTX + + TaskProvider.PERMISSIONS_OPENTASKS + + TaskProvider.PERMISSIONS_TASKS_ORG return if (PermissionUtils.haveAnyPermission(context, permissions)) IntroFragmentFactory.DONT_SHOW else From 58d4a9f663054532667beafcef49c0cf9467032c Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 17 Oct 2023 18:50:42 +0200 Subject: [PATCH 4/9] Make all IntroFragments appear at first start (#452) * IntroFragments: use (factory,order) List instead of (order,factory) Map to store them * Adapt OpenSourceFragment order --- .../bitfire/davdroid/ui/AccountsActivity.kt | 5 ++-- .../ui/intro/BatteryOptimizationsFragment.kt | 2 +- .../davdroid/ui/intro/IntroActivity.kt | 25 +++++++++++-------- .../davdroid/ui/intro/IntroFragmentFactory.kt | 2 +- .../davdroid/ui/intro/OpenSourceFragment.kt | 4 +-- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt index 5bf41a3c4..8d3053328 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/AccountsActivity.kt @@ -54,11 +54,10 @@ class AccountsActivity: AppCompatActivity(), NavigationView.OnNavigationItemSele super.onCreate(savedInstanceState) if (savedInstanceState == null) { + // use a separate thread to check whether IntroActivity should be shown CoroutineScope(Dispatchers.Default).launch { - // use a separate thread to check whether IntroActivity should be shown - if (IntroActivity.shouldShowIntroActivity(this@AccountsActivity)) { + if (IntroActivity.shouldShowIntroActivity(this@AccountsActivity)) introActivityLauncher.launch(null) - } } } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt index f58f08ba4..761e1f45e 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/BatteryOptimizationsFragment.kt @@ -210,4 +210,4 @@ class BatteryOptimizationsFragment: Fragment() { override fun create() = BatteryOptimizationsFragment() } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/IntroActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/IntroActivity.kt index 914c3ac29..535bebb17 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/IntroActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/IntroActivity.kt @@ -10,6 +10,7 @@ import android.content.Intent import android.os.Bundle import androidx.activity.result.contract.ActivityResultContract import androidx.activity.addCallback +import androidx.annotation.WorkerThread import androidx.core.content.res.ResourcesCompat import androidx.fragment.app.Fragment import at.bitfire.davdroid.R @@ -33,12 +34,11 @@ class IntroActivity: AppIntro2() { companion object { + @WorkerThread fun shouldShowIntroActivity(activity: Activity): Boolean { val factories = EntryPointAccessors.fromActivity(activity, IntroActivityEntryPoint::class.java).introFragmentFactories() return factories.any { - val order = it.getOrder(activity) - Logger.log.fine("Found intro fragment factory ${it::class.java} with order $order") - order > 0 + it.getOrder(activity) > 0 } } @@ -46,19 +46,24 @@ class IntroActivity: AppIntro2() { private var currentSlide = 0 - @Inject lateinit var introFragmentFactories: Set<@JvmSuppressWildcards IntroFragmentFactory> - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val factoriesWithOrder = introFragmentFactories - .associateBy { it.getOrder(this) } - .filterKeys { it != IntroFragmentFactory.DONT_SHOW } + val factories = EntryPointAccessors.fromActivity(this, IntroActivityEntryPoint::class.java).introFragmentFactories() + for (factory in factories) + Logger.log.fine("Found intro fragment factory ${factory::class.java} with order ${factory.getOrder(this)}") + + val factoriesWithOrder = factories + .associateWith { it.getOrder(this) } + .filterValues { it != IntroFragmentFactory.DONT_SHOW } - val anyPositiveOrder = factoriesWithOrder.keys.any { it > 0 } + val anyPositiveOrder = factoriesWithOrder.values.any { it > 0 } if (anyPositiveOrder) { - for ((_, factory) in factoriesWithOrder.toSortedMap()) + val factoriesSortedByOrder = factoriesWithOrder + .toList() + .sortedBy { (_, v) -> v } // sort by value (= getOrder()) + for ((factory, _) in factoriesSortedByOrder) addSlide(factory.create()) } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/IntroFragmentFactory.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/IntroFragmentFactory.kt index 3e6493ff3..3488f84de 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/IntroFragmentFactory.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/IntroFragmentFactory.kt @@ -22,7 +22,7 @@ interface IntroFragmentFactory { * @return Order with which an instance of this fragment type shall be created and shown. Possible values: * * * <0: only show the fragment when there is at least one other fragment with positive order (lower numbers are shown first) - * * 0: don't show the fragment + * * [DONT_SHOW] (0): don't show the fragment * * ≥0: show the fragment (lower numbers are shown first) */ fun getOrder(context: Context): Int diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt index 30f01de01..020482690 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/intro/OpenSourceFragment.kt @@ -21,7 +21,6 @@ import at.bitfire.davdroid.ui.UiUtils import at.bitfire.davdroid.ui.intro.OpenSourceFragment.Model.Companion.SETTING_NEXT_DONATION_POPUP import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @AndroidEntryPoint @@ -48,7 +47,6 @@ class OpenSourceFragment: Fragment() { @HiltViewModel class Model @Inject constructor( - @ApplicationContext val context: Context, val settings: SettingsManager ): ViewModel() { @@ -76,7 +74,7 @@ class OpenSourceFragment: Fragment() { override fun getOrder(context: Context) = if (System.currentTimeMillis() > (settingsManager.getLongOrNull(SETTING_NEXT_DONATION_POPUP) ?: 0)) - 100 + 500 else 0 From 52747e632fbedcb21b578918c6802bb32cc2a6f9 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Wed, 18 Oct 2023 14:44:16 +0200 Subject: [PATCH 5/9] LoginActivity: add Nextcloud Login Flow (bitfireAT/davx5#403) * Replace onActivityResult by contract * Add Nextcloud option to default login screen * Decouple NextcloudLoginFlowComposable from model * UI and model changes * Single-line URL field * Add progress indicator and other secondary UI --- .../setup/DefaultLoginCredentialsFragment.kt | 12 +- .../ui/setup/DefaultLoginCredentialsModel.kt | 1 + .../ui/setup/NextcloudLoginFlowFragment.kt | 279 ++++++++++++++---- .../res/layout/login_credentials_fragment.xml | 17 +- app/src/main/res/values/strings.xml | 8 + 5 files changed, 252 insertions(+), 65 deletions(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsFragment.kt index 27bca24a6..1f2b53382 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsFragment.kt @@ -71,10 +71,11 @@ class DefaultLoginCredentialsFragment : Fragment() { v.login.setOnClickListener { _ -> if (validate()) { val nextFragment = - if (model.loginGoogle.value == true) - GoogleLoginFragment() - else - DetectConfigurationFragment() + when { + model.loginGoogle.value == true -> GoogleLoginFragment() + model.loginNextcloud.value == true -> NextcloudLoginFlowFragment() + else -> DetectConfigurationFragment() + } parentFragmentManager.beginTransaction() .replace(android.R.id.content, nextFragment, null) @@ -204,7 +205,8 @@ class DefaultLoginCredentialsFragment : Fragment() { } } - model.loginGoogle.value == true -> { + // some login methods don't require further input → always valid + model.loginGoogle.value == true || model.loginNextcloud.value == true -> { valid = true } } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt index 30c9ed2d1..5edb235be 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DefaultLoginCredentialsModel.kt @@ -29,6 +29,7 @@ class DefaultLoginCredentialsModel(app: Application): AndroidViewModel(app) { val loginWithUrlAndUsername = MutableLiveData(false) val loginAdvanced = MutableLiveData(false) val loginGoogle = MutableLiveData(false) + val loginNextcloud = MutableLiveData(false) val baseUrl = MutableLiveData() val baseUrlError = MutableLiveData() diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/NextcloudLoginFlowFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/NextcloudLoginFlowFragment.kt index 3e9a25403..423a4499a 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/NextcloudLoginFlowFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/NextcloudLoginFlowFragment.kt @@ -7,16 +7,38 @@ package at.bitfire.davdroid.ui.setup import android.annotation.SuppressLint import android.app.Application import android.content.Intent -import android.net.Uri import android.os.Bundle import android.provider.Browser import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.browser.customtabs.CustomTabsIntent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Button +import androidx.compose.material.LinearProgressIndicator +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.intl.Locale +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.core.os.bundleOf import androidx.fragment.app.Fragment @@ -24,14 +46,15 @@ import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope import at.bitfire.dav4jvm.exception.DavException import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.davdroid.R import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.network.HttpClient -import at.bitfire.davdroid.ui.DebugInfoActivity import at.bitfire.davdroid.ui.UiUtils.haveCustomTabs +import com.google.accompanist.themeadapter.material.MdcTheme import com.google.android.material.snackbar.Snackbar import dagger.Binds import dagger.Module @@ -39,11 +62,11 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntKey import dagger.multibindings.IntoMap -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.MediaType.Companion.toMediaType import okhttp3.Request import okhttp3.RequestBody @@ -51,14 +74,15 @@ import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject import java.net.HttpURLConnection import java.net.URI +import java.util.logging.Level import javax.inject.Inject class NextcloudLoginFlowFragment: Fragment() { companion object { - const val LOGIN_FLOW_V1_PATH = "/index.php/login/flow" - const val LOGIN_FLOW_V2_PATH = "/index.php/login/v2" + const val LOGIN_FLOW_V1_PATH = "index.php/login/flow" + val LOGIN_FLOW_V2_PATH = "index.php/login/v2" /** Set this to 1 to indicate that Login Flow shall be used. */ const val EXTRA_LOGIN_FLOW = "loginFlow" @@ -66,31 +90,47 @@ class NextcloudLoginFlowFragment: Fragment() { /** Path to DAV endpoint (e.g. `/remote.php/dav`). Will be appended to the * server URL returned by Login Flow without further processing. */ const val EXTRA_DAV_PATH = "davPath" - - const val REQUEST_BROWSER = 0 } val loginModel by activityViewModels() - val loginFlowModel by viewModels() + val model by viewModels() + + val checkResultCallback = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + val davPath = requireActivity().intent.getStringExtra(EXTRA_DAV_PATH) + model.checkResult(davPath) + } @SuppressLint("SetJavaScriptEnabled") override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - val view = View(requireActivity()) - - val entryUrl = requireActivity().intent.data ?: throw IllegalArgumentException("Intent data must be set to Login Flow URL") - Logger.log.info("Using Login Flow entry point: $entryUrl") + val entryUrl = requireActivity().intent.data?.toString()?.toHttpUrlOrNull() + + val view = ComposeView(requireActivity()).apply { + setContent { + MdcTheme { + NextcloudLoginComposable( + onStart = { url -> + model.start(url) + }, + entryUrl = entryUrl, + inProgress = model.inProgress.observeAsState(false), + error = model.error.observeAsState() + ) + } + } + } - loginFlowModel.loginUrl.observe(viewLifecycleOwner) { loginUrl -> + model.loginUrl.observe(viewLifecycleOwner) { loginUrl -> if (loginUrl == null) return@observe val loginUri = loginUrl.toUri() // reset URL so that the browser isn't shown another time - loginFlowModel.loginUrl.value = null + model.loginUrl.value = null if (haveCustomTabs(requireActivity())) { // Custom Tabs are available + @Suppress("DEPRECATION") val browser = CustomTabsIntent.Builder() .setToolbarColor(resources.getColor(R.color.primaryColor)) .build() @@ -99,31 +139,23 @@ class NextcloudLoginFlowFragment: Fragment() { Browser.EXTRA_HEADERS, bundleOf("Accept-Language" to Locale.current.toLanguageTag()) ) - startActivityForResult(browser.intent, REQUEST_BROWSER, browser.startAnimationBundle) - + checkResultCallback.launch(browser.intent) } else { // fallback: launch normal browser val browser = Intent(Intent.ACTION_VIEW, loginUri) browser.addCategory(Intent.CATEGORY_BROWSABLE) if (browser.resolveActivity(requireActivity().packageManager) != null) - startActivityForResult(browser, REQUEST_BROWSER) + checkResultCallback.launch(browser) else Snackbar.make(view, getString(R.string.install_browser), Snackbar.LENGTH_INDEFINITE).show() } } - loginFlowModel.error.observe(viewLifecycleOwner) { exception -> - Snackbar.make(requireView(), exception.toString(), Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.exception_show_details) { - val intent = DebugInfoActivity.IntentBuilder(requireActivity()) - .withCause(exception) - .build() - startActivity(intent) - } - .show() - } + model.loginData.observe(viewLifecycleOwner) { loginData -> + if (loginData == null) + return@observe + val (baseUri, credentials) = loginData - loginFlowModel.loginData.observe(viewLifecycleOwner) { (baseUri, credentials) -> // continue to next fragment loginModel.baseURI = baseUri loginModel.credentials = credentials @@ -131,38 +163,33 @@ class NextcloudLoginFlowFragment: Fragment() { .replace(android.R.id.content, DetectConfigurationFragment(), null) .addToBackStack(null) .commit() + + // reset loginData so that we can go back + model.loginData.value = null } - // start Login Flow - loginFlowModel.setUrl(entryUrl) + if (savedInstanceState == null && entryUrl != null) + model.start(entryUrl) return view } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode != REQUEST_BROWSER) - return - - val davPath = requireActivity().intent.getStringExtra(EXTRA_DAV_PATH) - loginFlowModel.checkResult(davPath) - } - /** * Implements Login Flow v2. * * @see https://docs.nextcloud.com/server/20/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2 */ - class LoginFlowModel(app: Application): AndroidViewModel(app) { - - val error = MutableLiveData() + class Model(app: Application): AndroidViewModel(app) { val loginUrl = MutableLiveData() + val error = MutableLiveData() val httpClient by lazy { HttpClient.Builder(getApplication()) - .setForeground(true) - .build() + .setForeground(true) + .build() } + val inProgress = MutableLiveData(false) var pollUrl: HttpUrl? = null var token: String? = null @@ -174,20 +201,30 @@ class NextcloudLoginFlowFragment: Fragment() { } + /** + * Starts the Login Flow. + * + * @param entryUrl entryURL: either a Login Flow path (ending with [LOGIN_FLOW_V1_PATH] or [LOGIN_FLOW_V2_PATH]), + * or another URL which is treated as Nextcloud root URL. In this case, [LOGIN_FLOW_V2_PATH] is appended. + */ @UiThread - fun setUrl(entryUri: Uri) { - val entryUrl = entryUri.toString() - val v2Url = - if (entryUrl.endsWith(LOGIN_FLOW_V1_PATH)) - // got Login Flow v1 URL, rewrite to v2 - entryUrl.removeSuffix(LOGIN_FLOW_V1_PATH) + LOGIN_FLOW_V2_PATH - else - entryUrl + fun start(entryUrl: HttpUrl) { + inProgress.value = true + error.value = null + + var entryUrlStr = entryUrl.toString() + if (entryUrlStr.endsWith(LOGIN_FLOW_V1_PATH)) + // got Login Flow v1 URL, rewrite to v2 + entryUrlStr = entryUrlStr.removeSuffix(LOGIN_FLOW_V1_PATH) + + val v2Url = entryUrlStr.toHttpUrl().newBuilder() + .addPathSegments(LOGIN_FLOW_V2_PATH) + .build() // send POST request and process JSON reply - CoroutineScope(Dispatchers.IO).launch { + viewModelScope.launch(Dispatchers.IO) { try { - val json = postForJson(v2Url.toHttpUrl(), "".toRequestBody()) + val json = postForJson(v2Url, "".toRequestBody()) // login URL loginUrl.postValue(json.getString("login")) @@ -198,7 +235,10 @@ class NextcloudLoginFlowFragment: Fragment() { token = poll.getString("token") } } catch (e: Exception) { - error.postValue(e) + Logger.log.log(Level.WARNING, "Couldn't obtain login URL", e) + error.postValue(getApplication().getString(R.string.login_nextcloud_login_flow_no_login_url)) + } finally { + inProgress.postValue(false) } } } @@ -208,7 +248,7 @@ class NextcloudLoginFlowFragment: Fragment() { val pollUrl = pollUrl ?: return val token = token ?: return - CoroutineScope(Dispatchers.IO).launch { + viewModelScope.launch(Dispatchers.IO) { try { val json = postForJson(pollUrl, "token=$token".toRequestBody("application/x-www-form-urlencoded".toMediaType())) val serverUrl = json.getString("server") @@ -221,11 +261,12 @@ class NextcloudLoginFlowFragment: Fragment() { URI.create(serverUrl) loginData.postValue(Pair( - baseUri, - Credentials(loginName, appPassword) + baseUri, + Credentials(loginName, appPassword) )) } catch (e: Exception) { - error.postValue(e) + Logger.log.log(Level.WARNING, "Polling login URL failed", e) + error.postValue(getApplication().getString(R.string.login_nextcloud_login_flow_no_login_data)) } } } @@ -259,7 +300,7 @@ class NextcloudLoginFlowFragment: Fragment() { class Factory @Inject constructor(): LoginCredentialsFragmentFactory { override fun getFragment(intent: Intent) = - if (intent.hasExtra(EXTRA_LOGIN_FLOW)) + if (intent.hasExtra(EXTRA_LOGIN_FLOW) && intent.data != null) NextcloudLoginFlowFragment() else null @@ -275,4 +316,124 @@ class NextcloudLoginFlowFragment: Fragment() { abstract fun factory(impl: Factory): LoginCredentialsFragmentFactory } +} + + +@Composable +fun NextcloudLoginComposable( + entryUrl: HttpUrl?, + inProgress: State, + error: State, + onStart: (HttpUrl) -> Unit +) { + Column { + if (inProgress.value) + LinearProgressIndicator( + color = MaterialTheme.colors.secondary, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) + ) + + Column(modifier = Modifier.padding(8.dp)) { + Text( + stringResource(R.string.login_nextcloud_login_with_nextcloud), + style = MaterialTheme.typography.h5 + ) + NextcloudLoginFlowComposable( + providedEntryUrl = entryUrl, + inProgress = inProgress, + error = error, + onStart = onStart + ) + } + } +} + + +@Composable +fun NextcloudLoginFlowComposable( + providedEntryUrl: HttpUrl?, + inProgress: State, + error: State, + onStart: ((HttpUrl) -> Unit) +) { + Column { + Text( + stringResource(R.string.login_nextcloud_login_flow), + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(top = 16.dp) + ) + Text( + stringResource(R.string.login_nextcloud_login_flow_text), + modifier = Modifier.padding(vertical = 8.dp) + ) + + val entryUrlStr = remember { mutableStateOf(providedEntryUrl?.toString() ?: "") } + val entryUrl = remember { mutableStateOf(providedEntryUrl) } + OutlinedTextField(entryUrlStr.value, + onValueChange = { newUrlStr -> + entryUrlStr.value = newUrlStr + + entryUrl.value = try { + val withScheme = + if (!newUrlStr.startsWith("http://", true) && !newUrlStr.startsWith("https://", true)) + "https://$newUrlStr" + else + newUrlStr + withScheme.toHttpUrl() + } catch (e: IllegalArgumentException) { + null + } + }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + readOnly = inProgress.value, + label = { + Text(stringResource(R.string.login_nextcloud_login_flow_server_address)) + }, + placeholder = { + Text("cloud.example.com") + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Uri, + imeAction = ImeAction.Go + ), + keyboardActions = KeyboardActions( + onGo = { + entryUrl.value?.let(onStart) + } + ), + singleLine = true + ) + + Button( + onClick = { + entryUrl.value?.let(onStart) + }, + enabled = entryUrl.value != null && !inProgress.value + ) { + Text(stringResource(R.string.login_nextcloud_login_flow_sign_in)) + } + + error.value?.let { msg -> + Text( + msg, + color = MaterialTheme.colors.error, + modifier = Modifier.padding(vertical = 8.dp) + ) + } + } +} + +@Composable +@Preview +fun NextcloudLoginFlowComposable_PreviewWithError() { + NextcloudLoginFlowComposable( + providedEntryUrl = null, + inProgress = remember { mutableStateOf(true) }, + error = remember { mutableStateOf("Something wrong happened") }, + onStart = { } + ) } \ No newline at end of file diff --git a/app/src/main/res/layout/login_credentials_fragment.xml b/app/src/main/res/layout/login_credentials_fragment.xml index 8b7a93fb9..47fc82990 100644 --- a/app/src/main/res/layout/login_credentials_fragment.xml +++ b/app/src/main/res/layout/login_credentials_fragment.xml @@ -314,16 +314,31 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6ac45cec3..a64054e88 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -297,6 +297,14 @@ Privacy policy for details.]]> Google API Services User Data Policy, including the Limited Use requirements.]]> Couldn\'t obtain authorization code + Nextcloud + Login with Nextcloud + Login Flow + This will start the Nextcloud Login Flow in a Web browser. + Nextcloud server address + Sign in + Couldn\'t obtain login URL + Couldn\'t obtain login data Configuration detection Please wait, querying server… From b26ae345cd7d159920e500ab94e5b89c197a146c Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Wed, 18 Oct 2023 15:08:43 +0200 Subject: [PATCH 6/9] Nextcloud: pre-select contact group method (CATEGORIES) (bitfireAT/davx5#410) * LoginActivity: refactor menu to MenuProvider; LoginModel: add contact group type * Take LoginModel group method into account when creating the account; Nextcloud login: set preferred contact group type --- .../ui/setup/AccountDetailsFragment.kt | 26 +++++++++++++---- .../davdroid/ui/setup/LoginActivity.kt | 28 ++++++++++++------- .../bitfire/davdroid/ui/setup/LoginModel.kt | 10 ++++--- .../ui/setup/NextcloudLoginFlowFragment.kt | 2 ++ app/src/main/res/menu/activity_login.xml | 6 ++-- 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index aa2504b44..2376be214 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -19,7 +19,11 @@ import android.widget.ArrayAdapter import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import androidx.lifecycle.* +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.LoginAccountDetailsBinding @@ -101,10 +105,10 @@ class AccountDetailsFragment : Fragment() { v.createAccount.visibility = View.GONE model.createAccount( - name, - loginModel.credentials, - config, - GroupMethod.valueOf(groupMethodName) + name, + loginModel.credentials, + config, + GroupMethod.valueOf(groupMethodName) ).observe(viewLifecycleOwner, Observer { success -> if (success) { // close Create account activity @@ -126,6 +130,7 @@ class AccountDetailsFragment : Fragment() { val forcedGroupMethod = settings.getString(AccountSettings.KEY_CONTACT_GROUP_METHOD)?.let { GroupMethod.valueOf(it) } if (forcedGroupMethod != null) { + // contact group type forced by settings v.contactGroupMethod.isEnabled = false for ((i, method) in resources.getStringArray(R.array.settings_contact_group_method_values).withIndex()) { if (method == forcedGroupMethod.name) { @@ -133,8 +138,17 @@ class AccountDetailsFragment : Fragment() { break } } - } else + } else { + // contact group type selectable v.contactGroupMethod.isEnabled = true + for ((i, method) in resources.getStringArray(R.array.settings_contact_group_method_values).withIndex()) { + // take suggestion from detection process into account + if (method == loginModel.suggestedGroupMethod.name) { + v.contactGroupMethod.setSelection(i) + break + } + } + } return v.root } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginActivity.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginActivity.kt index e03c39899..3925fcc13 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginActivity.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginActivity.kt @@ -6,8 +6,10 @@ package at.bitfire.davdroid.ui.setup import android.os.Bundle import android.view.Menu +import android.view.MenuInflater import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import at.bitfire.davdroid.App import at.bitfire.davdroid.R @@ -51,6 +53,22 @@ class LoginActivity: AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + addMenuProvider(object: MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.activity_login, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + if (menuItem.itemId == R.id.help) { + UiUtils.launchUri(this@LoginActivity, + App.homepageUrl(this@LoginActivity).buildUpon().appendPath("tested-with").build()) + return true + } + + return false + } + }) + if (savedInstanceState == null) { // first call, add first login fragment val factories = loginFragmentFactories // get factories from hilt @@ -71,14 +89,4 @@ class LoginActivity: AppCompatActivity() { } } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.activity_login, menu) - return true - } - - fun showHelp(item: MenuItem) { - UiUtils.launchUri(this, - App.homepageUrl(this).buildUpon().appendPath("tested-with").build()) - } - } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginModel.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginModel.kt index 7aec2bfb2..976d30856 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginModel.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/LoginModel.kt @@ -7,6 +7,7 @@ package at.bitfire.davdroid.ui.setup import androidx.lifecycle.ViewModel import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.servicedetection.DavResourceFinder +import at.bitfire.vcard4android.GroupMethod import java.net.URI class LoginModel: ViewModel() { @@ -16,9 +17,10 @@ class LoginModel: ViewModel() { var configuration: DavResourceFinder.Configuration? = null - /** - * Account name that should be used as default account name when no email addresses have been found. - */ + /** account name that should be used as default account name when no email addresses have been found */ var suggestedAccountName: String? = null -} + /** group method that should be pre-selectedbr */ + var suggestedGroupMethod: GroupMethod = GroupMethod.GROUP_VCARDS + +} \ No newline at end of file diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/NextcloudLoginFlowFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/NextcloudLoginFlowFragment.kt index 423a4499a..4b2d3c721 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/NextcloudLoginFlowFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/NextcloudLoginFlowFragment.kt @@ -54,6 +54,7 @@ import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.network.HttpClient import at.bitfire.davdroid.ui.UiUtils.haveCustomTabs +import at.bitfire.vcard4android.GroupMethod import com.google.accompanist.themeadapter.material.MdcTheme import com.google.android.material.snackbar.Snackbar import dagger.Binds @@ -159,6 +160,7 @@ class NextcloudLoginFlowFragment: Fragment() { // continue to next fragment loginModel.baseURI = baseUri loginModel.credentials = credentials + loginModel.suggestedGroupMethod = GroupMethod.CATEGORIES parentFragmentManager.beginTransaction() .replace(android.R.id.content, DetectConfigurationFragment(), null) .addToBackStack(null) diff --git a/app/src/main/res/menu/activity_login.xml b/app/src/main/res/menu/activity_login.xml index e889350ce..0649926fe 100644 --- a/app/src/main/res/menu/activity_login.xml +++ b/app/src/main/res/menu/activity_login.xml @@ -7,8 +7,6 @@ android:id="@+id/help" android:title="@string/help" android:icon="@drawable/ic_help" - app:showAsAction="always" - android:onClick="showHelp"> - - + app:showAsAction="always" /> + \ No newline at end of file From e41ac428c9ba0e764934a3a36ca32765ca603106 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Tue, 17 Oct 2023 18:59:52 +0200 Subject: [PATCH 7/9] WebcalFragment: remove unused menu item code --- .../davdroid/ui/account/WebcalFragment.kt | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/WebcalFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/WebcalFragment.kt index a3501070d..6c70a5143 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/account/WebcalFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/account/WebcalFragment.kt @@ -50,7 +50,7 @@ class WebcalFragment: CollectionsFragment() { override val noCollectionsStringId = R.string.account_no_webcals @Inject lateinit var webcalModelFactory: WebcalModel.Factory - val webcalModel by viewModels() { + private val webcalModel by viewModels { object : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class) = @@ -69,21 +69,6 @@ class WebcalFragment: CollectionsFragment() { super.onPrepareMenu(menu) menu.findItem(R.id.create_calendar).isVisible = false } - - override fun onMenuItemSelected(menuItem: MenuItem): Boolean { - if (super.onMenuItemSelected(menuItem)) { - return true - } - - if (menuItem.itemId == R.id.create_calendar) { - val intent = Intent(requireActivity(), CreateCalendarActivity::class.java) - intent.putExtra(CreateCalendarActivity.EXTRA_ACCOUNT, accountModel.account) - startActivity(intent) - return true - } - - return false - } } override fun onCreate(savedInstanceState: Bundle?) { From c451c3fd70c8714c0055b17134eeff59561ac465 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Wed, 18 Oct 2023 15:08:52 +0200 Subject: [PATCH 8/9] Version bump to 4.3.9-beta.1 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 31ef4acaf..07421f39c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { defaultConfig { applicationId "at.bitfire.davdroid" - versionCode 403080000 - versionName '4.3.8' + versionCode 403090000 + versionName '4.3.9-beta.1' buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" From f8330e8f52f71ee9a36a8ed9e5858176b883f8cd Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Wed, 18 Oct 2023 15:30:33 +0200 Subject: [PATCH 9/9] dnsjava: fix R8 rules --- app/proguard-rules-release.pro | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/proguard-rules-release.pro b/app/proguard-rules-release.pro index 9fac0cab2..eeaa9c7a1 100644 --- a/app/proguard-rules-release.pro +++ b/app/proguard-rules-release.pro @@ -30,9 +30,13 @@ # Additional rules which are now required since missing classes can't be ignored in R8 anymore. # [https://developer.android.com/build/releases/past-releases/agp-7-0-0-release-notes#r8-missing-class-warning] -dontwarn com.android.org.conscrypt.SSLParametersImpl +-dontwarn com.sun.jna.** # dnsjava -dontwarn groovy.** -dontwarn java.beans.Transient +-dontwarn javax.naming.NamingException # dnsjava +-dontwarn javax.naming.directory.** # dnsjava -dontwarn junit.textui.TestRunner +-dontwarn lombok.** # dnsjava -dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl -dontwarn org.bouncycastle.jsse.** -dontwarn org.codehaus.groovy.**