mirror of
https://github.com/nextcloud/android.git
synced 2024-11-22 21:25:35 +03:00
Merge master
Signed-off-by: alperozturk <alper_ozturk@proton.me>
This commit is contained in:
commit
b39e7f7f13
8 changed files with 750 additions and 714 deletions
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
|
@ -12,6 +12,10 @@ on:
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: code-ql-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<String, Integer, Boolean> task = new AsyncTask<String, Integer, Boolean>() {
|
||||
@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<OCFile> 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<User> optionalUser = activity.getUser();
|
||||
if (optionalUser.isPresent()) {
|
||||
backgroundJobManager.startImmediateContactsBackup(optionalUser.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startCalendarBackupJob() {
|
||||
ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
|
||||
if (activity != null) {
|
||||
Optional<User> 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<User> 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<OCFile> 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<OCFile> 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<OCFile> 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<OCFile> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<String, Int, Boolean> =
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object : AsyncTask<String, Int, Boolean>() {
|
||||
@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<String>, 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<View>(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<View>(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<OCFile> = 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<OCFile> = ArrayList()
|
||||
if (contactsBackupToRestore != null) {
|
||||
backupToRestore.add(contactsBackupToRestore)
|
||||
}
|
||||
backupToRestore.addAll(calendarBackupsToRestore)
|
||||
if (backupToRestore.isEmpty()) {
|
||||
DisplayUtils.showSnackMessage(
|
||||
requireView().findViewById<View>(R.id.contacts_linear_layout),
|
||||
R.string.contacts_preferences_no_file_found
|
||||
)
|
||||
} else {
|
||||
val user = contactsPreferenceActivity.user.orElseThrow { RuntimeException() }
|
||||
val files: Array<OCFile?> = 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,8 +17,8 @@
|
|||
You should have received a copy of the GNU Affero General Public
|
||||
License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/contacts_linear_layout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -30,14 +30,14 @@
|
|||
android:layout_margin="@dimen/standard_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/data_to_back_up_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/data_to_back_up"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/contacts"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -46,22 +46,21 @@
|
|||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/two_line_primary_text_size" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/calendar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:text="@string/calendar"
|
||||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/two_line_primary_text_size"
|
||||
/>
|
||||
android:textSize="@dimen/two_line_primary_text_size" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/backup_settings_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -73,7 +72,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp">
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/daily_backup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -89,14 +88,14 @@
|
|||
android:orientation="vertical"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/daily_backup"
|
||||
android:textColor="@color/text_color"
|
||||
android:textSize="@dimen/two_line_primary_text_size" />
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/last_backup_with_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -113,32 +112,27 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/standard_margin"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="1.0">
|
||||
android:gravity="end"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/contacts_datepicker"
|
||||
style="@style/OutlinedButton"
|
||||
android:layout_width="0dp"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
android:layout_width="@dimen/backup_button_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="0.5"
|
||||
android:minHeight="@dimen/minimum_size_for_touchable_area"
|
||||
android:text="@string/restore_backup"
|
||||
android:visibility="invisible"
|
||||
app:cornerRadius="@dimen/button_corner_radius"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/backup_now"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="@dimen/backup_button_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/standard_half_margin"
|
||||
android:layout_weight="0.5"
|
||||
android:minHeight="@dimen/minimum_size_for_touchable_area"
|
||||
android:text="@string/contacts_backup_button"
|
||||
android:theme="@style/Button.Primary"
|
||||
app:cornerRadius="@dimen/button_corner_radius" />
|
||||
android:theme="@style/Widget.Material3.Button.IconButton.Filled" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -660,6 +660,7 @@
|
|||
<string name="share_no_password_title">Đặt mật khẩu</string>
|
||||
<string name="share_password_title">Mật khẩu được bảo vệ</string>
|
||||
<string name="share_permission_can_edit">Có thể chỉnh sửa</string>
|
||||
<string name="share_permission_file_drop">Thả file</string>
|
||||
<string name="share_permission_view_only">Chỉ xem</string>
|
||||
<string name="share_permissions">Quyền kho</string>
|
||||
<string name="share_remote_clarification">%1$s (từ xa)</string>
|
||||
|
|
|
@ -133,6 +133,8 @@
|
|||
<dimen name="bottom_sheet_text_size">16sp</dimen>
|
||||
<dimen name="permission_dialog_text_size">18sp</dimen>
|
||||
<dimen name="button_corner_radius">24dp</dimen>
|
||||
<dimen name="backup_button_width">160dp</dimen>
|
||||
|
||||
<integer name="media_grid_width">4</integer>
|
||||
<dimen name="account_action_button_margin">12dp</dimen>
|
||||
<dimen name="account_action_button_height">50dp</dimen>
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
DO NOT TOUCH; GENERATED BY DRONE
|
||||
<span class="mdl-layout-title">Lint Report: 80 warnings</span>
|
||||
<span class="mdl-layout-title">Lint Report: 79 warnings</span>
|
||||
|
|
Loading…
Reference in a new issue