diff --git a/.gitignore b/.gitignore index 5654a3c8dbec..0d18cdf6bb3a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,5 @@ tests/proguard-project.txt .gradle .idea *.iml -build \ No newline at end of file +build +/gradle.properties diff --git a/AndroidManifest.xml b/AndroidManifest.xml index e81f12e0032c..83435a4439e7 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -77,8 +77,10 @@ + @@ -122,6 +124,7 @@ android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> + @@ -133,6 +136,10 @@ android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter_files" /> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/folder_sync_layout.xml b/res/layout/folder_sync_layout.xml new file mode 100644 index 000000000000..cfcb67fd67a9 --- /dev/null +++ b/res/layout/folder_sync_layout.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/folder_sync_settings_layout.xml b/res/layout/folder_sync_settings_layout.xml new file mode 100644 index 000000000000..a751ee32ac94 --- /dev/null +++ b/res/layout/folder_sync_settings_layout.xml @@ -0,0 +1,331 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/grid_sync_item.xml b/res/layout/grid_sync_item.xml new file mode 100644 index 000000000000..1a5730b4a05f --- /dev/null +++ b/res/layout/grid_sync_item.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/drawer_menu.xml b/res/menu/drawer_menu.xml index 0679e5e987a1..8e6dc6c91774 100644 --- a/res/menu/drawer_menu.xml +++ b/res/menu/drawer_menu.xml @@ -37,6 +37,11 @@ android:id="@+id/nav_uploads" android:icon="@drawable/ic_uploads" android:title="@string/drawer_item_uploads_list"/> + 32dp + 4 diff --git a/res/values-sw600dp/dims.xml b/res/values-sw600dp/dims.xml new file mode 100644 index 000000000000..04d90bce3503 --- /dev/null +++ b/res/values-sw600dp/dims.xml @@ -0,0 +1,23 @@ + + + + 6 + diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 33c4f0f81184..99bee96986b5 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -16,9 +16,9 @@ - NOTHING - MOVE - DELETE + LOCAL_BEHAVIOUR_FORGET + LOCAL_BEHAVIOUR_MOVE + LOCAL_BEHAVIOUR_DELETE diff --git a/res/values/dims.xml b/res/values/dims.xml index 80f26a7e6d35..068ea6df6ad2 100644 --- a/res/values/dims.xml +++ b/res/values/dims.xml @@ -99,4 +99,6 @@ 12dip 35dp 12dp + 2dp + 4 diff --git a/res/values/strings.xml b/res/values/strings.xml index 9dd9efc6f369..359597a328ec 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -106,6 +106,7 @@ Cancel sync Cancel Back + Save Save & exit Error Loading … @@ -316,6 +317,8 @@ %1$s could not be copied to %2$s local folder Instant upload folder + Local folder + Remote folder Use subfolders Store in subfolders based on year and month @@ -500,6 +503,7 @@ Search This is a Nextcloud feature, please update. Learn more + Auto upload Participate Help us testing Found a bug? Something is odd? @@ -518,6 +522,13 @@ Contribute as a developer, see <a href="https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md">CONTRIBUTING.md</a> for details Move to… Copy to… + Choose folder… + Loading folders… + No media folders found. + Auto upload preferences + Settings + Instant upload has been revamped completely. Please see the main menu and re-configure your auto upload. Sorry for the inconvenience.\n\nEnjoy the new and extended auto upload capabilities! + For %1$s disable + show info + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && + (PreferenceManager.instantPictureUploadEnabled(this) || + PreferenceManager.instantPictureUploadEnabled(this))) { + + // remove legacy shared preferences + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit(); + editor.remove("instant_uploading") + .remove("instant_video_uploading") + .remove("instant_upload_path") + .remove("instant_upload_path_use_subfolders") + .remove("instant_upload_on_wifi") + .remove("instant_upload_on_charging") + .remove("instant_video_upload_path") + .remove("instant_video_upload_path_use_subfolders") + .remove("instant_video_upload_on_wifi") + .remove("instant_video_uploading") + .remove("instant_video_upload_on_charging") + .remove("prefs_instant_behaviour").apply(); + + // show info pop-up + new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog) + .setTitle(R.string.drawer_folder_sync) + .setMessage(R.string.folder_sync_new_info) + .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // show instant upload + Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class); + dialog.dismiss(); + startActivity(folderSyncIntent); + } + }) + .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .setIcon(R.drawable.ic_cloud_upload) + .show(); + } } @Override diff --git a/src/com/owncloud/android/ui/activity/FolderSyncActivity.java b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java new file mode 100644 index 000000000000..9ea678a1ec10 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/FolderSyncActivity.java @@ -0,0 +1,413 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.widget.GridLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.MenuItem; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.MediaFolder; +import com.owncloud.android.datamodel.MediaProvider; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.datamodel.SyncedFolder; +import com.owncloud.android.datamodel.SyncedFolderDisplayItem; +import com.owncloud.android.datamodel.SyncedFolderProvider; +import com.owncloud.android.ui.adapter.FolderSyncAdapter; +import com.owncloud.android.ui.decoration.MediaGridItemDecoration; +import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment; +import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimerTask; + +import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID; + +/** + * Activity displaying all auto-synced folders and/or instant upload media folders. + */ +public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener, + SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener { + private static final String TAG = FolderSyncActivity.class.getSimpleName(); + + private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG"; + public static final String PRIORITIZED_FOLDER = "Camera"; + + private RecyclerView mRecyclerView; + private FolderSyncAdapter mAdapter; + private LinearLayout mProgress; + private TextView mEmpty; + private SyncedFolderProvider mSyncedFolderProvider; + private List syncFolderItems; + private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.folder_sync_layout); + + // setup toolbar + setupToolbar(); + + // setup drawer + setupDrawer(R.id.nav_folder_sync); + getSupportActionBar().setTitle(getString(R.string.drawer_folder_sync)); + + setupContent(); + } + + /** + * sets up the UI elements and loads all media/synced folders. + */ + private void setupContent() { + mRecyclerView = (RecyclerView) findViewById(android.R.id.list); + + mProgress = (LinearLayout) findViewById(android.R.id.progress); + mEmpty = (TextView) findViewById(android.R.id.empty); + + final int gridWidth = getResources().getInteger(R.integer.media_grid_width); + mAdapter = new FolderSyncAdapter(this, gridWidth, this); + mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver()); + + final GridLayoutManager lm = new GridLayoutManager(this, gridWidth); + mAdapter.setLayoutManager(lm); + int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing); + mRecyclerView.addItemDecoration(new MediaGridItemDecoration(spacing)); + mRecyclerView.setLayoutManager(lm); + mRecyclerView.setAdapter(mAdapter); + + load(gridWidth * 2); + } + + /** + * loads all media/synced folders, adds them to the recycler view adapter and shows the list. + * + * @param perFolderMediaItemLimit the amount of media items to be loaded/shown per media folder + */ + private void load(final int perFolderMediaItemLimit) { + if (mAdapter.getItemCount() > 0) { + return; + } + setListShown(false); + final Handler mHandler = new Handler(); + new Thread(new Runnable() { + @Override + public void run() { + final List mediaFolders = MediaProvider.getMediaFolders(getContentResolver(), + perFolderMediaItemLimit); + syncFolderItems = sortSyncedFolderItems(mergeFolderData(mSyncedFolderProvider.getSyncedFolders(), + mediaFolders)); + + mHandler.post(new TimerTask() { + @Override + public void run() { + mAdapter.setSyncFolderItems(syncFolderItems); + setListShown(true); + } + }); + } + }).start(); + } + + /** + * merges two lists of {@link SyncedFolder} and {@link MediaFolder} items into one of SyncedFolderItems. + * + * @param syncedFolders the synced folders + * @param mediaFolders the media folders + * @return the merged list of SyncedFolderItems + */ + @NonNull + private List mergeFolderData(List syncedFolders, + @NonNull List mediaFolders) { + Map syncedFoldersMap = createSyncedFoldersMap(syncedFolders); + List result = new ArrayList<>(); + + for (MediaFolder mediaFolder : mediaFolders) { + if (syncedFoldersMap.containsKey(mediaFolder.absolutePath)) { + SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath); + result.add(createSyncedFolder(syncedFolder, mediaFolder)); + } else { + result.add(createSyncedFolderFromMediaFolder(mediaFolder)); + } + } + + return result; + } + + /** + * Sorts list of {@link SyncedFolderDisplayItem}s. + * + * @param syncFolderItemList list of items to be sorted + * @return sorted list of items + */ + public static List sortSyncedFolderItems(List + syncFolderItemList) { + Collections.sort(syncFolderItemList, new Comparator() { + public int compare(SyncedFolderDisplayItem f1, SyncedFolderDisplayItem f2) { + if (f1 == null && f2 == null) { + return 0; + } else if (f1 == null) { + return -1; + } else if (f2 == null) { + return 1; + } else if (f1.isEnabled() && f2.isEnabled()) { + return f1.getFolderName().toLowerCase().compareTo(f2.getFolderName().toLowerCase()); + } else if (f1.isEnabled()) { + return -1; + } else if (f2.isEnabled()) { + return 1; + } else if (f1.getFolderName() == null && f2.getFolderName() == null) { + return 0; + } else if (f1.getFolderName() == null) { + return -1; + } else if (f2.getFolderName() == null) { + return 1; + } else if (PRIORITIZED_FOLDER.equals(f1.getFolderName())) { + return -1; + } else if (PRIORITIZED_FOLDER.equals(f2.getFolderName())) { + return 1; + } else { + return f1.getFolderName().toLowerCase().compareTo(f2.getFolderName().toLowerCase()); + } + } + }); + + return syncFolderItemList; + } + + /** + * creates a SyncedFolderDisplayItem merging a {@link SyncedFolder} and a {@link MediaFolder} object instance. + * + * @param syncedFolder the synced folder object + * @param mediaFolder the media folder object + * @return the created SyncedFolderDisplayItem + */ + @NonNull + private SyncedFolderDisplayItem createSyncedFolder(@NonNull SyncedFolder syncedFolder, @NonNull MediaFolder mediaFolder) { + return new SyncedFolderDisplayItem( + syncedFolder.getId(), + syncedFolder.getLocalPath(), + syncedFolder.getRemotePath(), + syncedFolder.getWifiOnly(), + syncedFolder.getChargingOnly(), + syncedFolder.getSubfolderByDate(), + syncedFolder.getAccount(), + syncedFolder.getUploadAction(), + syncedFolder.isEnabled(), + mediaFolder.filePaths, + mediaFolder.folderName, + mediaFolder.numberOfFiles); + } + + /** + * creates a {@link SyncedFolderDisplayItem} based on a {@link MediaFolder} object instance. + * + * @param mediaFolder the media folder object + * @return the created SyncedFolderDisplayItem + */ + @NonNull + private SyncedFolderDisplayItem createSyncedFolderFromMediaFolder(@NonNull MediaFolder mediaFolder) { + return new SyncedFolderDisplayItem( + UNPERSISTED_ID, + mediaFolder.absolutePath, + getString(R.string.instant_upload_path) + "/" + mediaFolder.folderName, + true, + false, + false, + AccountUtils.getCurrentOwnCloudAccount(this).name, + 0, + false, + mediaFolder.filePaths, + mediaFolder.folderName, + mediaFolder.numberOfFiles); + } + + /** + * creates a lookup map for a list of given {@link SyncedFolder}s with their local path as the key. + * + * @param syncFolders list of {@link SyncedFolder}s + * @return the lookup map for {@link SyncedFolder}s + */ + @NonNull + private Map createSyncedFoldersMap(List syncFolders) { + Map result = new HashMap<>(); + if (syncFolders != null) { + for (SyncedFolder syncFolder : syncFolders) { + result.put(syncFolder.getLocalPath(), syncFolder); + } + } + return result; + } + + /** + * show/hide recycler view list or the empty message / progress info. + * + * @param shown flag if list should be shown + */ + private void setListShown(boolean shown) { + if (mRecyclerView != null) { + mRecyclerView.setVisibility(shown ? View.VISIBLE : View.GONE); + mProgress.setVisibility(shown ? View.GONE : View.VISIBLE); + mEmpty.setVisibility(shown && mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + boolean result; + switch (item.getItemId()) { + case android.R.id.home: { + if (isDrawerOpen()) { + closeDrawer(); + } else { + openDrawer(); + } + } + + default: + result = super.onOptionsItemSelected(item); + } + return result; + } + + @Override + public void restart() { + Intent i = new Intent(this, FileDisplayActivity.class); + i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(i); + } + + @Override + public void showFiles(boolean onDeviceOnly) { + MainApp.showOnlyFilesOnDevice(onDeviceOnly); + Intent fileDisplayActivity = new Intent(getApplicationContext(), FileDisplayActivity.class); + fileDisplayActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(fileDisplayActivity); + } + + @Override + public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) { + if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) { + mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderDisplayItem.getId(), syncedFolderDisplayItem.isEnabled()); + } else { + mSyncedFolderProvider.storeFolderSync(syncedFolderDisplayItem); + } + } + + @Override + public void onSyncFolderSettingsClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) { + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + ft.addToBackStack(null); + + mSyncedFolderPreferencesDialogFragment = SyncedFolderPreferencesDialogFragment.newInstance( + syncedFolderDisplayItem, section); + mSyncedFolderPreferencesDialogFragment.show(ft, SYNCED_FOLDER_PREFERENCES_DIALOG_TAG); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_REMOTE_FOLDER + && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) { + OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER); + mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath()); + + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) { + SyncedFolderDisplayItem item = syncFolderItems.get(syncedFolder.getSection()); + boolean dirty = item.isEnabled() != syncedFolder.getEnabled(); + item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder + .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder + .getUploadAction(), syncedFolder.getEnabled()); + + if (syncedFolder.getId() == UNPERSISTED_ID) { + // newly set up folder sync config + mSyncedFolderProvider.storeFolderSync(item); + } else { + // existing synced folder setup to be updated + mSyncedFolderProvider.updateSyncFolder(item); + } + mSyncedFolderPreferencesDialogFragment = null; + + if(dirty) { + mAdapter.setSyncFolderItem(syncedFolder.getSection(), item); + } + } + + @Override + public void onCancelSyncedFolderPreference() { + mSyncedFolderPreferencesDialogFragment = null; + } + + /** + * update given synced folder with the given values. + * + * @param item the synced folder to be updated + * @param localPath the local path + * @param remotePath the remote path + * @param wifiOnly upload on wifi only + * @param chargingOnly upload on charging only + * @param subfolderByDate created sub folders + * @param uploadAction upload action + * @param enabled is sync enabled + * @return the updated item + */ + private SyncedFolderDisplayItem updateSyncedFolderItem(SyncedFolderDisplayItem item, + String localPath, + String remotePath, + Boolean wifiOnly, + Boolean chargingOnly, + Boolean subfolderByDate, + Integer uploadAction, + Boolean enabled) { + item.setLocalPath(localPath); + item.setRemotePath(remotePath); + item.setWifiOnly(wifiOnly); + item.setChargingOnly(chargingOnly); + item.setSubfolderByDate(subfolderByDate); + item.setUploadAction(uploadAction); + item.setEnabled(enabled); + return item; + } +} diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java index 977eefe35baf..5274742238db 100644 --- a/src/com/owncloud/android/ui/activity/Preferences.java +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -5,6 +5,7 @@ * @author David A. Velasco * Copyright (C) 2011 Bartek Przybylski * Copyright (C) 2016 ownCloud Inc. + * Copyright (C) 2016 Nextcloud * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -30,6 +31,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.preference.CheckBoxPreference; @@ -69,8 +71,7 @@ /** * An Activity that allows the user to change the application's settings. * - * It proxies the necessary calls via {@link android.support.v7.app.AppCompatDelegate} to be used - * with AppCompat. + * It proxies the necessary calls via {@link android.support.v7.app.AppCompatDelegate} to be used with AppCompat. */ public class Preferences extends PreferenceActivity implements StorageMigration.StorageMigrationProgressListener { @@ -214,10 +215,10 @@ public boolean onPreferenceClick(Preference preference) { preferenceCategory.removePreference(pCalendarContacts); } } - + boolean helpEnabled = getResources().getBoolean(R.bool.help_enabled); Preference pHelp = findPreference("help"); - if (pHelp != null ) { + if (pHelp != null) { if (helpEnabled) { pHelp.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override @@ -236,8 +237,8 @@ public boolean onPreferenceClick(Preference preference) { } } - boolean recommendEnabled = getResources().getBoolean(R.bool.recommend_enabled); - Preference pRecommend = findPreference("recommend"); + boolean recommendEnabled = getResources().getBoolean(R.bool.recommend_enabled); + Preference pRecommend = findPreference("recommend"); if (pRecommend != null) { if (recommendEnabled) { pRecommend.setOnPreferenceClickListener(new OnPreferenceClickListener() { @@ -269,7 +270,7 @@ public boolean onPreferenceClick(Preference preference) { } boolean feedbackEnabled = getResources().getBoolean(R.bool.feedback_enabled); - Preference pFeedback = findPreference("feedback"); + Preference pFeedback = findPreference("feedback"); if (pFeedback != null) { if (feedbackEnabled) { pFeedback.setOnPreferenceClickListener(new OnPreferenceClickListener() { @@ -277,7 +278,7 @@ public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) { String feedbackMail = getString(R.string.mail_feedback); String feedback = getText(R.string.prefs_feedback) + " - android v" + appVersion; - Intent intent = new Intent(Intent.ACTION_SENDTO); + Intent intent = new Intent(Intent.ACTION_SENDTO); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, feedback); @@ -294,7 +295,7 @@ public boolean onPreferenceClick(Preference preference) { } boolean loggerEnabled = getResources().getBoolean(R.bool.logger_enabled) || BuildConfig.DEBUG; - Preference pLogger = findPreference("logger"); + Preference pLogger = findPreference("logger"); if (pLogger != null) { if (loggerEnabled) { pLogger.setOnPreferenceClickListener(new OnPreferenceClickListener() { @@ -312,7 +313,7 @@ public boolean onPreferenceClick(Preference preference) { } boolean imprintEnabled = getResources().getBoolean(R.bool.imprint_enabled); - Preference pImprint = findPreference("imprint"); + Preference pImprint = findPreference("imprint"); if (pImprint != null) { if (imprintEnabled) { pImprint.setOnPreferenceClickListener(new OnPreferenceClickListener() { @@ -333,7 +334,7 @@ public boolean onPreferenceClick(Preference preference) { } } - mPrefStoragePath = (ListPreference) findPreference(PreferenceKeys.STORAGE_PATH); + mPrefStoragePath = (ListPreference) findPreference(PreferenceKeys.STORAGE_PATH); if (mPrefStoragePath != null) { StoragePoint[] storageOptions = DataStorageProvider.getInstance().getAvailableStoragePoints(); String[] entries = new String[storageOptions.length]; @@ -353,22 +354,26 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { return true; } - StorageMigration storageMigration = new StorageMigration(Preferences.this, mStoragePath, newPath); + StorageMigration storageMigration = new StorageMigration(Preferences.this, mStoragePath, newPath); - storageMigration.setStorageMigrationProgressListener(Preferences.this); + storageMigration.setStorageMigrationProgressListener(Preferences.this); - storageMigration.migrate(); + storageMigration.migrate(); - return false; - } - }); + return false; + } + }); } - mPrefInstantUploadPath = findPreference(PreferenceKeys.INSTANT_UPLOAD_PATH); - if (mPrefInstantUploadPath != null) { + mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category"); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // Instant upload via preferences on pre Android Lollipop + mPrefInstantUploadPath = findPreference("instant_upload_path"); + if (mPrefInstantUploadPath != null) { - mPrefInstantUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { + mPrefInstantUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { if (!mUploadPath.endsWith(OCFile.PATH_SEPARATOR)) { @@ -380,33 +385,33 @@ public boolean onPreferenceClick(Preference preference) { return true; } }); - } + } mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category"); - mPrefInstantUploadUseSubfolders = findPreference("instant_upload_path_use_subfolders"); - mPrefInstantUploadPathWiFi = findPreference("instant_upload_on_wifi"); - mPrefInstantPictureUploadOnlyOnCharging = findPreference("instant_upload_on_charging"); - mPrefInstantUpload = findPreference("instant_uploading"); + mPrefInstantUploadUseSubfolders = findPreference("instant_upload_path_use_subfolders"); + mPrefInstantUploadPathWiFi = findPreference("instant_upload_on_wifi"); + mPrefInstantPictureUploadOnlyOnCharging = findPreference("instant_upload_on_charging"); + mPrefInstantUpload = findPreference("instant_uploading"); - toggleInstantPictureOptions(((CheckBoxPreference) mPrefInstantUpload).isChecked()); + toggleInstantPictureOptions(((CheckBoxPreference) mPrefInstantUpload).isChecked()); - mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - toggleInstantPictureOptions((Boolean) newValue); - toggleInstantUploadBehaviour( - ((CheckBoxPreference)mPrefInstantVideoUpload).isChecked(), - (Boolean) newValue); - return true; - } - }); + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + toggleInstantPictureOptions((Boolean) newValue); + toggleInstantUploadBehaviour( + ((CheckBoxPreference) mPrefInstantVideoUpload).isChecked(), + (Boolean) newValue); + return true; + } + }); - mPrefInstantVideoUploadPath = findPreference(PreferenceKeys.INSTANT_VIDEO_UPLOAD_PATH); + mPrefInstantVideoUploadPath = findPreference(PreferenceKeys.INSTANT_VIDEO_UPLOAD_PATH); if (mPrefInstantVideoUploadPath != null){ - mPrefInstantVideoUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { + mPrefInstantVideoUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { if (!mUploadVideoPath.endsWith(OCFile.PATH_SEPARATOR)) { @@ -419,42 +424,44 @@ public boolean onPreferenceClick(Preference preference) { return true; } }); - } + } - mPrefInstantVideoUploadUseSubfolders = findPreference("instant_video_upload_path_use_subfolders"); - mPrefInstantVideoUploadPathWiFi = findPreference("instant_video_upload_on_wifi"); - mPrefInstantVideoUpload = findPreference("instant_video_uploading"); - mPrefInstantVideoUploadOnlyOnCharging = findPreference("instant_video_upload_on_charging"); - toggleInstantVideoOptions(((CheckBoxPreference) mPrefInstantVideoUpload).isChecked()); + mPrefInstantVideoUploadUseSubfolders = findPreference("instant_video_upload_path_use_subfolders"); + mPrefInstantVideoUploadPathWiFi = findPreference("instant_video_upload_on_wifi"); + mPrefInstantVideoUpload = findPreference("instant_video_uploading"); + mPrefInstantVideoUploadOnlyOnCharging = findPreference("instant_video_upload_on_charging"); + toggleInstantVideoOptions(((CheckBoxPreference) mPrefInstantVideoUpload).isChecked()); + mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + toggleInstantVideoOptions((Boolean) newValue); + toggleInstantUploadBehaviour( + (Boolean) newValue, + ((CheckBoxPreference) mPrefInstantUpload).isChecked()); + return true; + } + }); - mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + mPrefInstantUploadBehaviour = findPreference("prefs_instant_behaviour"); + toggleInstantUploadBehaviour( + ((CheckBoxPreference) mPrefInstantVideoUpload).isChecked(), + ((CheckBoxPreference) mPrefInstantUpload).isChecked()); - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - toggleInstantVideoOptions((Boolean) newValue); - toggleInstantUploadBehaviour( - (Boolean) newValue, - ((CheckBoxPreference) mPrefInstantUpload).isChecked()); - return true; - } - }); - - mPrefInstantUploadBehaviour = findPreference("prefs_instant_behaviour"); - toggleInstantUploadBehaviour( - ((CheckBoxPreference)mPrefInstantVideoUpload).isChecked(), - ((CheckBoxPreference)mPrefInstantUpload).isChecked()); + loadInstantUploadPath(); + loadInstantUploadVideoPath(); + } else { + // Instant upload is handled via synced folders on Android Lollipop and up + getPreferenceScreen().removePreference(mPrefInstantUploadCategory); + } /* About App */ - pAboutApp = findPreference("about_app"); - if (pAboutApp != null) { - pAboutApp.setTitle(String.format(getString(R.string.about_android), - getString(R.string.app_name))); - pAboutApp.setSummary(String.format(getString(R.string.about_version), appVersion)); - } + pAboutApp = findPreference("about_app"); + if (pAboutApp != null) { + pAboutApp.setTitle(String.format(getString(R.string.about_android), getString(R.string.app_name))); + pAboutApp.setSummary(String.format(getString(R.string.about_version), appVersion)); + } - loadInstantUploadPath(); - loadStoragePath(); - loadInstantUploadVideoPath(); + loadStoragePath(); } private void launchDavDroidLogin() @@ -635,7 +642,8 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { Toast.makeText(this, R.string.pass_code_removed, Toast.LENGTH_LONG).show(); } } else if (requestCode == ACTION_REQUEST_CODE_DAVDROID_SETUP && resultCode == RESULT_OK) { - Toast.makeText(this, R.string.prefs_calendar_contacts_sync_setup_successful, Toast.LENGTH_LONG).show(); } + Toast.makeText(this, R.string.prefs_calendar_contacts_sync_setup_successful, Toast.LENGTH_LONG).show(); + } } public ActionBar getSupportActionBar() { diff --git a/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java new file mode 100644 index 000000000000..df6de99ca831 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/FolderSyncAdapter.java @@ -0,0 +1,187 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.SyncedFolderDisplayItem; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Adapter to display all auto-synced folders and/or instant upload media folders. + */ +public class FolderSyncAdapter extends SectionedRecyclerViewAdapter { + + private static final String TAG = FolderSyncAdapter.class.getSimpleName(); + + private final Context mContext; + private final int mGridWidth; + private final int mGridTotal; + private final ClickListener mListener; + private final List mSyncFolderItems; + + public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener) { + mContext = context; + mGridWidth = gridWidth; + mGridTotal = gridWidth * 2; + mListener = listener; + mSyncFolderItems = new ArrayList<>(); + } + + public void setSyncFolderItems(List syncFolderItems) { + mSyncFolderItems.clear(); + mSyncFolderItems.addAll(syncFolderItems); + notifyDataSetChanged(); + } + + public void setSyncFolderItem(int location, SyncedFolderDisplayItem syncFolderItem) { + mSyncFolderItems.set(location, syncFolderItem); + notifyDataSetChanged(); + } + + @Override + public int getSectionCount() { + return mSyncFolderItems.size(); + } + + @Override + public int getItemCount(int section) { + return mSyncFolderItems.get(section).getFilePaths().size(); + } + + @Override + public void onBindHeaderViewHolder(final MainViewHolder holder, final int section) { + holder.title.setText(mSyncFolderItems.get(section).getFolderName()); + holder.syncStatusButton.setVisibility(View.VISIBLE); + holder.syncStatusButton.setTag(section); + holder.syncStatusButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled()); + setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); + mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section)); + } + }); + setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled()); + + holder.menuButton.setVisibility(View.VISIBLE); + holder.menuButton.setTag(section); + holder.menuButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mListener.onSyncFolderSettingsClick(section, mSyncFolderItems.get(section)); + } + }); + } + + @Override + public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) { + + File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition)); + + ThumbnailsCacheManager.MediaThumbnailGenerationTask task = + new ThumbnailsCacheManager.MediaThumbnailGenerationTask(holder.image); + + ThumbnailsCacheManager.AsyncMediaThumbnailDrawable asyncDrawable = + new ThumbnailsCacheManager.AsyncMediaThumbnailDrawable( + mContext.getResources(), + ThumbnailsCacheManager.mDefaultImg, + task + ); + holder.image.setImageDrawable(asyncDrawable); + + task.execute(file); + + // set proper tag + holder.image.setTag(file.hashCode()); + + holder.itemView.setTag(relativePosition % mGridWidth); + + if (mSyncFolderItems.get(section).getNumberOfFiles() > mGridTotal && relativePosition >= mGridTotal - 1) { + holder.counterValue.setText(Long.toString(mSyncFolderItems.get(section).getNumberOfFiles() - mGridTotal)); + holder.counterBar.setVisibility(View.VISIBLE); + holder.thumbnailDarkener.setVisibility(View.VISIBLE); + } else { + holder.counterBar.setVisibility(View.GONE); + holder.thumbnailDarkener.setVisibility(View.GONE); + } + + //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos)); + //holder.itemView.setOnClickListener(this); + } + + @Override + public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate( + viewType == VIEW_TYPE_HEADER ? + R.layout.folder_sync_item_header : R.layout.grid_sync_item, parent, false); + return new MainViewHolder(v); + } + + public interface ClickListener { + void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem); + void onSyncFolderSettingsClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem); + } + + static class MainViewHolder extends RecyclerView.ViewHolder { + private final ImageView image; + private final TextView title; + private final ImageButton menuButton; + private final ImageButton syncStatusButton; + private final LinearLayout counterBar; + private final TextView counterValue; + private final ImageView thumbnailDarkener; + + private MainViewHolder(View itemView) { + super(itemView); + image = (ImageView) itemView.findViewById(R.id.thumbnail); + title = (TextView) itemView.findViewById(R.id.title); + menuButton = (ImageButton) itemView.findViewById(R.id.settingsButton); + syncStatusButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton); + counterBar = (LinearLayout) itemView.findViewById(R.id.counterLayout); + counterValue = (TextView) itemView.findViewById(R.id.counter); + thumbnailDarkener = (ImageView) itemView.findViewById(R.id.thumbnailDarkener); + } + } + + private void setSyncButtonActiveIcon(ImageButton syncStatusButton, boolean enabled) { + if(enabled) { + syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_on); + } else { + syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_off); + } + } +} diff --git a/src/com/owncloud/android/ui/decoration/MediaGridItemDecoration.java b/src/com/owncloud/android/ui/decoration/MediaGridItemDecoration.java new file mode 100644 index 000000000000..de8bca3a1c19 --- /dev/null +++ b/src/com/owncloud/android/ui/decoration/MediaGridItemDecoration.java @@ -0,0 +1,44 @@ +/** + * Nextcloud Android client application + * + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.decoration; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.ItemDecoration; +import android.view.View; + +/** + * Decoration for media grid items. + */ +public class MediaGridItemDecoration extends ItemDecoration { + private int space; + + public MediaGridItemDecoration(int space) { + this.space = space; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.right = space; + outRect.bottom = space; + outRect.left = space; + outRect.top = space; + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java new file mode 100644 index 000000000000..c453f718f112 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java @@ -0,0 +1,294 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.dialog; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.SwitchCompat; +import android.text.style.StyleSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.TextView; + +import com.owncloud.android.R; +import com.owncloud.android.datamodel.SyncedFolderDisplayItem; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.activity.FolderPickerActivity; +import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable; +import com.owncloud.android.utils.DisplayUtils; + +/** + * Dialog to show the preferences/configuration of a synced folder allowing the user to change the different parameters. + */ +public class SyncedFolderPreferencesDialogFragment extends DialogFragment { + + private final static String TAG = SyncedFolderPreferencesDialogFragment.class.getSimpleName(); + public static final String SYNCED_FOLDER_PARCELABLE = "SyncedFolderParcelable"; + public static final int REQUEST_CODE__SELECT_REMOTE_FOLDER = 0; + + private CharSequence[] mUploadBehaviorItemStrings; + + protected View mView = null; + private SwitchCompat mEnabledSwitch; + private CheckBox mUploadOnWifiCheckbox; + private CheckBox mUploadOnChargingCheckbox; + private CheckBox mUploadUseSubfoldersCheckbox; + private TextView mUploadBehaviorSummary; + private TextView mLocalFolderPath; + private TextView mRemoteFolderSummary; + + private SyncedFolderParcelable mSyncedFolder; + + public static SyncedFolderPreferencesDialogFragment newInstance(SyncedFolderDisplayItem syncedFolder, int section) { + SyncedFolderPreferencesDialogFragment dialogFragment = new SyncedFolderPreferencesDialogFragment(); + + if (syncedFolder == null) { + throw new IllegalArgumentException("SyncedFolder is mandatory but NULL!"); + } + + Bundle args = new Bundle(); + args.putParcelable(SYNCED_FOLDER_PARCELABLE, new SyncedFolderParcelable(syncedFolder, section)); + dialogFragment.setArguments(args); + dialogFragment.setStyle(STYLE_NORMAL,R.style.Theme_ownCloud_Dialog); + + return dialogFragment; + } + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + if (!(activity instanceof OnSyncedFolderPreferenceListener)) { + throw new IllegalArgumentException("The host activity must implement " + + OnSyncedFolderPreferenceListener.class.getCanonicalName()); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // keep the state of the fragment on configuration changes + setRetainInstance(true); + + setCancelable(false); + mView = null; + + mSyncedFolder = getArguments().getParcelable(SYNCED_FOLDER_PARCELABLE); + mUploadBehaviorItemStrings = getResources().getTextArray(R.array.pref_behaviour_entries); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreateView, savedInstanceState is " + savedInstanceState); + + mView = inflater.inflate(R.layout.folder_sync_settings_layout, container, false); + + setupDialogElements(mView); + setupListeners(mView); + + return mView; + } + + /** + * find all relevant UI elements and set their values. + * + * @param view the parent view + */ + private void setupDialogElements(View view) { + // find/saves UI elements + mEnabledSwitch = (SwitchCompat) view.findViewById(R.id.sync_enabled); + mLocalFolderPath = (TextView) view.findViewById(R.id.folder_sync_settings_local_folder_path); + + mRemoteFolderSummary = (TextView) view.findViewById(R.id.remote_folder_summary); + + mUploadOnWifiCheckbox = (CheckBox) view.findViewById(R.id.setting_instant_upload_on_wifi_checkbox); + mUploadOnChargingCheckbox = (CheckBox) view.findViewById(R.id.setting_instant_upload_on_charging_checkbox); + mUploadUseSubfoldersCheckbox = (CheckBox) view.findViewById(R.id + .setting_instant_upload_path_use_subfolders_checkbox); + + mUploadBehaviorSummary = (TextView) view.findViewById(R.id.setting_instant_behaviour_summary); + + // Set values + setEnabled(mSyncedFolder.getEnabled()); + mLocalFolderPath.setText( + DisplayUtils.createTextWithSpan( + String.format( + getString(R.string.folder_sync_preferences_folder_path), + mSyncedFolder.getLocalPath()), + mSyncedFolder.getFolderName(), + new StyleSpan(Typeface.BOLD))); + + mRemoteFolderSummary.setText(mSyncedFolder.getRemotePath()); + + mUploadOnWifiCheckbox.setChecked(mSyncedFolder.getWifiOnly()); + mUploadOnChargingCheckbox.setChecked(mSyncedFolder.getChargingOnly()); + mUploadUseSubfoldersCheckbox.setChecked(mSyncedFolder.getSubfolderByDate()); + + mUploadBehaviorSummary.setText(mUploadBehaviorItemStrings[mSyncedFolder.getUploadActionInteger()]); + } + + /** + * set correct icon/flag. + * + * @param enabled if enabled or disabled + */ + private void setEnabled(boolean enabled) { + mSyncedFolder.setEnabled(enabled); + mEnabledSwitch.setChecked(enabled); + } + + /** + * set (new) remote path on activity result of the folder picker activity. The result gets originally propagated + * to the underlying activity since the picker is an activity and the result can't get passed to the dialog + * fragment directly. + * + * @param path the remote path to be set + */ + public void setRemoteFolderSummary(String path) { + mSyncedFolder.setRemotePath(path); + mRemoteFolderSummary.setText(path); + } + + /** + * setup all listeners. + * + * @param view the parent view + */ + private void setupListeners(View view) { + view.findViewById(R.id.save).setOnClickListener(new OnSyncedFolderSaveClickListener()); + view.findViewById(R.id.cancel).setOnClickListener(new OnSyncedFolderCancelClickListener()); + + view.findViewById(R.id.setting_instant_upload_on_wifi_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + mSyncedFolder.setWifiOnly(!mSyncedFolder.getWifiOnly()); + mUploadOnWifiCheckbox.toggle(); + } + }); + + view.findViewById(R.id.setting_instant_upload_on_charging_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + mSyncedFolder.setChargingOnly(!mSyncedFolder.getChargingOnly()); + mUploadOnChargingCheckbox.toggle(); + } + }); + + view.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + mSyncedFolder.setSubfolderByDate(!mSyncedFolder.getSubfolderByDate()); + mUploadUseSubfoldersCheckbox.toggle(); + } + }); + + view.findViewById(R.id.remote_folder_container).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + Intent action = new Intent(getActivity(), FolderPickerActivity.class); + action.putExtra( + FolderPickerActivity.EXTRA_ACTION, getResources().getText(R.string.choose_remote_folder)); + getActivity().startActivityForResult(action, REQUEST_CODE__SELECT_REMOTE_FOLDER); + } + }); + + view.findViewById(R.id.sync_enabled).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + setEnabled(!mSyncedFolder.getEnabled()); + } + }); + + view.findViewById(R.id.setting_instant_behaviour_container).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.prefs_instant_behaviour_dialogTitle) + .setSingleChoiceItems(getResources().getTextArray(R.array.pref_behaviour_entries), + mSyncedFolder.getUploadActionInteger(), + new + DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mSyncedFolder.setUploadAction( + getResources().getTextArray( + R.array.pref_behaviour_entryValues)[which].toString()); + mUploadBehaviorSummary.setText(SyncedFolderPreferencesDialogFragment + .this.mUploadBehaviorItemStrings[which]); + dialog.dismiss(); + } + }); + builder.create().show(); + } + }); + } + + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setTitle(null); + return dialog; + } + + @Override + public void onDestroyView() { + Log_OC.d(TAG, "destroy SyncedFolderPreferencesDialogFragment view"); + if (getDialog() != null && getRetainInstance()) { + getDialog().setDismissMessage(null); + } + super.onDestroyView(); + } + + private class OnSyncedFolderSaveClickListener implements OnClickListener { + @Override + public void onClick(View v) { + dismiss(); + ((OnSyncedFolderPreferenceListener) getActivity()).onSaveSyncedFolderPreference(mSyncedFolder); + } + } + + private class OnSyncedFolderCancelClickListener implements OnClickListener { + @Override + public void onClick(View v) { + dismiss(); + ((OnSyncedFolderPreferenceListener) getActivity()).onCancelSyncedFolderPreference(); + } + } + + public interface OnSyncedFolderPreferenceListener { + void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder); + + void onCancelSyncedFolderPreference(); + } +} diff --git a/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java new file mode 100644 index 000000000000..f5d9f499ba57 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java @@ -0,0 +1,219 @@ +/** + * Nextcloud Android client application + * + * @author Andy Scherzinger + * Copyright (C) 2016 Andy Scherzinger + * Copyright (C) 2016 Nextcloud + *

+ * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or 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.dialog.parcel; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.owncloud.android.datamodel.SyncedFolderDisplayItem; +import com.owncloud.android.files.services.FileUploader; + +/** + * Parcelable for {@link SyncedFolderDisplayItem} objects to transport them from/to dialog fragments. + */ +public class SyncedFolderParcelable implements Parcelable { + private String mFolderName; + private String mLocalPath; + private String mRemotePath; + private Boolean mWifiOnly = false; + private Boolean mChargingOnly = false; + private Boolean mEnabled = false; + private Boolean mSubfolderByDate = false; + private Integer mUploadAction; + private long mId; + private String mAccount; + private int mSection; + + public SyncedFolderParcelable() { + } + + public SyncedFolderParcelable(SyncedFolderDisplayItem syncedFolderDisplayItem, int section) { + mId = syncedFolderDisplayItem.getId(); + mFolderName = syncedFolderDisplayItem.getFolderName(); + mLocalPath = syncedFolderDisplayItem.getLocalPath(); + mRemotePath = syncedFolderDisplayItem.getRemotePath(); + mWifiOnly = syncedFolderDisplayItem.getWifiOnly(); + mChargingOnly = syncedFolderDisplayItem.getChargingOnly(); + mEnabled = syncedFolderDisplayItem.isEnabled(); + mSubfolderByDate = syncedFolderDisplayItem.getSubfolderByDate(); + mAccount = syncedFolderDisplayItem.getAccount(); + mUploadAction = syncedFolderDisplayItem.getUploadAction(); + mSection = section; + } + + private SyncedFolderParcelable(Parcel read) { + mId = read.readLong(); + mFolderName = read.readString(); + mLocalPath = read.readString(); + mRemotePath = read.readString(); + mWifiOnly = read.readInt()!= 0; + mChargingOnly = read.readInt() != 0; + mEnabled = read.readInt() != 0; + mSubfolderByDate = read.readInt() != 0; + mAccount = read.readString(); + mUploadAction = read.readInt(); + mSection = read.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mId); + dest.writeString(mFolderName); + dest.writeString(mLocalPath); + dest.writeString(mRemotePath); + dest.writeInt(mWifiOnly ? 1 : 0); + dest.writeInt(mChargingOnly ? 1 : 0); + dest.writeInt(mEnabled ? 1 : 0); + dest.writeInt(mSubfolderByDate ? 1 : 0); + dest.writeString(mAccount); + dest.writeInt(mUploadAction); + dest.writeInt(mSection); + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public SyncedFolderParcelable createFromParcel(Parcel source) { + return new SyncedFolderParcelable(source); + } + + @Override + public SyncedFolderParcelable[] newArray(int size) { + return new SyncedFolderParcelable[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + public String getFolderName() { + return mFolderName; + } + + public void setFolderName(String mFolderName) { + this.mFolderName = mFolderName; + } + + public String getLocalPath() { + return mLocalPath; + } + + public void setLocalPath(String mLocalPath) { + this.mLocalPath = mLocalPath; + } + + public String getRemotePath() { + return mRemotePath; + } + + public void setRemotePath(String mRemotePath) { + this.mRemotePath = mRemotePath; + } + + public Boolean getWifiOnly() { + return mWifiOnly; + } + + public void setWifiOnly(Boolean mWifiOnly) { + this.mWifiOnly = mWifiOnly; + } + + public Boolean getChargingOnly() { + return mChargingOnly; + } + + public void setChargingOnly(Boolean mChargingOnly) { + this.mChargingOnly = mChargingOnly; + } + + public Boolean getEnabled() { + return mEnabled; + } + + public void setEnabled(boolean mEnabled) { + this.mEnabled = mEnabled; + } + + public Boolean getSubfolderByDate() { + return mSubfolderByDate; + } + + public void setSubfolderByDate(Boolean mSubfolderByDate) { + this.mSubfolderByDate = mSubfolderByDate; + } + + public Integer getUploadAction() { + return mUploadAction; + } + + public Integer getUploadActionInteger() { + switch (mUploadAction) { + case FileUploader.LOCAL_BEHAVIOUR_FORGET: + return 0; + case FileUploader.LOCAL_BEHAVIOUR_MOVE: + return 1; + case FileUploader.LOCAL_BEHAVIOUR_DELETE: + return 2; + } + return 0; + } + + public void setUploadAction(String mUploadAction) { + switch (mUploadAction) { + case "LOCAL_BEHAVIOUR_FORGET": + this.mUploadAction = FileUploader.LOCAL_BEHAVIOUR_FORGET; + break; + case "LOCAL_BEHAVIOUR_MOVE": + this.mUploadAction = FileUploader.LOCAL_BEHAVIOUR_MOVE; + break; + case "LOCAL_BEHAVIOUR_DELETE": + this.mUploadAction = FileUploader.LOCAL_BEHAVIOUR_DELETE; + break; + } + } + + public long getId() { + return mId; + } + + public void setId(long mId) { + this.mId = mId; + } + + public String getAccount() { + return mAccount; + } + + public void setAccount(String mAccount) { + this.mAccount = mAccount; + } + + public int getSection() { + return mSection; + } + + public void setSection(int mSection) { + this.mSection = mSection; + } +} diff --git a/src/com/owncloud/android/utils/DisplayUtils.java b/src/com/owncloud/android/utils/DisplayUtils.java index ec2ef5a82f63..508eef9168e5 100644 --- a/src/com/owncloud/android/utils/DisplayUtils.java +++ b/src/com/owncloud/android/utils/DisplayUtils.java @@ -38,7 +38,10 @@ import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; +import android.text.Spannable; +import android.text.SpannableStringBuilder; import android.text.format.DateUtils; +import android.text.style.StyleSpan; import android.view.View; import android.widget.ProgressBar; import android.widget.SeekBar; @@ -68,9 +71,9 @@ public class DisplayUtils { private static final String[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; private static final int[] sizeScales = { 0, 0, 1, 1, 1, 2, 2, 2, 2 }; - public static final int RELATIVE_THRESHOLD_WARNING = 90; - public static final int RELATIVE_THRESHOLD_CRITICAL = 95; - public static final String MIME_TYPE_UNKNOWN = "Unknown type"; + private static final int RELATIVE_THRESHOLD_WARNING = 90; + private static final int RELATIVE_THRESHOLD_CRITICAL = 95; + private static final String MIME_TYPE_UNKNOWN = "Unknown type"; private static Map mimeType2HumanReadable; @@ -355,6 +358,21 @@ public static void colorStatusBar(FragmentActivity fragmentActivity, @ColorInt i } } + /** + * styling of given spanText within a given text. + * + * @param text the non styled complete text + * @param spanText the to be styled text + * @param style the style to be applied + */ + public static SpannableStringBuilder createTextWithSpan(String text, String spanText, StyleSpan style) { + SpannableStringBuilder sb = new SpannableStringBuilder(text); + int start = text.lastIndexOf(spanText); + int end = start + spanText.length(); + sb.setSpan(style, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + return sb; + } + /** * Sets the color of the progressbar to {@code color} within the given toolbar. * diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index c0f3a8f29adf..1eec561edd01 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -142,21 +142,17 @@ private static String getSubpathFromDate(long date) { /** * Returns the InstantUploadFilePath on the owncloud instance * - * @param context * @param fileName * @param dateTaken: Time in milliseconds since 1970 when the picture was taken. * @return */ - public static String getInstantUploadFilePath(Context context, String fileName, long dateTaken) { - SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); - String uploadPathdef = context.getString(R.string.instant_upload_path); - String uploadPath = pref.getString("instant_upload_path", uploadPathdef); + public static String getInstantUploadFilePath(String remotePath, String fileName, long dateTaken, + Boolean subfolderByDate) { String subPath = ""; - if (com.owncloud.android.db.PreferenceManager.instantPictureUploadPathUseSubfolders(context)) { + if (subfolderByDate) { subPath = getSubpathFromDate(dateTaken); } - return uploadPath + OCFile.PATH_SEPARATOR + subPath - + (fileName == null ? "" : fileName); + return remotePath + OCFile.PATH_SEPARATOR + subPath + (fileName == null ? "" : fileName); } /** diff --git a/src/com/owncloud/android/utils/RecursiveFileObserver.java b/src/com/owncloud/android/utils/RecursiveFileObserver.java index e85b30d18af6..b482882c43f6 100644 --- a/src/com/owncloud/android/utils/RecursiveFileObserver.java +++ b/src/com/owncloud/android/utils/RecursiveFileObserver.java @@ -20,13 +20,13 @@ package com.owncloud.android.utils; +import android.os.FileObserver; + import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Stack; -import android.os.FileObserver; - public class RecursiveFileObserver extends FileObserver { private final List mObservers = new ArrayList<>(); @@ -38,7 +38,7 @@ public RecursiveFileObserver(String path) { this(path, ALL_EVENTS); } - RecursiveFileObserver(String path, int mask) { + public RecursiveFileObserver(String path, int mask) { super(path, mask); mPath = path; mMask = mask;