From ae8e20d1c4da630d729ee17cdf9712e0b355a89c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 11 May 2026 14:29:19 +0200 Subject: [PATCH 1/3] fix(backup-fragment): lifecycle, permission management Signed-off-by: alperozturk96 --- .../fragment/contactsbackup/BackupFragment.kt | 194 +++++++++--------- 1 file changed, 102 insertions(+), 92 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt index 957cec7b0d0b..c3fd881ace9f 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt @@ -15,17 +15,22 @@ import android.content.Context import android.content.Intent import android.os.Bundle 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 android.widget.CompoundButton import android.widget.DatePicker -import androidx.core.app.ActivityCompat +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.MenuProvider +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import com.nextcloud.client.account.User import com.nextcloud.client.di.Injectable import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.utils.extensions.getSerializableArgument +import com.nextcloud.utils.extensions.getTypedActivity import com.nextcloud.utils.extensions.setVisibleIf import com.owncloud.android.R import com.owncloud.android.databinding.BackupFragmentBinding @@ -116,8 +121,6 @@ class BackupFragment : val view: View = binding.root - setHasOptionsMenu(true) - if (arguments != null) { showSidebar = requireArguments().getBoolean(ARG_SHOW_SIDEBAR) } @@ -139,7 +142,7 @@ class BackupFragment : setupDates(savedInstanceState) applyUserColor() - + addMenuProvider() return view } @@ -320,28 +323,92 @@ class BackupFragment : } } - @Deprecated("Deprecated in Java") - override fun onOptionsItemSelected(item: MenuItem): Boolean { - val contactsPreferenceActivity = activity as ContactsPreferenceActivity? + private fun addMenuProvider() { + requireActivity().addMenuProvider( + object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + } - if (item.itemId == android.R.id.home) { - if (showSidebar) { - if (contactsPreferenceActivity!!.isDrawerOpen) { - contactsPreferenceActivity.closeDrawer() - } else { - contactsPreferenceActivity.openDrawer() + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + val activity = getTypedActivity(ContactsPreferenceActivity::class.java) + + if (menuItem.itemId == android.R.id.home) { + if (showSidebar) { + if (activity?.isDrawerOpen == true) { + activity.closeDrawer() + } else { + activity?.openDrawer() + } + } else if (activity != null) { + activity.finish() + } else { + val settingsIntent = Intent(context, SettingsActivity::class.java) + startActivity(settingsIntent) + } + return true + } + + return false } - } else if (contactsPreferenceActivity != null) { - contactsPreferenceActivity.finish() + }, + viewLifecycleOwner, + Lifecycle.State.RESUMED + ) + } + + // region permission handling + private fun checkAndAskForContactsReadPermission(): Boolean { + return if (checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { + true + } else { + requestContactsPermissionLauncher.launch(Manifest.permission.READ_CONTACTS) + false + } + } + + private fun checkAndAskForCalendarReadPermission(): Boolean { + return if (checkCalendarBackupPermission(requireActivity())) { + true + } else { + requestCalendarPermissionLauncher.launch( + arrayOf( + Manifest.permission.READ_CALENDAR, + Manifest.permission.WRITE_CALENDAR + ) + ) + false + } + } + + private val requestContactsPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + isContactsBackupEnabled = true } else { - val settingsIntent = Intent(context, SettingsActivity::class.java) - startActivity(settingsIntent) + binding.contacts.setOnCheckedChangeListener(null) + binding.contacts.isChecked = false + binding.contacts.setOnCheckedChangeListener(contactsCheckedListener) } - return true + setBackupNowButtonVisibility() + setAutomaticBackup(binding.dailyBackup.isChecked) } - return super.onOptionsItemSelected(item) - } + private val requestCalendarPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> + val readGranted = permissions[Manifest.permission.READ_CALENDAR] == true + val writeGranted = permissions[Manifest.permission.WRITE_CALENDAR] == true + + if (readGranted && writeGranted) { + isCalendarBackupEnabled = true + } else { + binding.calendar.setOnCheckedChangeListener(null) + binding.calendar.isChecked = false + binding.calendar.setOnCheckedChangeListener(calendarCheckedListener) + } + setBackupNowButtonVisibility() + setAutomaticBackup(binding.dailyBackup.isChecked) + } + // endregion @Deprecated("Deprecated in Java") @Suppress("NestedBlockDepth") @@ -398,45 +465,27 @@ class BackupFragment : } private fun backupNow() { + val activity = getTypedActivity(ContactsPreferenceActivity::class.java) ?: return + val user = activity.user?.takeIf { it.isPresent }?.get() ?: return + if (isContactsBackupEnabled && checkContactBackupPermission()) { - startContactsBackupJob() + backgroundJobManager.startImmediateContactsBackup(user) } + if (showCalendarBackup && isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())) { - startCalendarBackupJob() + backgroundJobManager.startImmediateCalendarBackup(user) } + DisplayUtils.showSnackMessage( - requireView().findViewById(R.id.contacts_linear_layout), + this, R.string.contacts_preferences_backup_scheduled ) } - private fun startContactsBackupJob() { - val activity = activity as ContactsPreferenceActivity? - if (activity != null) { - val optionalUser = activity.user - if (optionalUser.isPresent) { - backgroundJobManager.startImmediateContactsBackup(optionalUser.get()) - } - } - } - - private fun startCalendarBackupJob() { - val activity = activity as ContactsPreferenceActivity? - if (activity != null) { - val optionalUser = activity.user - if (optionalUser.isPresent) { - backgroundJobManager.startImmediateCalendarBackup(optionalUser.get()) - } - } - } - private fun setAutomaticBackup(enabled: Boolean) { - val activity = activity as ContactsPreferenceActivity? ?: return - val optionalUser = activity.user - if (!optionalUser.isPresent) { - return - } - val user = optionalUser.get() + val activity = getTypedActivity(ContactsPreferenceActivity::class.java) ?: return + val user = activity.user?.takeIf { it.isPresent }?.get() ?: return + if (enabled) { if (isContactsBackupEnabled) { Log_OC.d(TAG, "Scheduling contacts backup job") @@ -464,43 +513,6 @@ class BackupFragment : ) } - private fun checkAndAskForContactsReadPermission(): Boolean { - val contactsPreferenceActivity = activity as ContactsPreferenceActivity? - - // check permissions - return if (checkSelfPermission(contactsPreferenceActivity!!, Manifest.permission.READ_CONTACTS)) { - true - } else { - // No explanation needed, request the permission. - ActivityCompat.requestPermissions( - requireActivity(), - arrayOf(Manifest.permission.READ_CONTACTS), - PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC - ) - false - } - } - - private fun checkAndAskForCalendarReadPermission(): Boolean { - val contactsPreferenceActivity = activity as ContactsPreferenceActivity? - - // check permissions - return if (contactsPreferenceActivity?.let { checkCalendarBackupPermission(it) } == true) { - true - } else { - // No explanation needed, request the permission. - ActivityCompat.requestPermissions( - requireActivity(), - arrayOf( - Manifest.permission.READ_CALENDAR, - Manifest.permission.WRITE_CALENDAR - ), - PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC - ) - false - } - } - private fun checkCalendarBackupPermission(context: Context): Boolean = checkSelfPermission(context, Manifest.permission.READ_CALENDAR) && checkSelfPermission( @@ -660,12 +672,10 @@ class BackupFragment : const val PREFERENCE_CALENDAR_BACKUP_ENABLED = "PREFERENCE_CALENDAR_BACKUP_ENABLED" @JvmStatic - fun create(showSidebar: Boolean): BackupFragment { - val fragment = BackupFragment() - val bundle = Bundle() - bundle.putBoolean(ARG_SHOW_SIDEBAR, showSidebar) - fragment.arguments = bundle - return fragment + fun create(showSidebar: Boolean): BackupFragment = BackupFragment().apply { + arguments = Bundle().apply { + putBoolean(ARG_SHOW_SIDEBAR, showSidebar) + } } } } From 6ca4cf610e1e25f27801501791af40a8447da263 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 11 May 2026 14:30:38 +0200 Subject: [PATCH 2/3] fix(backup-fragment): lifecycle, permission management Signed-off-by: alperozturk96 --- .../fragment/contactsbackup/BackupFragment.kt | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt index c3fd881ace9f..18011f660461 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt @@ -410,60 +410,6 @@ class BackupFragment : } // endregion - @Deprecated("Deprecated in Java") - @Suppress("NestedBlockDepth") - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - - if (requestCode == PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC) { - for (index in permissions.indices) { - if (Manifest.permission.READ_CONTACTS.equals(permissions[index], ignoreCase = true)) { - if (grantResults[index] >= 0) { - // if approved, exit for loop - isContactsBackupEnabled = true - break - } - - // if not accepted, disable again - binding.contacts.setOnCheckedChangeListener(null) - binding.contacts.isChecked = false - binding.contacts.setOnCheckedChangeListener(contactsCheckedListener) - } - } - } - if (requestCode == PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC) { - var readGranted = false - var writeGranted = false - for (index in permissions.indices) { - if (Manifest.permission.WRITE_CALENDAR.equals( - permissions[index], - ignoreCase = true - ) && - grantResults[index] >= 0 - ) { - writeGranted = true - } else if (Manifest.permission.READ_CALENDAR.equals( - permissions[index], - ignoreCase = true - ) && - grantResults[index] >= 0 - ) { - readGranted = true - } - } - if (!readGranted || !writeGranted) { - // if not accepted, disable again - binding.calendar.setOnCheckedChangeListener(null) - binding.calendar.isChecked = false - binding.calendar.setOnCheckedChangeListener(calendarCheckedListener) - } else { - isCalendarBackupEnabled = true - } - } - setBackupNowButtonVisibility() - setAutomaticBackup(binding.dailyBackup.isChecked) - } - private fun backupNow() { val activity = getTypedActivity(ContactsPreferenceActivity::class.java) ?: return val user = activity.user?.takeIf { it.isPresent }?.get() ?: return From 0d7b3ff02b38ca40d03d1a117a4abb23c1fc3824 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 11 May 2026 14:42:54 +0200 Subject: [PATCH 3/3] fix(backup-fragment): lifecycle, permission management Signed-off-by: alperozturk96 --- .../fragment/contactsbackup/BackupFragment.kt | 623 ++++++++---------- 1 file changed, 257 insertions(+), 366 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt index 18011f660461..28cbf6b10da7 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt @@ -1,6 +1,7 @@ /* * Nextcloud - Android Client * + * SPDX-FileCopyrightText: 2026 Alper Ozturk * SPDX-FileCopyrightText: 2023 TSI-mc * SPDX-FileCopyrightText: 2017 Mario Danic * SPDX-FileCopyrightText: 2017 Nextcloud GmbH @@ -44,7 +45,6 @@ import com.owncloud.android.ui.activity.SettingsActivity import com.owncloud.android.ui.fragment.FileFragment import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.MimeTypeUtil -import com.owncloud.android.utils.PermissionUtil import com.owncloud.android.utils.PermissionUtil.checkSelfPermission import com.owncloud.android.utils.theme.ThemeUtils import com.owncloud.android.utils.theme.ViewThemeUtils @@ -61,138 +61,128 @@ class BackupFragment : FileFragment(), OnDateSetListener, Injectable { - private lateinit var binding: BackupFragmentBinding - @Inject - lateinit var backgroundJobManager: BackgroundJobManager + @Inject lateinit var backgroundJobManager: BackgroundJobManager - @Inject - lateinit var themeUtils: ThemeUtils + @Inject lateinit var themeUtils: ThemeUtils - @Inject - lateinit var arbitraryDataProvider: ArbitraryDataProvider + @Inject lateinit var arbitraryDataProvider: ArbitraryDataProvider - @Inject - lateinit var viewThemeUtils: ViewThemeUtils + @Inject lateinit var viewThemeUtils: ViewThemeUtils + + private lateinit var binding: BackupFragmentBinding + private lateinit var user: User + private lateinit var contactsBackupFolderPath: String + private lateinit var calendarBackupFolderPath: String + private lateinit var contactsCheckedListener: CompoundButton.OnCheckedChangeListener + private lateinit var calendarCheckedListener: CompoundButton.OnCheckedChangeListener private var selectedDate: Calendar? = null private var calendarPickerOpen = false private var datePickerDialog: DatePickerDialog? = null - private lateinit var contactsCheckedListener: CompoundButton.OnCheckedChangeListener - private lateinit var calendarCheckedListener: CompoundButton.OnCheckedChangeListener - private lateinit var user: User private var showSidebar = true - - // flag to check if calendar backup should be shown and backup should be done or not private var showCalendarBackup = true + private var isCalendarBackupEnabled: Boolean get() = arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CALENDAR_BACKUP_ENABLED) - set(enabled) { - arbitraryDataProvider.storeOrUpdateKeyValue( - user.accountName, - PREFERENCE_CALENDAR_BACKUP_ENABLED, - enabled - ) - } + set(enabled) = arbitraryDataProvider.storeOrUpdateKeyValue( + user.accountName, + PREFERENCE_CALENDAR_BACKUP_ENABLED, + enabled + ) private var isContactsBackupEnabled: Boolean get() = arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CONTACTS_BACKUP_ENABLED) - set(enabled) { - arbitraryDataProvider.storeOrUpdateKeyValue( - user.accountName, - PREFERENCE_CONTACTS_BACKUP_ENABLED, - enabled - ) - } + set(enabled) = arbitraryDataProvider.storeOrUpdateKeyValue( + user.accountName, + PREFERENCE_CONTACTS_BACKUP_ENABLED, + enabled + ) - private lateinit var contactsBackupFolderPath: String - private lateinit var calendarBackupFolderPath: String + //region Lifecycle override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - // use grey as fallback for elements where custom theming is not available - if (themeUtils.themingEnabled(context)) { - requireContext().theme.applyStyle(R.style.FallbackThemingTheme, true) - } + if (themeUtils.themingEnabled(context)) requireContext().theme.applyStyle(R.style.FallbackThemingTheme, true) binding = BackupFragmentBinding.inflate(inflater, container, false) - contactsBackupFolderPath = getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR calendarBackupFolderPath = getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR - - val view: View = binding.root - - if (arguments != null) { - showSidebar = requireArguments().getBoolean(ARG_SHOW_SIDEBAR) - } - + showSidebar = arguments?.getBoolean(ARG_SHOW_SIDEBAR) ?: true showCalendarBackup = resources.getBoolean(R.bool.show_calendar_backup) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) val contactsPreferenceActivity = requireActivity() as ContactsPreferenceActivity user = contactsPreferenceActivity.user.orElseThrow { RuntimeException() } setupSwitches(user) - setupCheckListeners() setBackupNowButtonVisibility() - setOnClickListeners() - displayLastBackup(contactsPreferenceActivity) applyUserColorToActionBar(contactsPreferenceActivity) - setupDates(savedInstanceState) applyUserColor() addMenuProvider() - return view } + override fun onResume() { + super.onResume() + if (calendarPickerOpen) openDate(selectedDate) + (activity as? ContactsPreferenceActivity)?.let { refreshBackupFolder(it.storageManager) } + } + + override fun onStop() { + super.onStop() + datePickerDialog?.dismiss() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + val dialog = datePickerDialog?.takeIf { it.isShowing } ?: return + outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, true) + dialog.datePicker.let { + outState.putSerializable(KEY_CALENDAR_DATE, GregorianCalendar(it.year, it.month, it.dayOfMonth)) + } + } + + //endregion + + //region Setup + private fun setupSwitches(user: User) { binding.dailyBackup.isChecked = arbitraryDataProvider.getBooleanValue( user, ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP ) - binding.contacts.isChecked = isContactsBackupEnabled && checkContactBackupPermission() binding.calendar.isChecked = isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext()) binding.calendar.visibility = if (showCalendarBackup) View.VISIBLE else View.GONE } private fun setupCheckListeners() { - binding.dailyBackup.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - if (checkAndAskForContactsReadPermission()) { - setAutomaticBackup(isChecked) - } + binding.dailyBackup.setOnCheckedChangeListener { _, isChecked -> + if (checkAndAskForContactsReadPermission()) setAutomaticBackup(isChecked) + } + contactsCheckedListener = CompoundButton.OnCheckedChangeListener { _, isChecked -> + isContactsBackupEnabled = isChecked && checkAndAskForContactsReadPermission() + setBackupNowButtonVisibility() + setAutomaticBackup(binding.dailyBackup.isChecked) + } + calendarCheckedListener = CompoundButton.OnCheckedChangeListener { _, isChecked -> + isCalendarBackupEnabled = isChecked && checkAndAskForCalendarReadPermission() + setBackupNowButtonVisibility() + setAutomaticBackup(binding.dailyBackup.isChecked) } - - initContactsCheckedListener() binding.contacts.setOnCheckedChangeListener(contactsCheckedListener) - - initCalendarCheckedListener() binding.calendar.setOnCheckedChangeListener(calendarCheckedListener) } - private fun initContactsCheckedListener() { - contactsCheckedListener = - CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - isContactsBackupEnabled = isChecked && checkAndAskForContactsReadPermission() - setBackupNowButtonVisibility() - setAutomaticBackup(binding.dailyBackup.isChecked) - } - } - - private fun initCalendarCheckedListener() { - calendarCheckedListener = - CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> - isCalendarBackupEnabled = isChecked && checkAndAskForCalendarReadPermission() - setBackupNowButtonVisibility() - setAutomaticBackup(binding.dailyBackup.isChecked) - } - } - private fun setBackupNowButtonVisibility() { - binding.run { - backupNow.isEnabled = (contacts.isChecked || calendar.isChecked) - } + binding.backupNow.isEnabled = binding.contacts.isChecked || binding.calendar.isChecked } private fun setOnClickListeners() { @@ -205,19 +195,18 @@ class BackupFragment : user, ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP ) - if (lastBackupTimestamp == -1L) { binding.lastBackupWithDate.visibility = View.GONE - } else { - binding.lastBackupWithDate.text = String.format( - getString(R.string.last_backup), - DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp) - ) + return } + binding.lastBackupWithDate.text = getString( + R.string.last_backup, + DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp) + ) } - private fun applyUserColorToActionBar(contactsPreferenceActivity: ContactsPreferenceActivity) { - contactsPreferenceActivity.supportActionBar?.let { actionBar -> + private fun applyUserColorToActionBar(activity: ContactsPreferenceActivity) { + activity.supportActionBar?.let { actionBar -> actionBar.setDisplayHomeAsUpEnabled(true) viewThemeUtils.files.themeActionBar( requireContext(), @@ -228,127 +217,37 @@ class BackupFragment : } private fun setupDates(savedInstanceState: Bundle?) { - if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) { - savedInstanceState.getSerializableArgument(KEY_CALENDAR_DATE, Calendar::class.java)?.let { - selectedDate = it - } + savedInstanceState?.takeIf { it.getBoolean(KEY_CALENDAR_PICKER_OPEN, false) }?.let { + selectedDate = it.getSerializableArgument(KEY_CALENDAR_DATE, Calendar::class.java) calendarPickerOpen = true } } private fun applyUserColor() { - viewThemeUtils.androidx.colorSwitchCompat(binding.contacts) - viewThemeUtils.androidx.colorSwitchCompat(binding.calendar) - viewThemeUtils.androidx.colorSwitchCompat(binding.dailyBackup) - - viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.backupNow) - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker) - - viewThemeUtils.platform.colorTextView(binding.dataToBackUpTitle) - viewThemeUtils.platform.colorTextView(binding.backupSettingsTitle) - } - - override fun onResume() { - super.onResume() - - if (calendarPickerOpen) { - openDate(selectedDate) + viewThemeUtils.androidx.run { + colorSwitchCompat(binding.contacts) + colorSwitchCompat(binding.calendar) + colorSwitchCompat(binding.dailyBackup) } - (activity as? ContactsPreferenceActivity)?.let { - refreshBackupFolder(it.storageManager) + viewThemeUtils.material.run { + colorMaterialButtonPrimaryFilled(binding.backupNow) + colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker) } - } - - private fun refreshBackupFolder(storageManager: FileDataStorageManager) { - lifecycleScope.launch { - val backupFiles = listOf(calendarBackupFolderPath, contactsBackupFolderPath) - .mapNotNull { path -> storageManager.getFileByDecryptedRemotePath(path) } - .flatMap { folder -> fetchBackupFiles(folder, storageManager) } - .toMutableList() - .also { - it.sortWith(AlphanumComparator()) - } - withContext(Dispatchers.Main) { - binding.contactsDatepicker.setVisibleIf(backupFiles.isNotEmpty()) - } + viewThemeUtils.platform.run { + colorTextView(binding.dataToBackUpTitle) + colorTextView(binding.backupSettingsTitle) } } - /** - * Returns backup files from database - */ - private fun getBackupFiles(): List { - val contactsPreferenceActivity = activity as ContactsPreferenceActivity? - val storageManager = contactsPreferenceActivity?.storageManager ?: return listOf() - val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderPath) - val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderPath) - - val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false) - backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false)) - return backupFiles - } - - /** - * Refreshes the folder and returns updated backup files - */ - @Suppress("TooGenericExceptionCaught") - private suspend fun fetchBackupFiles(folder: OCFile, storageManager: FileDataStorageManager): List = - withContext(Dispatchers.IO) { - try { - val operation = RefreshFolderOperation( - folder, - System.currentTimeMillis(), - false, - false, - storageManager, - user, - context - ) - - @Suppress("DEPRECATION") - val result = operation.execute(user, context) - - if (result.isSuccess) { - Log_OC.d(TAG, "Backup files fetched") - storageManager.getFolderContent(folder, false) - } else { - Log_OC.d(TAG, "Backup files cannot be fetched") - listOf() - } - } catch (e: Exception) { - Log_OC.d(TAG, "Exception fetchBackupFiles: $e") - listOf() - } - } - private fun addMenuProvider() { requireActivity().addMenuProvider( object : MenuProvider { - override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { - } - + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) = Unit override fun onMenuItemSelected(menuItem: MenuItem): Boolean { - val activity = getTypedActivity(ContactsPreferenceActivity::class.java) - - if (menuItem.itemId == android.R.id.home) { - if (showSidebar) { - if (activity?.isDrawerOpen == true) { - activity.closeDrawer() - } else { - activity?.openDrawer() - } - } else if (activity != null) { - activity.finish() - } else { - val settingsIntent = Intent(context, SettingsActivity::class.java) - startActivity(settingsIntent) - } - return true - } - - return false + if (menuItem.itemId != android.R.id.home) return false + return handleHomeMenuAction(getTypedActivity(ContactsPreferenceActivity::class.java)) } }, viewLifecycleOwner, @@ -356,59 +255,18 @@ class BackupFragment : ) } - // region permission handling - private fun checkAndAskForContactsReadPermission(): Boolean { - return if (checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) { - true - } else { - requestContactsPermissionLauncher.launch(Manifest.permission.READ_CONTACTS) - false + private fun handleHomeMenuAction(activity: ContactsPreferenceActivity?): Boolean { + when { + showSidebar -> if (activity?.isDrawerOpen == true) activity.closeDrawer() else activity?.openDrawer() + activity != null -> activity.finish() + else -> startActivity(Intent(context, SettingsActivity::class.java)) } + return true } - private fun checkAndAskForCalendarReadPermission(): Boolean { - return if (checkCalendarBackupPermission(requireActivity())) { - true - } else { - requestCalendarPermissionLauncher.launch( - arrayOf( - Manifest.permission.READ_CALENDAR, - Manifest.permission.WRITE_CALENDAR - ) - ) - false - } - } + //endregion - private val requestContactsPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> - if (isGranted) { - isContactsBackupEnabled = true - } else { - binding.contacts.setOnCheckedChangeListener(null) - binding.contacts.isChecked = false - binding.contacts.setOnCheckedChangeListener(contactsCheckedListener) - } - setBackupNowButtonVisibility() - setAutomaticBackup(binding.dailyBackup.isChecked) - } - - private val requestCalendarPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> - val readGranted = permissions[Manifest.permission.READ_CALENDAR] == true - val writeGranted = permissions[Manifest.permission.WRITE_CALENDAR] == true - - if (readGranted && writeGranted) { - isCalendarBackupEnabled = true - } else { - binding.calendar.setOnCheckedChangeListener(null) - binding.calendar.isChecked = false - binding.calendar.setOnCheckedChangeListener(calendarCheckedListener) - } - setBackupNowButtonVisibility() - setAutomaticBackup(binding.dailyBackup.isChecked) - } - // endregion + //region Backup operations private fun backupNow() { val activity = getTypedActivity(ContactsPreferenceActivity::class.java) ?: return @@ -417,15 +275,10 @@ class BackupFragment : if (isContactsBackupEnabled && checkContactBackupPermission()) { backgroundJobManager.startImmediateContactsBackup(user) } - if (showCalendarBackup && isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())) { backgroundJobManager.startImmediateCalendarBackup(user) } - - DisplayUtils.showSnackMessage( - this, - R.string.contacts_preferences_backup_scheduled - ) + DisplayUtils.showSnackMessage(this, R.string.contacts_preferences_backup_scheduled) } private fun setAutomaticBackup(enabled: Boolean) { @@ -459,155 +312,195 @@ class BackupFragment : ) } - private fun checkCalendarBackupPermission(context: Context): Boolean = - checkSelfPermission(context, Manifest.permission.READ_CALENDAR) && - checkSelfPermission( - context, - Manifest.permission.WRITE_CALENDAR - ) - - private fun checkContactBackupPermission(): Boolean = - checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) - - private fun openCleanDate() { - if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) { - openDate(null) + private fun refreshBackupFolder(storageManager: FileDataStorageManager) { + lifecycleScope.launch { + val backupFiles = listOf(calendarBackupFolderPath, contactsBackupFolderPath) + .mapNotNull { path -> storageManager.getFileByDecryptedRemotePath(path) } + .flatMap { folder -> fetchBackupFiles(folder, storageManager) } + .sortedWith(AlphanumComparator()) + withContext(Dispatchers.Main) { + binding.contactsDatepicker.setVisibleIf(backupFiles.isNotEmpty()) + } } } - private fun openDate(savedDate: Calendar?) { - val contactsPreferenceActivity = activity as ContactsPreferenceActivity? - if (contactsPreferenceActivity == null) { - activity?.let { - DisplayUtils.showSnackMessage(it, R.string.error_choosing_date) + @Suppress("TooGenericExceptionCaught") + private suspend fun fetchBackupFiles(folder: OCFile, storageManager: FileDataStorageManager): List = + withContext(Dispatchers.IO) { + try { + @Suppress("DEPRECATION") + val result = RefreshFolderOperation( + folder, + System.currentTimeMillis(), + false, + false, + storageManager, + user, + context + ).execute(user, context) + if (result.isSuccess) storageManager.getFolderContent(folder, false) else emptyList() + } catch (e: Exception) { + Log_OC.d(TAG, "Exception fetchBackupFiles: $e") + emptyList() } - return } - val backupFiles = getBackupFiles().toMutableList() - backupFiles.sortBy { it.modificationTimestamp } + private fun getBackupFiles(): List { + val storageManager = (activity as? ContactsPreferenceActivity)?.storageManager ?: return emptyList() + val contactsFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderPath) + val calendarFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderPath) + return buildList { + addAll(storageManager.getFolderContent(contactsFolder, false)) + addAll(storageManager.getFolderContent(calendarFolder, false)) + } + } + + //endregion - if (backupFiles.isNotEmpty() && backupFiles.last() != null) { - val cal = savedDate ?: Calendar.getInstance() + //region Date picker - datePickerDialog = DatePickerDialog( - contactsPreferenceActivity, + private fun openCleanDate() { + if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) openDate(null) + } + + private fun openDate(savedDate: Calendar?) { + val contactsPreferenceActivity = activity as? ContactsPreferenceActivity ?: run { + activity?.let { DisplayUtils.showSnackMessage(it, R.string.error_choosing_date) } + return + } + val backupFiles = getBackupFiles().sortedBy { it.modificationTimestamp } + if (backupFiles.isEmpty()) { + DisplayUtils.showSnackMessage( this, - cal.get(Calendar.YEAR), - cal.get(Calendar.MONTH), - cal.get(Calendar.DAY_OF_MONTH) + R.string.contacts_preferences_something_strange_happened ) - - datePickerDialog?.apply { - datePicker.maxDate = backupFiles.last().modificationTimestamp - datePicker.minDate = backupFiles.first().modificationTimestamp - setOnDismissListener { selectedDate = null } - setTitle("") - show() - } + return + } + val cal = savedDate ?: Calendar.getInstance() + datePickerDialog = DatePickerDialog( + contactsPreferenceActivity, + this, + cal.get(Calendar.YEAR), + cal.get(Calendar.MONTH), + cal.get(Calendar.DAY_OF_MONTH) + ).apply { + datePicker.maxDate = backupFiles.last().modificationTimestamp + datePicker.minDate = backupFiles.first().modificationTimestamp + setOnDismissListener { selectedDate = null } + setTitle("") + show() viewThemeUtils.platform.colorTextButtons( - datePickerDialog!!.getButton(DatePickerDialog.BUTTON_NEGATIVE), - datePickerDialog!!.getButton(DatePickerDialog.BUTTON_POSITIVE) + getButton(DatePickerDialog.BUTTON_NEGATIVE), + getButton(DatePickerDialog.BUTTON_POSITIVE) ) - } else { + } + } + + @Suppress("ComplexMethod", "MagicNumber") + override fun onDateSet(view: DatePicker, year: Int, month: Int, dayOfMonth: Int) { + val contactsPreferenceActivity = activity as? ContactsPreferenceActivity ?: run { + activity?.let { DisplayUtils.showSnackMessage(it, R.string.error_choosing_date) } + return + } + selectedDate = GregorianCalendar(year, month, dayOfMonth) + val backupFiles = getBackupFiles() + val (start, end) = calculateDayRange(year, month, dayOfMonth) + val backupToRestore = collectFilesForRestore(backupFiles, start, end) + + if (backupToRestore.isEmpty()) { DisplayUtils.showSnackMessage( - requireView().findViewById(R.id.contacts_linear_layout), - R.string.contacts_preferences_something_strange_happened + this, + R.string.contacts_preferences_no_file_found ) + return } + val user = contactsPreferenceActivity.user.orElseThrow { RuntimeException() } + val fragment = BackupListFragment.newInstance(backupToRestore.toTypedArray(), user) + contactsPreferenceActivity.supportFragmentManager.beginTransaction() + .replace(R.id.frame_container, fragment, BackupListFragment.TAG) + .addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST) + .commit() } - override fun onStop() { - super.onStop() + @Suppress("MagicNumber") + private fun calculateDayRange(year: Int, month: Int, dayOfMonth: Int): Pair { + val date = Calendar.getInstance().apply { set(year, month, dayOfMonth) }.apply { + set(Calendar.HOUR, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 1) + set(Calendar.MILLISECOND, 0) + set(Calendar.AM_PM, Calendar.AM) + } - datePickerDialog?.dismiss() + val start = date.timeInMillis + + date.set(Calendar.HOUR, 23) + date.set(Calendar.MINUTE, 59) + date.set(Calendar.SECOND, 59) + + return start to date.timeInMillis } - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) + private fun collectFilesForRestore(backupFiles: List, start: Long, end: Long): List { + val inRange = backupFiles.filter { it.modificationTimestamp in (start + 1).. - outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, dialog.isShowing) + //endregion - if (dialog.isShowing) { - dialog.datePicker.let { - outState.putSerializable(KEY_CALENDAR_DATE, GregorianCalendar(it.year, it.month, it.dayOfMonth)) - } - } - } + //region Permissions + + private fun checkAndAskForContactsReadPermission(): Boolean { + if (checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) return true + requestContactsPermissionLauncher.launch(Manifest.permission.READ_CONTACTS) + return false } - @Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "ComplexMethod", "LongMethod", "MagicNumber") - override fun onDateSet(view: DatePicker, year: Int, month: Int, dayOfMonth: Int) { - val contactsPreferenceActivity = activity as ContactsPreferenceActivity? - if (contactsPreferenceActivity == null) { - activity?.let { - DisplayUtils.showSnackMessage(it, R.string.error_choosing_date) - } - return - } + private fun checkAndAskForCalendarReadPermission(): Boolean { + if (checkCalendarBackupPermission(requireActivity())) return true + requestCalendarPermissionLauncher.launch( + arrayOf(Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR) + ) + return false + } - selectedDate = GregorianCalendar(year, month, dayOfMonth) - val backupFiles = getBackupFiles() + private fun checkCalendarBackupPermission(context: Context): Boolean = + checkSelfPermission(context, Manifest.permission.READ_CALENDAR) && + checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR) - // find file with modification with date and time between 00:00 and 23:59 - // if more than one file exists, take oldest - val date = Calendar.getInstance() - date[year, month] = dayOfMonth - - // start - date[Calendar.HOUR] = 0 - date[Calendar.MINUTE] = 0 - date[Calendar.SECOND] = 1 - date[Calendar.MILLISECOND] = 0 - date[Calendar.AM_PM] = Calendar.AM - val start = date.timeInMillis + private fun checkContactBackupPermission(): Boolean = + checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) - // end - date[Calendar.HOUR] = 23 - date[Calendar.MINUTE] = 59 - date[Calendar.SECOND] = 59 - val end = date.timeInMillis - var contactsBackupToRestore: OCFile? = null - val calendarBackupsToRestore: MutableList = ArrayList() - for (file in backupFiles) { - if (file.modificationTimestamp in (start + 1).. = ArrayList() - if (contactsBackupToRestore != null) { - backupToRestore.add(contactsBackupToRestore) + private val requestContactsPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) isContactsBackupEnabled = true else resetSwitch(binding.contacts, contactsCheckedListener) + setBackupNowButtonVisibility() + setAutomaticBackup(binding.dailyBackup.isChecked) } - backupToRestore.addAll(calendarBackupsToRestore) - if (backupToRestore.isEmpty()) { - DisplayUtils.showSnackMessage( - requireView().findViewById(R.id.contacts_linear_layout), - R.string.contacts_preferences_no_file_found - ) - } else { - val user = contactsPreferenceActivity.user.orElseThrow { RuntimeException() } - val contactListFragment = BackupListFragment.newInstance(backupToRestore.toTypedArray(), user) - contactsPreferenceActivity.supportFragmentManager.beginTransaction() - .replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG) - .addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST) - .commit() + private val requestCalendarPermissionLauncher = + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> + val readGranted = permissions[Manifest.permission.READ_CALENDAR] == true + val writeGranted = permissions[Manifest.permission.WRITE_CALENDAR] == true + if (readGranted && writeGranted) { + isCalendarBackupEnabled = true + } else { + resetSwitch(binding.calendar, calendarCheckedListener) + } + setBackupNowButtonVisibility() + setAutomaticBackup(binding.dailyBackup.isChecked) } - } + + //endregion companion object { val TAG: String = BackupFragment::class.java.simpleName @@ -619,9 +512,7 @@ class BackupFragment : @JvmStatic fun create(showSidebar: Boolean): BackupFragment = BackupFragment().apply { - arguments = Bundle().apply { - putBoolean(ARG_SHOW_SIDEBAR, showSidebar) - } + arguments = Bundle().apply { putBoolean(ARG_SHOW_SIDEBAR, showSidebar) } } } }