Merge pull request #5328 from nextcloud/fix/not-enough-space-dialog

Added "not enough space dialog"
This commit is contained in:
Tobias Kaminsky 2020-02-05 12:14:40 +01:00 committed by GitHub
commit 0936945b3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 269 additions and 8 deletions

View file

@ -0,0 +1,77 @@
/*
*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2020 Tobias Kaminsky
* Copyright (C) 2020 Nextcloud GmbH
*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.dialog;
import android.Manifest;
import com.facebook.testing.screenshot.Screenshot;
import com.owncloud.android.AbstractIT;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import org.junit.Rule;
import org.junit.Test;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.rule.GrantPermissionRule;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
public class SyncFileNotEnoughSpaceDialogFragmentTest extends AbstractIT {
@Rule public IntentsTestRule<FileDisplayActivity> activityRule = new IntentsTestRule<>(FileDisplayActivity.class,
true,
false);
@Rule
public final GrantPermissionRule permissionRule = GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE);
@Test
public void showNotEnoughSpaceDialogForFolder() {
FileDisplayActivity test = activityRule.launchActivity(null);
OCFile ocFile = new OCFile("/Document/");
ocFile.setFileLength(5000000);
SyncFileNotEnoughSpaceDialogFragment dialog = SyncFileNotEnoughSpaceDialogFragment.newInstance(ocFile, 1000);
dialog.show(test.getListOfFilesFragment().getFragmentManager(), "1");
getInstrumentation().waitForIdleSync();
Screenshot.snap(dialog.getDialog().getWindow().getDecorView()).record();
}
@Test
public void showNotEnoughSpaceDialogForFile() {
FileDisplayActivity test = activityRule.launchActivity(null);
OCFile ocFile = new OCFile("/Video.mp4");
ocFile.setFileLength(1000000);
SyncFileNotEnoughSpaceDialogFragment dialog = SyncFileNotEnoughSpaceDialogFragment.newInstance(ocFile, 2000);
dialog.show(test.getListOfFilesFragment().getFragmentManager(), "2");
getInstrumentation().waitForIdleSync();
Screenshot.snap(dialog.getDialog().getWindow().getDecorView()).record();
}
}

View file

@ -32,7 +32,7 @@ public class ConfirmationDialogFragment extends DialogFragment {
final static String ARG_MESSAGE_RESOURCE_ID = "resource_id"; final static String ARG_MESSAGE_RESOURCE_ID = "resource_id";
final static String ARG_MESSAGE_ARGUMENTS = "string_array"; final static String ARG_MESSAGE_ARGUMENTS = "string_array";
private static final String ARG_TITLE_ID = "title_id"; final static String ARG_TITLE_ID = "title_id";
final static String ARG_POSITIVE_BTN_RES = "positive_btn_res"; final static String ARG_POSITIVE_BTN_RES = "positive_btn_res";
final static String ARG_NEUTRAL_BTN_RES = "neutral_btn_res"; final static String ARG_NEUTRAL_BTN_RES = "neutral_btn_res";

View file

@ -0,0 +1,136 @@
/*
* Nextcloud Android client application
*
* @author Kilian Périsset
* Copyright (C) 2020 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License (GPLv3),
* as published by the Free Software Foundation.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.dialog;
import android.app.Dialog;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.storage.StorageManager;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.ThemeUtils;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
/**
* Dialog requiring confirmation when a file/folder is too "big" to be synchronized/downloaded on device.
*/
public class SyncFileNotEnoughSpaceDialogFragment extends ConfirmationDialogFragment implements
ConfirmationDialogFragmentListener {
private static final String ARG_PASSED_FILE = "fragment_parent_caller";
private static final int REQUEST_CODE_STORAGE = 20;
private OCFile targetFile;
public static SyncFileNotEnoughSpaceDialogFragment newInstance(OCFile file, long availableDeviceSpace) {
Bundle args = new Bundle();
SyncFileNotEnoughSpaceDialogFragment frag = new SyncFileNotEnoughSpaceDialogFragment();
String properFileSize = DisplayUtils.bytesToHumanReadable(file.getFileLength());
String properDiskAvailableSpace = DisplayUtils.bytesToHumanReadable(availableDeviceSpace);
// Defining title, message and resources
args.putInt(ARG_TITLE_ID, R.string.sync_not_enough_space_dialog_title);
args.putInt(ARG_MESSAGE_RESOURCE_ID, R.string.sync_not_enough_space_dialog_placeholder);
args.putStringArray(ARG_MESSAGE_ARGUMENTS,
new String[] {
file.getFileName(),
properFileSize,
properDiskAvailableSpace});
args.putParcelable(ARG_PASSED_FILE, file);
// Defining buttons
if (file.isFolder()) {
args.putInt(ARG_POSITIVE_BTN_RES, R.string.sync_not_enough_space_dialog_action_choose);
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) {
args.putInt(ARG_NEGATIVE_BTN_RES, R.string.sync_not_enough_space_dialog_action_free_space);
}
args.putInt(ARG_NEUTRAL_BTN_RES, R.string.common_cancel);
frag.setArguments(args);
return frag;
}
@Override
public void onStart() {
super.onStart();
int color = ThemeUtils.primaryAccentColor(getActivity());
AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog != null) {
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(color);
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(color);
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(color);
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle arguments = getArguments();
if (arguments == null) {
throw new IllegalArgumentException("Arguments may not be null");
}
targetFile = arguments.getParcelable(ARG_PASSED_FILE);
setOnConfirmationListener(this);
return super.onCreateDialog(savedInstanceState);
}
/**
* (Only if file is a folder), will access the destination folder to allow user to choose what to synchronize
*/
@Override
public void onConfirmation(String callerTag) {
OCFileListFragment frag = (OCFileListFragment) getTargetFragment();
if (frag != null && targetFile != null) {
frag.onItemClicked(targetFile);
}
}
/**
* Will abort/cancel the process (is neutral to "hack" android button position ._.)
*/
@Override
public void onNeutral(String callerTag) {
// Nothing
}
/**
* Will access to storage manager in order to empty useless files
*/
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
@Override
public void onCancel(String callerTag) {
Intent storageIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
startActivityForResult(storageIntent, REQUEST_CODE_STORAGE);
}
}

