diff --git a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png index 26c12663b702..f450422215b5 100644 Binary files a/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png and b/app/screenshots/gplay/debug/com.owncloud.android.ui.activity.ContactsPreferenceActivityIT_openContactsPreference.png differ diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.java deleted file mode 100644 index aad71fe6246f..000000000000 --- a/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.java +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Nextcloud Android client application - * - * @author Mario Danic - * @author TSI-mc - * Copyright (C) 2017 Mario Danic - * Copyright (C) 2017 Nextcloud GmbH. - * Copyright (C) 2023 TSI-mc - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package com.owncloud.android.ui.fragment.contactsbackup; - -import android.Manifest; -import android.app.DatePickerDialog; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CompoundButton; -import android.widget.DatePicker; -import android.widget.Toast; - -import com.nextcloud.client.account.User; -import com.nextcloud.client.di.Injectable; -import com.nextcloud.client.jobs.BackgroundJobManager; -import com.nextcloud.java.util.Optional; -import com.owncloud.android.R; -import com.owncloud.android.databinding.BackupFragmentBinding; -import com.owncloud.android.datamodel.ArbitraryDataProvider; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.operations.RefreshFolderOperation; -import com.owncloud.android.ui.activity.ContactsPreferenceActivity; -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.theme.ThemeUtils; -import com.owncloud.android.utils.theme.ViewThemeUtils; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -import javax.inject.Inject; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.ActionBar; -import androidx.fragment.app.Fragment; -import third_parties.daveKoeller.AlphanumComparator; - -import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP; -import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP; - -public class BackupFragment extends FileFragment implements DatePickerDialog.OnDateSetListener, Injectable { - public static final String TAG = BackupFragment.class.getSimpleName(); - private static final String ARG_SHOW_SIDEBAR = "SHOW_SIDEBAR"; - private static final String KEY_CALENDAR_PICKER_OPEN = "IS_CALENDAR_PICKER_OPEN"; - private static final String KEY_CALENDAR_DAY = "CALENDAR_DAY"; - private static final String KEY_CALENDAR_MONTH = "CALENDAR_MONTH"; - private static final String KEY_CALENDAR_YEAR = "CALENDAR_YEAR"; - - public static final String PREFERENCE_CONTACTS_BACKUP_ENABLED = "PREFERENCE_CONTACTS_BACKUP_ENABLED"; - public static final String PREFERENCE_CALENDAR_BACKUP_ENABLED = "PREFERENCE_CALENDAR_BACKUP_ENABLED"; - - - private BackupFragmentBinding binding; - - @Inject BackgroundJobManager backgroundJobManager; - @Inject ThemeUtils themeUtils; - - @Inject ArbitraryDataProvider arbitraryDataProvider; - @Inject ViewThemeUtils viewThemeUtils; - - private Date selectedDate; - private boolean calendarPickerOpen; - - private DatePickerDialog datePickerDialog; - - private CompoundButton.OnCheckedChangeListener dailyBackupCheckedChangeListener; - private CompoundButton.OnCheckedChangeListener contactsCheckedListener; - private CompoundButton.OnCheckedChangeListener calendarCheckedListener; - private User user; - private boolean showSidebar = true; - //flag to check if calendar backup should be shown and backup should be done or not - private boolean showCalendarBackup = true; - public static BackupFragment create(boolean showSidebar) { - BackupFragment fragment = new BackupFragment(); - Bundle bundle = new Bundle(); - bundle.putBoolean(ARG_SHOW_SIDEBAR, showSidebar); - fragment.setArguments(bundle); - return fragment; - } - - private boolean isCalendarBackupEnabled() { - return arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CALENDAR_BACKUP_ENABLED); - } - - private void setCalendarBackupEnabled(final boolean enabled) { - arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREFERENCE_CALENDAR_BACKUP_ENABLED, enabled); - } - - private boolean isContactsBackupEnabled() { - return arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CONTACTS_BACKUP_ENABLED); - } - - private void setContactsBackupEnabled(final boolean enabled) { - arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREFERENCE_CONTACTS_BACKUP_ENABLED, enabled); - } - - @Override - public View onCreateView(@NonNull final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - - // use grey as fallback for elements where custom theming is not available - if (themeUtils.themingEnabled(getContext())) { - getContext().getTheme().applyStyle(R.style.FallbackThemingTheme, true); - } - - binding = BackupFragmentBinding.inflate(inflater, container, false); - View view = binding.getRoot(); - - setHasOptionsMenu(true); - - if (getArguments() != null) { - showSidebar = getArguments().getBoolean(ARG_SHOW_SIDEBAR); - } - - showCalendarBackup = requireContext().getResources().getBoolean(R.bool.show_calendar_backup); - - final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity(); - user = contactsPreferenceActivity.getUser().orElseThrow(RuntimeException::new); - - ActionBar actionBar = contactsPreferenceActivity != null ? contactsPreferenceActivity.getSupportActionBar() : null; - - if (actionBar != null) { - actionBar.setDisplayHomeAsUpEnabled(true); - viewThemeUtils.files.themeActionBar(requireContext(), actionBar, - showCalendarBackup ? R.string.backup_title : R.string.contact_backup_title); - } - - - viewThemeUtils.androidx.colorSwitchCompat(binding.contacts); - viewThemeUtils.androidx.colorSwitchCompat(binding.calendar); - viewThemeUtils.androidx.colorSwitchCompat(binding.dailyBackup); - binding.dailyBackup.setChecked(arbitraryDataProvider.getBooleanValue(user, - PREFERENCE_CONTACTS_AUTOMATIC_BACKUP)); - - binding.contacts.setChecked(isContactsBackupEnabled() && checkContactBackupPermission()); - binding.calendar.setChecked(isCalendarBackupEnabled() && checkCalendarBackupPermission(getContext())); - - binding.calendar.setVisibility(showCalendarBackup ? View.VISIBLE : View.GONE); - - setupCheckListeners(); - - setBackupNowButtonVisibility(); - - binding.backupNow.setOnClickListener(v -> backupNow()); - - binding.contactsDatepicker.setOnClickListener(v -> openCleanDate()); - - // display last backup - Long lastBackupTimestamp = arbitraryDataProvider.getLongValue(user, PREFERENCE_CONTACTS_LAST_BACKUP); - - if (lastBackupTimestamp == -1) { - binding.lastBackupWithDate.setVisibility(View.GONE); - } else { - binding.lastBackupWithDate.setText( - String.format(getString(R.string.last_backup), - DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp))); - } - - if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) { - if (savedInstanceState.getInt(KEY_CALENDAR_YEAR, -1) != -1 && - savedInstanceState.getInt(KEY_CALENDAR_MONTH, -1) != -1 && - savedInstanceState.getInt(KEY_CALENDAR_DAY, -1) != -1) { - selectedDate = new Date(savedInstanceState.getInt(KEY_CALENDAR_YEAR), - savedInstanceState.getInt(KEY_CALENDAR_MONTH), savedInstanceState.getInt(KEY_CALENDAR_DAY)); - } - calendarPickerOpen = true; - } - - viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.backupNow); - viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker); - - viewThemeUtils.platform.colorTextView(binding.dataToBackUpTitle); - viewThemeUtils.platform.colorTextView(binding.backupSettingsTitle); - - return view; - } - - private void setupCheckListeners() { - dailyBackupCheckedChangeListener = (buttonView, isChecked) -> { - if (checkAndAskForContactsReadPermission()) { - setAutomaticBackup(isChecked); - } - }; - binding.dailyBackup.setOnCheckedChangeListener(dailyBackupCheckedChangeListener); - - - contactsCheckedListener = (buttonView, isChecked) -> { - if (isChecked) { - if (checkAndAskForContactsReadPermission()) { - setContactsBackupEnabled(true); - } - } else { - setContactsBackupEnabled(false); - } - setBackupNowButtonVisibility(); - setAutomaticBackup(binding.dailyBackup.isChecked()); - }; - binding.contacts.setOnCheckedChangeListener(contactsCheckedListener); - - calendarCheckedListener = (buttonView, isChecked) -> { - if (isChecked) { - if (checkAndAskForCalendarReadPermission()) { - setCalendarBackupEnabled(true); - } - } else { - setCalendarBackupEnabled(false); - } - setBackupNowButtonVisibility(); - setAutomaticBackup(binding.dailyBackup.isChecked()); - }; - binding.calendar.setOnCheckedChangeListener(calendarCheckedListener); - } - - private void setBackupNowButtonVisibility() { - if (binding.contacts.isChecked() || binding.calendar.isChecked()) { - binding.backupNow.setVisibility(View.VISIBLE); - } else { - binding.backupNow.setVisibility(View.INVISIBLE); - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - } - - @Override - public void onResume() { - super.onResume(); - - if (calendarPickerOpen) { - if (selectedDate != null) { - openDate(selectedDate); - } else { - openDate(null); - } - } - - final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity(); - if (contactsPreferenceActivity != null) { - String backupFolderPath = getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR; - refreshBackupFolder(backupFolderPath, - contactsPreferenceActivity.getApplicationContext(), - contactsPreferenceActivity.getStorageManager()); - } - } - - private void refreshBackupFolder(final String backupFolderPath, - final Context context, - final FileDataStorageManager storageManager) { - AsyncTask task = new AsyncTask() { - @Override - protected Boolean doInBackground(String... path) { - OCFile folder = storageManager.getFileByPath(path[0]); - - if (folder != null) { - RefreshFolderOperation operation = new RefreshFolderOperation(folder, System.currentTimeMillis(), - false, false, storageManager, user, context); - - RemoteOperationResult result = operation.execute(user, context); - return result.isSuccess(); - } else { - return Boolean.FALSE; - } - } - - @Override - protected void onPostExecute(Boolean result) { - if (result && binding != null) { - OCFile backupFolder = storageManager.getFileByPath(backupFolderPath); - - List backupFiles = storageManager - .getFolderContent(backupFolder, false); - - Collections.sort(backupFiles, new AlphanumComparator<>()); - - if (backupFiles == null || backupFiles.isEmpty()) { - binding.contactsDatepicker.setVisibility(View.INVISIBLE); - } else { - binding.contactsDatepicker.setVisibility(View.VISIBLE); - } - } - } - }; - - task.execute(backupFolderPath); - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity(); - - boolean retval; - switch (item.getItemId()) { - case android.R.id.home: - if (showSidebar) { - if (contactsPreferenceActivity.isDrawerOpen()) { - contactsPreferenceActivity.closeDrawer(); - } else { - contactsPreferenceActivity.openDrawer(); - } - } else if (getActivity() != null) { - getActivity().finish(); - } else { - Intent settingsIntent = new Intent(getContext(), SettingsActivity.class); - settingsIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(settingsIntent); - } - retval = true; - break; - - default: - retval = super.onOptionsItemSelected(item); - break; - } - return retval; - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - if (requestCode == PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC) { - for (int index = 0; index < permissions.length; index++) { - if (Manifest.permission.READ_CONTACTS.equalsIgnoreCase(permissions[index])) { - if (grantResults[index] >= 0) { - // if approved, exit for loop - setContactsBackupEnabled(true); - break; - } - - // if not accepted, disable again - binding.contacts.setOnCheckedChangeListener(null); - binding.contacts.setChecked(false); - binding.contacts.setOnCheckedChangeListener(contactsCheckedListener); - } - } - } - - if (requestCode == PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC) { - boolean readGranted = false; - boolean writeGranted = false; - for (int index = 0; index < permissions.length; index++) { - if (Manifest.permission.WRITE_CALENDAR.equalsIgnoreCase(permissions[index]) && grantResults[index] >= 0) { - writeGranted = true; - } else if (Manifest.permission.READ_CALENDAR.equalsIgnoreCase(permissions[index]) && grantResults[index] >= 0) { - readGranted = true; - } - } - if (!readGranted || !writeGranted) { - // if not accepted, disable again - binding.calendar.setOnCheckedChangeListener(null); - binding.calendar.setChecked(false); - binding.calendar.setOnCheckedChangeListener(calendarCheckedListener); - } else { - setCalendarBackupEnabled(true); - } - } - - setBackupNowButtonVisibility(); - setAutomaticBackup(binding.dailyBackup.isChecked()); - } - - public void backupNow() { - if (isContactsBackupEnabled() && checkContactBackupPermission()) { - startContactsBackupJob(); - } - - if (showCalendarBackup && isCalendarBackupEnabled() && checkCalendarBackupPermission(requireContext())) { - startCalendarBackupJob(); - } - - DisplayUtils.showSnackMessage(requireView().findViewById(R.id.contacts_linear_layout), - R.string.contacts_preferences_backup_scheduled); - } - - private void startContactsBackupJob() { - ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity(); - if (activity != null) { - Optional optionalUser = activity.getUser(); - if (optionalUser.isPresent()) { - backgroundJobManager.startImmediateContactsBackup(optionalUser.get()); - } - } - } - - private void startCalendarBackupJob() { - ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity(); - if (activity != null) { - Optional optionalUser = activity.getUser(); - if (optionalUser.isPresent()) { - backgroundJobManager.startImmediateCalendarBackup(optionalUser.get()); - } - } - } - - private void setAutomaticBackup(final boolean enabled) { - - final ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity(); - if (activity == null) { - return; - } - Optional optionalUser = activity.getUser(); - if (!optionalUser.isPresent()) { - return; - } - User user = optionalUser.get(); - if (enabled) { - if (isContactsBackupEnabled()) { - Log_OC.d(TAG, "Scheduling contacts backup job"); - backgroundJobManager.schedulePeriodicContactsBackup(user); - } else { - Log_OC.d(TAG, "Cancelling contacts backup job"); - backgroundJobManager.cancelPeriodicContactsBackup(user); - } - if (isCalendarBackupEnabled()) { - Log_OC.d(TAG, "Scheduling calendar backup job"); - backgroundJobManager.schedulePeriodicCalendarBackup(user); - } else { - Log_OC.d(TAG, "Cancelling calendar backup job"); - backgroundJobManager.cancelPeriodicCalendarBackup(user); - } - } else { - Log_OC.d(TAG, "Cancelling all backup jobs"); - backgroundJobManager.cancelPeriodicContactsBackup(user); - backgroundJobManager.cancelPeriodicCalendarBackup(user); - } - - arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), - PREFERENCE_CONTACTS_AUTOMATIC_BACKUP, - String.valueOf(enabled)); - } - - private boolean checkAndAskForContactsReadPermission() { - final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity(); - - // check permissions - if (PermissionUtil.checkSelfPermission(contactsPreferenceActivity, Manifest.permission.READ_CONTACTS)) { - return true; - } else { - // No explanation needed, request the permission. - requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, - PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC); - return false; - } - } - - private boolean checkAndAskForCalendarReadPermission() { - final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity(); - - // check permissions - if (checkCalendarBackupPermission(contactsPreferenceActivity)) { - return true; - } else { - // No explanation needed, request the permission. - requestPermissions(new String[]{Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR}, - PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC); - return false; - } - } - - private boolean checkCalendarBackupPermission(final Context context) { - return PermissionUtil.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) && PermissionUtil.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR); - } - - private boolean checkContactBackupPermission() { - return PermissionUtil.checkSelfPermission(getContext(), Manifest.permission.READ_CONTACTS); - } - - public void openCleanDate() { - if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) { - openDate(null); - } - } - - public void openDate(@Nullable Date savedDate) { - final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity(); - - if (contactsPreferenceActivity == null) { - Toast.makeText(getContext(), getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show(); - return; - } - - String contactsBackupFolderString = - getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR; - String calendarBackupFolderString = - getResources().getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR; - - FileDataStorageManager storageManager = contactsPreferenceActivity.getStorageManager(); - - OCFile contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString); - OCFile calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString); - - List backupFiles = storageManager.getFolderContent(contactsBackupFolder, false); - backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false)); - - Collections.sort(backupFiles, (o1, o2) -> { - return Long.compare(o1.getModificationTimestamp(), o2.getModificationTimestamp()); - }); - - Calendar cal = Calendar.getInstance(); - int year; - int month; - int day; - - if (savedDate == null) { - year = cal.get(Calendar.YEAR); - month = cal.get(Calendar.MONTH) + 1; - day = cal.get(Calendar.DAY_OF_MONTH); - } else { - year = savedDate.getYear(); - month = savedDate.getMonth(); - day = savedDate.getDay(); - } - - if (backupFiles.size() > 0 && backupFiles.get(backupFiles.size() - 1) != null) { - datePickerDialog = new DatePickerDialog(contactsPreferenceActivity, this, year, month, day); - datePickerDialog.getDatePicker().setMaxDate(backupFiles.get(backupFiles.size() - 1) - .getModificationTimestamp()); - datePickerDialog.getDatePicker().setMinDate(backupFiles.get(0).getModificationTimestamp()); - - datePickerDialog.setOnDismissListener(dialog -> selectedDate = null); - - datePickerDialog.setTitle(""); - datePickerDialog.show(); - - viewThemeUtils.platform.colorTextButtons(datePickerDialog.getButton(DatePickerDialog.BUTTON_NEGATIVE), - datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE)); - - // set background to transparent - datePickerDialog.getButton(DatePickerDialog.BUTTON_NEGATIVE).setBackgroundColor(0x00000000); - datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE).setBackgroundColor(0x00000000); - } else { - DisplayUtils.showSnackMessage(getView().findViewById(R.id.contacts_linear_layout), - R.string.contacts_preferences_something_strange_happened); - } - } - - @Override - public void onDestroyView() { - binding = null; - super.onDestroyView(); - } - - @Override - public void onStop() { - super.onStop(); - if (datePickerDialog != null) { - datePickerDialog.dismiss(); - } - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - if (datePickerDialog != null) { - outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, datePickerDialog.isShowing()); - - if (datePickerDialog.isShowing()) { - outState.putInt(KEY_CALENDAR_DAY, datePickerDialog.getDatePicker().getDayOfMonth()); - outState.putInt(KEY_CALENDAR_MONTH, datePickerDialog.getDatePicker().getMonth()); - outState.putInt(KEY_CALENDAR_YEAR, datePickerDialog.getDatePicker().getYear()); - } - } - } - - @Override - public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { - final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity(); - - if (contactsPreferenceActivity == null) { - Toast.makeText(getContext(), getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show(); - return; - } - - selectedDate = new Date(year, month, dayOfMonth); - - String contactsBackupFolderString = - getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR; - String calendarBackupFolderString = - getResources().getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR; - - FileDataStorageManager storageManager = contactsPreferenceActivity.getStorageManager(); - - OCFile contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString); - OCFile calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString); - - List backupFiles = storageManager.getFolderContent(contactsBackupFolder, false); - backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false)); - - // find file with modification with date and time between 00:00 and 23:59 - // if more than one file exists, take oldest - Calendar date = Calendar.getInstance(); - date.set(year, month, dayOfMonth); - - // start - date.set(Calendar.HOUR, 0); - date.set(Calendar.MINUTE, 0); - date.set(Calendar.SECOND, 1); - date.set(Calendar.MILLISECOND, 0); - date.set(Calendar.AM_PM, Calendar.AM); - long start = date.getTimeInMillis(); - - // end - date.set(Calendar.HOUR, 23); - date.set(Calendar.MINUTE, 59); - date.set(Calendar.SECOND, 59); - long end = date.getTimeInMillis(); - - OCFile contactsBackupToRestore = null; - List calendarBackupsToRestore = new ArrayList<>(); - - for (OCFile file : backupFiles) { - if (start < file.getModificationTimestamp() && end > file.getModificationTimestamp()) { - // contact - if (MimeTypeUtil.isVCard(file)) { - if (contactsBackupToRestore == null) { - contactsBackupToRestore = file; - } else if (contactsBackupToRestore.getModificationTimestamp() < file.getModificationTimestamp()) { - contactsBackupToRestore = file; - } - } - - // calendars - if (showCalendarBackup && MimeTypeUtil.isCalendar(file)) { - calendarBackupsToRestore.add(file); - } - } - } - - List backupToRestore = new ArrayList<>(); - - if (contactsBackupToRestore != null) { - backupToRestore.add(contactsBackupToRestore); - } - - backupToRestore.addAll(calendarBackupsToRestore); - - - if (backupToRestore.isEmpty()) { - DisplayUtils.showSnackMessage(getView().findViewById(R.id.contacts_linear_layout), - R.string.contacts_preferences_no_file_found); - } else { - final User user = contactsPreferenceActivity.getUser().orElseThrow(RuntimeException::new); - OCFile[] files = new OCFile[backupToRestore.size()]; - Fragment contactListFragment = BackupListFragment.newInstance(backupToRestore.toArray(files), user); - - contactsPreferenceActivity.getSupportFragmentManager(). - beginTransaction() - .replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG) - .addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST) - .commit(); - } - } -} 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 new file mode 100644 index 000000000000..e216bacaed7d --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupFragment.kt @@ -0,0 +1,726 @@ +/* + * Nextcloud Android client application + * + * @author Mario Danic + * @author TSI-mc + * Copyright (C) 2017 Mario Danic + * Copyright (C) 2017 Nextcloud GmbH. + * Copyright (C) 2023 TSI-mc + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.ui.fragment.contactsbackup + +import android.Manifest +import android.annotation.SuppressLint +import android.app.DatePickerDialog +import android.app.DatePickerDialog.OnDateSetListener +import android.content.Context +import android.content.Intent +import android.os.AsyncTask +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import android.widget.DatePicker +import android.widget.Toast +import com.nextcloud.client.account.User +import com.nextcloud.client.di.Injectable +import com.nextcloud.client.jobs.BackgroundJobManager +import com.owncloud.android.R +import com.owncloud.android.databinding.BackupFragmentBinding +import com.owncloud.android.datamodel.ArbitraryDataProvider +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.operations.RefreshFolderOperation +import com.owncloud.android.ui.activity.ContactsPreferenceActivity +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 +import third_parties.daveKoeller.AlphanumComparator +import java.util.Calendar +import java.util.Collections +import java.util.Date +import javax.inject.Inject + +@Suppress("TooManyFunctions") +class BackupFragment : FileFragment(), OnDateSetListener, Injectable { + private lateinit var binding: BackupFragmentBinding + + @JvmField + @Inject + var backgroundJobManager: BackgroundJobManager? = null + + @JvmField + @Inject + var themeUtils: ThemeUtils? = null + + @JvmField + @Inject + var arbitraryDataProvider: ArbitraryDataProvider? = null + + @JvmField + @Inject + var viewThemeUtils: ViewThemeUtils? = null + + private var selectedDate: Date? = null + private var calendarPickerOpen = false + private var datePickerDialog: DatePickerDialog? = null + private var contactsCheckedListener: CompoundButton.OnCheckedChangeListener? = null + private var calendarCheckedListener: CompoundButton.OnCheckedChangeListener? = null + private var user: User? = null + 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() = user?.let { arbitraryDataProvider?.getBooleanValue(it, PREFERENCE_CALENDAR_BACKUP_ENABLED) } ?: false + private set(enabled) { + arbitraryDataProvider!!.storeOrUpdateKeyValue( + user!!.accountName, + PREFERENCE_CALENDAR_BACKUP_ENABLED, + enabled + ) + } + + private var isContactsBackupEnabled: Boolean + get() = user?.let { arbitraryDataProvider?.getBooleanValue(it, PREFERENCE_CONTACTS_BACKUP_ENABLED) } ?: false + private set(enabled) { + arbitraryDataProvider!!.storeOrUpdateKeyValue( + user!!.accountName, + PREFERENCE_CONTACTS_BACKUP_ENABLED, + enabled + ) + } + + 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) == true) { + requireContext().theme.applyStyle(R.style.FallbackThemingTheme, true) + } + + binding = BackupFragmentBinding.inflate(inflater, container, false) + val view: View = binding.root + + setHasOptionsMenu(true) + + if (arguments != null) { + showSidebar = requireArguments().getBoolean(ARG_SHOW_SIDEBAR) + } + + showCalendarBackup = requireContext().resources.getBoolean(R.bool.show_calendar_backup) + + val contactsPreferenceActivity = activity as ContactsPreferenceActivity? + user = contactsPreferenceActivity?.user?.orElseThrow { RuntimeException() } + + setupSwitches(user) + + setupCheckListeners() + setBackupNowButtonVisibility() + + setOnClickListeners() + + contactsPreferenceActivity?.let { + displayLastBackup(it) + applyUserColorToActionBar(it) + } + + setupDates(savedInstanceState) + applyUserColor() + + return view + } + + private fun setupSwitches(user: User?) { + user?.let { + binding.dailyBackup.isChecked = arbitraryDataProvider?.getBooleanValue( + it, + ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP + ) ?: false + } + + 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) + } + } + + initContactsCheckedListener() + binding.contacts.setOnCheckedChangeListener(contactsCheckedListener) + + initCalendarCheckedListener() + binding.calendar.setOnCheckedChangeListener(calendarCheckedListener) + } + + private fun initContactsCheckedListener() { + contactsCheckedListener = + CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + if (isChecked) { + if (checkAndAskForContactsReadPermission()) { + isContactsBackupEnabled = true + } + } else { + isContactsBackupEnabled = false + } + setBackupNowButtonVisibility() + setAutomaticBackup(binding.dailyBackup.isChecked) + } + } + + private fun initCalendarCheckedListener() { + calendarCheckedListener = + CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + if (isChecked) { + if (checkAndAskForCalendarReadPermission()) { + isCalendarBackupEnabled = true + } + } else { + isCalendarBackupEnabled = false + } + setBackupNowButtonVisibility() + setAutomaticBackup(binding.dailyBackup.isChecked) + } + } + + private fun setBackupNowButtonVisibility() { + binding.backupNow.visibility = + if (binding.contacts.isChecked || binding.calendar.isChecked) View.VISIBLE else View.INVISIBLE + } + + private fun setOnClickListeners() { + binding.backupNow.setOnClickListener { backupNow() } + binding.contactsDatepicker.setOnClickListener { openCleanDate() } + } + + private fun displayLastBackup(contactsPreferenceActivity: ContactsPreferenceActivity) { + val lastBackupTimestamp = user?.let { + arbitraryDataProvider?.getLongValue( + it, + ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP + ) + } ?: -1L + + if (lastBackupTimestamp == -1L) { + binding.lastBackupWithDate.visibility = View.GONE + } else { + binding.lastBackupWithDate.text = String.format( + getString(R.string.last_backup), + DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp) + ) + } + } + + private fun applyUserColorToActionBar(contactsPreferenceActivity: ContactsPreferenceActivity) { + val actionBar = contactsPreferenceActivity.supportActionBar + + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true) + viewThemeUtils?.files?.themeActionBar( + requireContext(), + actionBar, + if (showCalendarBackup) R.string.backup_title else R.string.contact_backup_title + ) + } + } + + private fun setupDates(savedInstanceState: Bundle?) { + if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) { + if (savedInstanceState.getInt(KEY_CALENDAR_YEAR, -1) != -1 && savedInstanceState.getInt( + KEY_CALENDAR_MONTH, + -1 + ) != -1 && savedInstanceState.getInt( + KEY_CALENDAR_DAY, -1 + ) != -1 + ) { + val cal = Calendar.getInstance() + cal[Calendar.YEAR] = savedInstanceState.getInt(KEY_CALENDAR_YEAR) + cal[Calendar.MONTH] = savedInstanceState.getInt(KEY_CALENDAR_MONTH) + cal[Calendar.DAY_OF_MONTH] = savedInstanceState.getInt(KEY_CALENDAR_DAY) + selectedDate = cal.time + } + 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) { + if (selectedDate != null) { + openDate(selectedDate) + } else { + openDate(null) + } + } + + val contactsPreferenceActivity = activity as ContactsPreferenceActivity? + if (contactsPreferenceActivity != null) { + val backupFolderPath = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR + refreshBackupFolder( + backupFolderPath, + contactsPreferenceActivity.applicationContext, + contactsPreferenceActivity.storageManager + ) + } + } + + private fun refreshBackupFolder( + backupFolderPath: String, + context: Context, + storageManager: FileDataStorageManager + ) { + val task: AsyncTask = + @SuppressLint("StaticFieldLeak") + object : AsyncTask() { + @Deprecated("Deprecated in Java") + override fun doInBackground(vararg path: String): Boolean { + val folder = storageManager.getFileByPath(path[0]) + return if (folder != null) { + val operation = RefreshFolderOperation( + folder, + System.currentTimeMillis(), + false, + false, + storageManager, + user, + context + ) + val result = operation.execute(user, context) + result.isSuccess + } else { + java.lang.Boolean.FALSE + } + } + + @Deprecated("Deprecated in Java") + override fun onPostExecute(result: Boolean) { + if (result) { + val backupFolder = storageManager.getFileByPath(backupFolderPath) + val backupFiles = storageManager + .getFolderContent(backupFolder, false) + Collections.sort(backupFiles, AlphanumComparator()) + if (backupFiles == null || backupFiles.isEmpty()) { + binding.contactsDatepicker.visibility = View.INVISIBLE + } else { + binding.contactsDatepicker.visibility = View.VISIBLE + } + } + } + } + + task.execute(backupFolderPath) + } + + @Deprecated("Deprecated in Java") + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val contactsPreferenceActivity = activity as ContactsPreferenceActivity? + + val retval: Boolean + when (item.itemId) { + android.R.id.home -> { + if (showSidebar) { + if (contactsPreferenceActivity!!.isDrawerOpen) { + contactsPreferenceActivity.closeDrawer() + } else { + contactsPreferenceActivity.openDrawer() + } + } else if (activity != null) { + requireActivity().finish() + } else { + val settingsIntent = Intent(context, SettingsActivity::class.java) + settingsIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + startActivity(settingsIntent) + } + retval = true + } + + else -> retval = super.onOptionsItemSelected(item) + } + return retval + } + + @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() { + if (isContactsBackupEnabled && checkContactBackupPermission()) { + startContactsBackupJob() + } + if (showCalendarBackup && isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())) { + startCalendarBackupJob() + } + DisplayUtils.showSnackMessage( + requireView().findViewById(R.id.contacts_linear_layout), + 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() + if (enabled) { + if (isContactsBackupEnabled) { + Log_OC.d(TAG, "Scheduling contacts backup job") + backgroundJobManager?.schedulePeriodicContactsBackup(user) + } else { + Log_OC.d(TAG, "Cancelling contacts backup job") + backgroundJobManager?.cancelPeriodicContactsBackup(user) + } + if (isCalendarBackupEnabled) { + Log_OC.d(TAG, "Scheduling calendar backup job") + backgroundJobManager?.schedulePeriodicCalendarBackup(user) + } else { + Log_OC.d(TAG, "Cancelling calendar backup job") + backgroundJobManager?.cancelPeriodicCalendarBackup(user) + } + } else { + Log_OC.d(TAG, "Cancelling all backup jobs") + backgroundJobManager?.cancelPeriodicContactsBackup(user) + backgroundJobManager?.cancelPeriodicCalendarBackup(user) + } + arbitraryDataProvider?.storeOrUpdateKeyValue( + user.accountName, + ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP, + enabled.toString() + ) + } + + 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. + requestPermissions( + 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. + requestPermissions( + arrayOf( + Manifest.permission.READ_CALENDAR, + Manifest.permission.WRITE_CALENDAR + ), + PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC + ) + false + } + } + + private fun checkCalendarBackupPermission(context: Context): Boolean { + return checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) && checkSelfPermission( + context, Manifest.permission.WRITE_CALENDAR + ) + } + + private fun checkContactBackupPermission(): Boolean { + return checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS) + } + + private fun openCleanDate() { + if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) { + openDate(null) + } + } + + private fun openDate(savedDate: Date?) { + val contactsPreferenceActivity = activity as ContactsPreferenceActivity? + if (contactsPreferenceActivity == null) { + Toast.makeText(context, getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show() + return + } + + val contactsBackupFolderString = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR + val calendarBackupFolderString = resources.getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR + val storageManager = contactsPreferenceActivity.storageManager + val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString) + val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString) + + val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false) + backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false)) + backupFiles.sortWith { o1: OCFile?, o2: OCFile? -> + if (o1 != null && o2 != null) { + o1.modificationTimestamp.compareTo(o2.modificationTimestamp) + } else { + -1 + } + } + + val cal = Calendar.getInstance() + val year: Int + val month: Int + val day: Int + if (savedDate == null) { + year = cal[Calendar.YEAR] + month = cal[Calendar.MONTH] + 1 + day = cal[Calendar.DAY_OF_MONTH] + } else { + year = savedDate.year + month = savedDate.month + day = savedDate.day + } + if (backupFiles.size > 0 && backupFiles[backupFiles.size - 1] != null) { + datePickerDialog = DatePickerDialog(contactsPreferenceActivity, this, year, month, day) + datePickerDialog?.datePicker?.maxDate = backupFiles[backupFiles.size - 1]!! + .modificationTimestamp + datePickerDialog?.datePicker?.minDate = backupFiles[0]!!.modificationTimestamp + datePickerDialog?.setOnDismissListener { selectedDate = null } + datePickerDialog?.setTitle("") + datePickerDialog?.show() + + viewThemeUtils?.platform?.colorTextButtons( + datePickerDialog!!.getButton(DatePickerDialog.BUTTON_NEGATIVE), + datePickerDialog!!.getButton(DatePickerDialog.BUTTON_POSITIVE) + ) + + // set background to transparent + datePickerDialog?.getButton(DatePickerDialog.BUTTON_NEGATIVE)?.setBackgroundColor(0x00000000) + datePickerDialog?.getButton(DatePickerDialog.BUTTON_POSITIVE)?.setBackgroundColor(0x00000000) + } else { + DisplayUtils.showSnackMessage( + requireView().findViewById(R.id.contacts_linear_layout), + R.string.contacts_preferences_something_strange_happened + ) + } + } + + override fun onStop() { + super.onStop() + + datePickerDialog?.dismiss() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + + datePickerDialog?.let { + outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, it.isShowing) + + if (it.isShowing) { + outState.putInt(KEY_CALENDAR_DAY, it.datePicker.dayOfMonth) + outState.putInt(KEY_CALENDAR_MONTH, it.datePicker.month) + outState.putInt(KEY_CALENDAR_YEAR, it.datePicker.year) + } + } + } + + @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) { + Toast.makeText(context, getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show() + return + } + + selectedDate = Date(year, month, dayOfMonth) + val contactsBackupFolderString = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR + val calendarBackupFolderString = resources.getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR + val storageManager = contactsPreferenceActivity.storageManager + val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString) + val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString) + val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false) + backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false)) + + // 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 + + // 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 (start < file.modificationTimestamp && end > file.modificationTimestamp) { + // contact + if (MimeTypeUtil.isVCard(file)) { + if (contactsBackupToRestore == null) { + contactsBackupToRestore = file + } else if (contactsBackupToRestore.modificationTimestamp < file.modificationTimestamp) { + contactsBackupToRestore = file + } + } + + // calendars + if (showCalendarBackup && MimeTypeUtil.isCalendar(file)) { + calendarBackupsToRestore.add(file) + } + } + } + val backupToRestore: MutableList = ArrayList() + if (contactsBackupToRestore != null) { + backupToRestore.add(contactsBackupToRestore) + } + 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 files: Array = arrayOfNulls(backupToRestore.size) + + val contactListFragment = BackupListFragment.newInstance(files, user) + + contactsPreferenceActivity.supportFragmentManager.beginTransaction() + .replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG) + .addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST) + .commit() + } + } + + companion object { + val TAG = BackupFragment::class.java.simpleName + private const val ARG_SHOW_SIDEBAR = "SHOW_SIDEBAR" + private const val KEY_CALENDAR_PICKER_OPEN = "IS_CALENDAR_PICKER_OPEN" + private const val KEY_CALENDAR_DAY = "CALENDAR_DAY" + private const val KEY_CALENDAR_MONTH = "CALENDAR_MONTH" + private const val KEY_CALENDAR_YEAR = "CALENDAR_YEAR" + const val PREFERENCE_CONTACTS_BACKUP_ENABLED = "PREFERENCE_CONTACTS_BACKUP_ENABLED" + 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 + } + } +} diff --git a/app/src/main/res/layout/backup_fragment.xml b/app/src/main/res/layout/backup_fragment.xml index d5b873b8878f..a8467ef4c1d5 100644 --- a/app/src/main/res/layout/backup_fragment.xml +++ b/app/src/main/res/layout/backup_fragment.xml @@ -17,8 +17,8 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . --> - - - - + android:textSize="@dimen/two_line_primary_text_size" /> - - - - + android:gravity="end" + android:orientation="horizontal"> + android:theme="@style/Widget.Material3.Button.IconButton.Filled" /> diff --git a/app/src/main/res/values/dims.xml b/app/src/main/res/values/dims.xml index 3430bee41c38..6b3fe89a1e9c 100644 --- a/app/src/main/res/values/dims.xml +++ b/app/src/main/res/values/dims.xml @@ -133,6 +133,8 @@ 16sp 18sp 24dp + 160dp + 4 12dp 50dp diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index 31824795b1a7..2c0dfa2b0d8a 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 80 warnings + Lint Report: 79 warnings