View file

@ -83,12 +83,14 @@ import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
import com.owncloud.android.ui.dialog.RenameFileDialogFragment; import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment; import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragment;
import com.owncloud.android.ui.events.ChangeMenuEvent; import com.owncloud.android.ui.events.ChangeMenuEvent;
import com.owncloud.android.ui.events.CommentsEvent; import com.owncloud.android.ui.events.CommentsEvent;
import com.owncloud.android.ui.events.DummyDrawerEvent; import com.owncloud.android.ui.events.DummyDrawerEvent;
import com.owncloud.android.ui.events.EncryptionEvent; import com.owncloud.android.ui.events.EncryptionEvent;
import com.owncloud.android.ui.events.FavoriteEvent; import com.owncloud.android.ui.events.FavoriteEvent;
import com.owncloud.android.ui.events.SearchEvent; import com.owncloud.android.ui.events.SearchEvent;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface; import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface;
import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.ui.preview.PreviewImageFragment;
import com.owncloud.android.ui.preview.PreviewMediaFragment; import com.owncloud.android.ui.preview.PreviewMediaFragment;
@ -107,6 +109,7 @@ import org.parceler.Parcels;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -165,6 +168,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
private static final String DIALOG_CREATE_DOCUMENT = "DIALOG_CREATE_DOCUMENT"; private static final String DIALOG_CREATE_DOCUMENT = "DIALOG_CREATE_DOCUMENT";
private static final int SINGLE_SELECTION = 1; private static final int SINGLE_SELECTION = 1;
private static final int NOT_ENOUGH_SPACE_FRAG_REQUEST_CODE = 2;
@Inject AppPreferences preferences; @Inject AppPreferences preferences;
@Inject UserAccountManager accountManager; @Inject UserAccountManager accountManager;
@ -1114,7 +1118,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
} }
case R.id.action_download_file: case R.id.action_download_file:
case R.id.action_sync_file: { case R.id.action_sync_file: {
mContainerActivity.getFileOperationsHelper().syncFiles(checkedFiles); syncAndCheckFiles(checkedFiles);
exitSelectionMode(); exitSelectionMode();
return true; return true;
} }
@ -1720,6 +1724,32 @@ public class OCFileListFragment extends ExtendedListFragment implements
&& event.getUnsetType() != null; && event.getUnsetType() != null;
} }
private void syncAndCheckFiles(Collection<OCFile> files) {
for (OCFile file : files) {
// Get the remaining space on device (after file download)
long availableSpaceOnDevice = FileOperationsHelper.getAvailableSpaceOnDevice();
// Determine if space is enough to download the file
boolean isSpaceEnough = availableSpaceOnDevice > file.getFileLength();
if (isSpaceEnough) {
mContainerActivity.getFileOperationsHelper().syncFile(file);
} else {
showSpaceErrorDialog(file, availableSpaceOnDevice);
}
}
}
private void showSpaceErrorDialog(OCFile file, long availableSpaceOnDevice) {
SyncFileNotEnoughSpaceDialogFragment dialog =
SyncFileNotEnoughSpaceDialogFragment.newInstance(file, availableSpaceOnDevice);
dialog.setTargetFragment(this, NOT_ENOUGH_SPACE_FRAG_REQUEST_CODE);
if (getFragmentManager() != null) {
dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
}
}
@Override @Override
public boolean isLoading() { public boolean isLoading() {
return false; return false;

View file

@ -38,6 +38,7 @@ import android.content.pm.ResolveInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.os.StatFs;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@ -747,12 +748,6 @@ public class FileOperationsHelper {
sendShareFile(file, !file.canReshare()); sendShareFile(file, !file.canReshare());
} }
public void syncFiles(Collection<OCFile> files) {
for (OCFile file : files) {
syncFile(file);
}
}
public void sendCachedImage(OCFile file, String packageName, String activityName) { public void sendCachedImage(OCFile file, String packageName, String activityName) {
if (file != null) { if (file != null) {
Context context = MainApp.getAppContext(); Context context = MainApp.getAppContext();
@ -1043,5 +1038,18 @@ public class FileOperationsHelper {
return new SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US).format(new Date()) + ".jpg"; return new SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US).format(new Date()) + ".jpg";
} }
public static Long getAvailableSpaceOnDevice() {
StatFs stat = new StatFs(MainApp.getStoragePath());
long availableBytesOnDevice;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
availableBytesOnDevice = stat.getBlockSizeLong() * stat.getAvailableBlocksLong();
} else {
availableBytesOnDevice = (long) stat.getBlockSize() * (long) stat.getAvailableBlocks();
}
return availableBytesOnDevice;
}
} }

View file

@ -674,6 +674,10 @@ Attention la suppression est irréversible.</string>
<string name="sync_foreign_files_forgotten_explanation">Depuis la version 1.3.16, les fichiers envoyés depuis cet appareil sont copiés dans le dossier local %1$s pour éviter une perte de données lorsqu\'un même fichier est synchronisé avec plusieurs comptes.\n\nEn raison de cette modification, tous les fichiers envoyés avec des versions antérieures de cette application ont été copiés dans le dossier %2$s. Cependant, une erreur a empêché l\'achèvement de cette opération pendant la synchronisation du compte. Vous pouvez soit laisser les fichiers tels quels et supprimer le lien vers %3$s, soit déplacer les fichiers dans le dossier %1$s et garder le lien vers %4$s.\n\nCi-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxquels ils étaient liés.</string> <string name="sync_foreign_files_forgotten_explanation">Depuis la version 1.3.16, les fichiers envoyés depuis cet appareil sont copiés dans le dossier local %1$s pour éviter une perte de données lorsqu\'un même fichier est synchronisé avec plusieurs comptes.\n\nEn raison de cette modification, tous les fichiers envoyés avec des versions antérieures de cette application ont été copiés dans le dossier %2$s. Cependant, une erreur a empêché l\'achèvement de cette opération pendant la synchronisation du compte. Vous pouvez soit laisser les fichiers tels quels et supprimer le lien vers %3$s, soit déplacer les fichiers dans le dossier %1$s et garder le lien vers %4$s.\n\nCi-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxquels ils étaient liés.</string>
<string name="sync_foreign_files_forgotten_ticker">Certains fichiers locaux ont été ignorés</string> <string name="sync_foreign_files_forgotten_ticker">Certains fichiers locaux ont été ignorés</string>
<string name="sync_in_progress">Récupération de la version la plus récente du fichier.</string> <string name="sync_in_progress">Récupération de la version la plus récente du fichier.</string>
<string name="sync_not_enough_space_dialog_action_choose">Choisir quels fichiers synchroniser</string>
<string name="sync_not_enough_space_dialog_action_free_space">Libérer de l\'espace</string>
<string name="sync_not_enough_space_dialog_placeholder">%1$s est trop volumineux (%2$s) par rapport à l\'espace disponible sur votre appareil (%3$s).</string>
<string name="sync_not_enough_space_dialog_title">Plus assez d\'espace disponible</string>
<string name="sync_status_button">Bouton de statut de la synchronisation</string> <string name="sync_status_button">Bouton de statut de la synchronisation</string>
<string name="sync_string_files">Fichiers</string> <string name="sync_string_files">Fichiers</string>
<string name="synced_folder_settings_button">Bouton des paramètres</string> <string name="synced_folder_settings_button">Bouton des paramètres</string>

View file

@ -913,4 +913,10 @@
<string name="edit_rich_workspace">edit folder info</string> <string name="edit_rich_workspace">edit folder info</string>
<string name="create_new">Create new</string> <string name="create_new">Create new</string>
<string name="editor_placeholder" translatable="false">%1$s %2$s</string> <string name="editor_placeholder" translatable="false">%1$s %2$s</string>
<string name="sync_not_enough_space_dialog_action_choose">Choose what to sync</string>
<string name="sync_not_enough_space_dialog_action_free_space">Free up space</string>
<string name="sync_not_enough_space_dialog_placeholder">%1$s is %2$s, but there is only %3$s
available on device.</string>
<string name="sync_not_enough_space_dialog_title">Not enough space</string>
</resources> </resources>