From 42cf3c9a597277ccf48741e67a4678eae3f5a7ff Mon Sep 17 00:00:00 2001 From: A117870935 Date: Tue, 25 May 2021 21:52:42 +0530 Subject: [PATCH 01/12] Cherry pick commit id - 8e3b34a from branch feature/NMCLOUD-323. --- .../client/jobs/BackgroundJobFactory.kt | 10 ++ .../client/jobs/BackgroundJobManager.kt | 2 + .../client/jobs/BackgroundJobManagerImpl.kt | 11 ++ .../nextcloud/ui/fileactions/FileAction.kt | 6 +- .../nmc/android/jobs/UploadImagesWorker.kt | 126 +++++++++++++++ .../java/com/nmc/android/utils/FileUtils.java | 139 ++++++++++++++++ .../datamodel/ThumbnailsCacheManager.java | 8 + .../ui/notifications/NotificationUtils.java | 1 + .../ui/preview/PreviewImageActivity.java | 151 +++++++++++------- .../ui/preview/PreviewImageFragment.java | 142 ++++++++++++---- .../ui/preview/PreviewImagePagerAdapter.java | 4 +- .../owncloud/android/utils/MimeTypeUtil.java | 6 + app/src/main/res/drawable/ic_rotate_right.xml | 12 ++ .../res/layout/preview_image_activity.xml | 1 + .../res/layout/preview_image_fragment.xml | 2 +- app/src/main/res/values-b+en+001/strings.xml | 41 +++++ app/src/main/res/values-de/strings.xml | 39 +++++ app/src/main/res/values/ids.xml | 1 + app/src/main/res/values/strings.xml | 5 + 19 files changed, 616 insertions(+), 91 deletions(-) create mode 100644 app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt create mode 100644 app/src/main/java/com/nmc/android/utils/FileUtils.java create mode 100644 app/src/main/res/drawable/ic_rotate_right.xml diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt index d16d3c619815..1e185d32189f 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -40,6 +40,7 @@ import com.nextcloud.client.integrations.deck.DeckApi import com.nextcloud.client.logger.Logger import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.AppPreferences +import com.nmc.android.jobs.UploadImagesWorker import com.owncloud.android.datamodel.ArbitraryDataProvider import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.UploadsStorageManager @@ -268,4 +269,13 @@ class BackgroundJobFactory @Inject constructor( params = params ) } + + private fun createUploadImagesWork(context: Context, params: WorkerParameters): UploadImagesWorker { + return UploadImagesWorker( + context = context, + params = params, + notificationManager, + accountManager + ) + } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt index 83dbe1f377f3..cdb1fcd84083 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt @@ -143,6 +143,8 @@ interface BackgroundJobManager { fun startPdfGenerateAndUploadWork(user: User, uploadFolder: String, imagePaths: List, pdfPath: String) + fun scheduleImmediateUploadImagesJob(): LiveData + fun scheduleTestJob() fun startImmediateTestJob() fun cancelTestJob() diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt index 582bc6ec07c6..aab8ba460c17 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -40,6 +40,7 @@ import com.nextcloud.client.account.User import com.nextcloud.client.core.Clock import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork import com.owncloud.android.datamodel.OCFile +import com.nmc.android.jobs.UploadImagesWorker import java.util.Date import java.util.UUID import java.util.concurrent.TimeUnit @@ -84,6 +85,8 @@ internal class BackgroundJobManagerImpl( const val JOB_PDF_GENERATION = "pdf_generation" const val JOB_IMMEDIATE_CALENDAR_BACKUP = "immediate_calendar_backup" const val JOB_IMMEDIATE_FILES_EXPORT = "immediate_files_export" + const val JOB_IMMEDIATE_SCAN_DOC_UPLOAD = "immediate_scan_doc_upload" + const val JOB_IMAGE_FILES_UPLOAD = "immediate_image_files_upload" const val JOB_TEST = "test_job" @@ -482,6 +485,14 @@ internal class BackgroundJobManagerImpl( workManager.enqueue(request) } + override fun scheduleImmediateUploadImagesJob(): LiveData { + val request = oneTimeRequestBuilder(UploadImagesWorker::class, JOB_IMAGE_FILES_UPLOAD) + .build() + + workManager.enqueueUniqueWork(JOB_IMAGE_FILES_UPLOAD, ExistingWorkPolicy.APPEND_OR_REPLACE, request) + return workManager.getJobInfo(request.id) + } + override fun scheduleTestJob() { val request = periodicRequestBuilder(TestJob::class, JOB_TEST) .setInitialDelay(DEFAULT_IMMEDIATE_JOB_DELAY_SEC, TimeUnit.SECONDS) diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt index 024e07a89416..63847246fc31 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt @@ -68,7 +68,10 @@ enum class FileAction(@IdRes val id: Int, @StringRes val title: Int, @DrawableRe LOCK_FILE(R.id.action_lock_file, R.string.lock_file, R.drawable.ic_lock), // Shortcuts - PIN_TO_HOMESCREEN(R.id.action_pin_to_homescreen, R.string.pin_home, R.drawable.add_to_home_screen); + PIN_TO_HOMESCREEN(R.id.action_pin_to_homescreen, R.string.pin_home, R.drawable.add_to_home_screen), + + // Rotate + ROTATE_IMAGE(R.id.action_rotate_image, R.string.action_rotate, R.drawable.ic_rotate_right); companion object { /** @@ -85,6 +88,7 @@ enum class FileAction(@IdRes val id: Int, @StringRes val title: Int, @DrawableRe RENAME_FILE, MOVE, COPY, + ROTATE_IMAGE, DOWNLOAD_FILE, EXPORT_FILE, STREAM_MEDIA, diff --git a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt new file mode 100644 index 000000000000..af07aa686417 --- /dev/null +++ b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt @@ -0,0 +1,126 @@ +package com.nmc.android.jobs + +import android.app.NotificationManager +import android.content.Context +import android.graphics.BitmapFactory +import androidx.core.app.NotificationCompat +import androidx.work.Worker +import androidx.work.WorkerParameters +import com.nextcloud.client.account.UserAccountManager +import com.nmc.android.utils.FileUtils +import com.owncloud.android.R +import com.owncloud.android.datamodel.ThumbnailsCacheManager +import com.owncloud.android.files.services.FileUploader +import com.owncloud.android.files.services.NameCollisionPolicy +import com.owncloud.android.operations.UploadFileOperation +import com.owncloud.android.ui.notifications.NotificationUtils +import com.owncloud.android.ui.preview.PreviewImageActivity +import com.owncloud.android.ui.preview.PreviewImageFragment +import java.io.File +import java.security.SecureRandom + +/** + * worker to upload the files in background when app is not running or app is killed + * right now we are using it for to save rotated images + */ +class UploadImagesWorker constructor( + private val context: Context, + params: WorkerParameters, + private val notificationManager: NotificationManager, + private val accountManager: UserAccountManager +) : Worker(context, params) { + + private val savedFiles = mutableListOf() + private val remotePaths = mutableListOf() + + companion object { + const val TAG = "UploadImagesWorkerJob" + } + + override fun doWork(): Result { + + val bitmapHashMap: HashMap = PreviewImageActivity.bitmapHashMap + + val randomId = SecureRandom() + val pushNotificationId = randomId.nextInt() + showNotification(pushNotificationId) + + for ((_, value) in bitmapHashMap) { + val fileName = value.ocFile.fileName + //get the file extension + val extension: String = fileName.substring(fileName.lastIndexOf(".")) + //get the file name without extension + val fileNameWithoutExt: String = fileName.replace(extension, "") + + //if extension is jpg then save the image as jpg + if (extension == ".jpg") { + val jpgFile = FileUtils.saveJpgImage(context, value.bitmap, fileNameWithoutExt) + + //if file is available on local then rewrite the file as well + if (value.ocFile.isDown) { + FileUtils.saveJpgImage(context, value.bitmap, File(value.ocFile.storagePath)) + } + onImageSaveSuccess(value, jpgFile) + + //if extension is png then save the image as png + } else if (extension == ".png") { + val pngFile = FileUtils.savePngImage(context, value.bitmap, fileNameWithoutExt) + + //if file is available on local then rewrite the file as well + if (value.ocFile.isDown) { + FileUtils.savePngImage(context, value.bitmap, File(value.ocFile.storagePath)) + } + onImageSaveSuccess(value, pngFile) + } + + //remove the cache for the existing image + ThumbnailsCacheManager.removeBitmapFromCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + value.ocFile.remoteId) + } + + notificationManager.cancel(pushNotificationId) + + //upload image files + if (!savedFiles.isNullOrEmpty() && !remotePaths.isNullOrEmpty()) { + uploadImageFiles() + } + + return Result.success() + } + + private fun onImageSaveSuccess( + value: PreviewImageFragment.LoadImage, + imageFile: File + ) { + savedFiles.add(imageFile.path) + remotePaths.add(value.ocFile.remotePath) + } + + private fun showNotification(pushNotificationId: Int) { + val notificationBuilder = + NotificationCompat.Builder(context, NotificationUtils.NOTIFICATION_CHANNEL_SCAN_DOC_SAVE) + .setSmallIcon(R.drawable.notification_icon) + .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon)) + //.setColor(ThemeUtils.primaryColor(context, true)) + .setContentTitle(context.resources.getString(R.string.app_name)) + .setContentText(context.resources.getString(R.string.foreground_service_save)) + .setAutoCancel(false) + + notificationManager.notify(pushNotificationId, notificationBuilder.build()) + } + + private fun uploadImageFiles() { + FileUploader.uploadNewFile( + context, + accountManager.user, + savedFiles.toTypedArray(), + remotePaths.toTypedArray(), + null, // MIME type will be detected from file name + FileUploader.LOCAL_BEHAVIOUR_DELETE, + false, // do not create parent folder if not existent + UploadFileOperation.CREATED_BY_USER, + false, + false, + NameCollisionPolicy.OVERWRITE //overwrite the images + ) + } +} diff --git a/app/src/main/java/com/nmc/android/utils/FileUtils.java b/app/src/main/java/com/nmc/android/utils/FileUtils.java new file mode 100644 index 000000000000..9184efdd53b2 --- /dev/null +++ b/app/src/main/java/com/nmc/android/utils/FileUtils.java @@ -0,0 +1,139 @@ +package com.nmc.android.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Environment; +import android.text.TextUtils; +import android.util.Log; + +import com.owncloud.android.MainApp; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.helpers.FileOperationsHelper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class FileUtils { + private static final String TAG = FileUtils.class.getSimpleName(); + + private static final String SCANNED_FILE_PREFIX = "scan_"; + private static final int JPG_FILE_TYPE = 1; + private static final int PNG_FILE_TYPE = 2; + + public static File saveJpgImage(Context context, Bitmap bitmap, String imageName) { + return createFileAndSaveImage(context, bitmap, imageName, JPG_FILE_TYPE); + } + + public static File savePngImage(Context context, Bitmap bitmap, String imageName) { + return createFileAndSaveImage(context, bitmap, imageName, PNG_FILE_TYPE); + } + + public static File saveJpgImage(Context context, Bitmap bitmap, File file) { + return saveImage(file, bitmap, JPG_FILE_TYPE); + } + + public static File savePngImage(Context context, Bitmap bitmap, File file) { + return saveImage(file, bitmap, PNG_FILE_TYPE); + } + + private static File createFileAndSaveImage(Context context, Bitmap bitmap, String imageName, int fileType) { + File file = fileType == PNG_FILE_TYPE ? getPngImageName(context, imageName) : getJpgImageName(context, + imageName); + return saveImage(file, bitmap, fileType); + } + + private static File saveImage(File file, Bitmap bitmap, int fileType) { + try { + FileOutputStream fileOutputStream = new FileOutputStream(file); + bitmap.compress(fileType == PNG_FILE_TYPE ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, 100, + fileOutputStream); + fileOutputStream.flush(); + fileOutputStream.close(); + return file; + } catch (Exception e) { + Log_OC.e(TAG, " Failed to save image : " + e.getLocalizedMessage()); + return null; + } + } + + private static File getJpgImageName(Context context, String imageName) { + File imageFile = getOutputMediaFile(context); + if (!TextUtils.isEmpty(imageName)) { + return new File(imageFile.getPath() + File.separator + imageName + ".jpg"); + } else { + return new File(imageFile.getPath() + File.separator + "IMG_" + FileOperationsHelper.getCapturedImageName()); + } + } + + private static File getPngImageName(Context context, String imageName) { + File imageFile = getOutputMediaFile(context); + if (!TextUtils.isEmpty(imageName)) { + return new File(imageFile.getPath() + File.separator + imageName + ".png"); + } else { + return new File(imageFile.getPath() + File.separator + "IMG_" + FileOperationsHelper.getCapturedImageName().replace(".jpg", ".png")); + } + } + + private static File getTextFileName(Context context, String fileName) { + File txtFileName = getOutputMediaFile(context); + if (!TextUtils.isEmpty(fileName)) { + return new File(txtFileName.getPath() + File.separator + fileName + ".txt"); + } else { + return new File(txtFileName.getPath() + File.separator + FileOperationsHelper.getCapturedImageName().replace(".jpg", ".txt")); + } + } + + private static File getPdfFileName(Context context, String fileName) { + File pdfFileName = getOutputMediaFile(context); + if (!TextUtils.isEmpty(fileName)) { + return new File(pdfFileName.getPath() + File.separator + fileName + ".pdf"); + } else { + return new File(pdfFileName.getPath() + File.separator + FileOperationsHelper.getCapturedImageName().replace(".pdf", ".txt")); + } + } + + public static String scannedFileName() { + return SCANNED_FILE_PREFIX + new SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US).format(new Date()); + } + + public static File getOutputMediaFile(Context context) { + File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), ""); + if (!file.exists()) { + file.mkdir(); + } + return file; + } + + public static Bitmap convertFileToBitmap(File file) { + String filePath = file.getPath(); + Bitmap bitmap = BitmapFactory.decodeFile(filePath); + return bitmap; + } + + public static File writeTextToFile(Context context, String textToWrite, String fileName) { + File file = getTextFileName(context, fileName); + try { + FileWriter fileWriter = new FileWriter(file); + fileWriter.write(textToWrite); + fileWriter.flush(); + fileWriter.close(); + return file; + } catch (IOException e) { + //e.printStackTrace(); + Log_OC.e(TAG, "Failed to write file : " + e.toString()); + } + return null; + + } + +} diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index c0d0a252f098..254e7157b2b4 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -216,6 +216,14 @@ public static void addBitmapToCache(String key, Bitmap bitmap) { } } + public static void removeBitmapFromCache(String key) { + synchronized (mThumbnailsDiskCacheLock) { + if (mThumbnailCache != null) { + mThumbnailCache.removeKey(key); + } + } + } + public static boolean containsBitmap(String key) { return mThumbnailCache.containsKey(key); } diff --git a/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java b/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java index 6c70dc45c91a..bf48ad2ff1e8 100644 --- a/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java +++ b/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java @@ -41,6 +41,7 @@ public final class NotificationUtils { public static final String NOTIFICATION_CHANNEL_FILE_SYNC = "NOTIFICATION_CHANNEL_FILE_SYNC"; public static final String NOTIFICATION_CHANNEL_FILE_OBSERVER = "NOTIFICATION_CHANNEL_FILE_OBSERVER"; public static final String NOTIFICATION_CHANNEL_PUSH = "NOTIFICATION_CHANNEL_PUSH"; + public static final String NOTIFICATION_CHANNEL_SCAN_DOC_SAVE = "NOTIFICATION_CHANNEL_SCAN_DOC_SAVE"; private NotificationUtils() { // utility class -> private constructor diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java index df1ac4616bc3..3405ed3ebe98 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -29,6 +29,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.graphics.Bitmap; import android.graphics.Color; import android.os.Bundle; import android.os.IBinder; @@ -37,6 +38,7 @@ import com.nextcloud.client.account.User; import com.nextcloud.client.di.Injectable; +import com.nextcloud.client.jobs.BackgroundJobManager; import com.nextcloud.client.preferences.AppPreferences; import com.nextcloud.java.util.Optional; import com.owncloud.android.MainApp; @@ -61,6 +63,8 @@ import java.io.Serializable; +import java.util.HashMap; + import javax.inject.Inject; import androidx.annotation.NonNull; @@ -72,14 +76,15 @@ /** - * Holds a swiping galley where image files contained in an Nextcloud directory are shown + * Holds a swiping galley where image files contained in an Nextcloud directory are shown */ @SuppressWarnings("PMD.AvoidDuplicateLiterals") public class PreviewImageActivity extends FileActivity implements - FileFragment.ContainerActivity, - ViewPager.OnPageChangeListener, - OnRemoteOperationListener, - Injectable { + FileFragment.ContainerActivity, + ViewPager.OnPageChangeListener, + OnRemoteOperationListener, + Injectable, + PreviewImageFragment.OnImageLoadListener { public static final String TAG = PreviewImageActivity.class.getSimpleName(); public static final String EXTRA_VIRTUAL_TYPE = "EXTRA_VIRTUAL_TYPE"; @@ -95,6 +100,11 @@ public class PreviewImageActivity extends FileActivity implements private View mFullScreenAnchorView; @Inject AppPreferences preferences; @Inject LocalBroadcastManager localBroadcastManager; + @Inject BackgroundJobManager backgroundJobManager; + + //bitmap hash map to hold the rotated images bitmap + //LoadImage use is used because it will hold bitmap and OCFile which will later use for uploading + public static final HashMap bitmapHashMap = new HashMap<>(); public static Intent previewFileIntent(Context context, User user, OCFile file) { final Intent intent = new Intent(context, PreviewImageActivity.class); @@ -116,6 +126,8 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.preview_image_activity); + //clear the static bitmap hashmap on every on create to clear old data + bitmapHashMap.clear(); // Navigation Drawer setupDrawer(); @@ -171,7 +183,7 @@ private void initViewPager(User user) { mViewPager = findViewById(R.id.fragmentPager); int position = mHasSavedPosition ? mSavedPosition : mPreviewImagePagerAdapter.getFilePosition(getFile()); - position = position >= 0 ? position : 0; + position = Math.max(position, 0); mViewPager.setAdapter(mPreviewImagePagerAdapter); mViewPager.addOnPageChangeListener(this); @@ -182,6 +194,7 @@ private void initViewPager(User user) { // adapter does not result in a call to #onPageSelected(0) mRequestWaitingForBinder = true; } + } @Override @@ -247,24 +260,31 @@ protected ServiceConnection newTransferenceServiceConnection() { return new PreviewImageServiceConnection(); } - /** Defines callbacks for service binding, passed to bindService() */ + @Override + public void onImageLoadCompleted() { + mViewPager.setBackgroundColor(getResources().getColor(R.color.background_color_inverse)); + } + + /** + * Defines callbacks for service binding, passed to bindService() + */ private class PreviewImageServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName component, IBinder service) { if (component.equals(new ComponentName(PreviewImageActivity.this, - FileDownloader.class))) { + FileDownloader.class))) { mDownloaderBinder = (FileDownloaderBinder) service; if (mRequestWaitingForBinder) { mRequestWaitingForBinder = false; Log_OC.d(TAG, "Simulating reselection of current page after connection " + - "of download binder"); + "of download binder"); onPageSelected(mViewPager.getCurrentItem()); } } else if (component.equals(new ComponentName(PreviewImageActivity.this, - FileUploader.class))) { + FileUploader.class))) { Log_OC.d(TAG, "Upload service connected"); mUploaderBinder = (FileUploaderBinder) service; } @@ -274,11 +294,11 @@ public void onServiceConnected(ComponentName component, IBinder service) { @Override public void onServiceDisconnected(ComponentName component) { if (component.equals(new ComponentName(PreviewImageActivity.this, - FileDownloader.class))) { + FileDownloader.class))) { Log_OC.d(TAG, "Download service suddenly disconnected"); mDownloaderBinder = null; } else if (component.equals(new ComponentName(PreviewImageActivity.this, - FileUploader.class))) { + FileUploader.class))) { Log_OC.d(TAG, "Upload service suddenly disconnected"); mUploaderBinder = null; } @@ -288,6 +308,10 @@ public void onServiceDisconnected(ComponentName component) { @Override public void onStop() { + //start the image upload worker when activity goes to onStop state + if (bitmapHashMap.size() > 0) { + backgroundJobManager.scheduleImmediateUploadImagesJob(); + } super.onStop(); } @@ -301,18 +325,18 @@ public void onDestroy() { public boolean onOptionsItemSelected(MenuItem item) { boolean returnValue = false; - switch(item.getItemId()){ - case android.R.id.home: - if (isDrawerOpen()) { - closeDrawer(); - } else { - backToDisplayActivity(); - } - returnValue = true; - break; - default: - returnValue = super.onOptionsItemSelected(item); - break; + switch (item.getItemId()) { + case android.R.id.home: + if (isDrawerOpen()) { + closeDrawer(); + } else { + backToDisplayActivity(); + } + returnValue = true; + break; + default: + returnValue = super.onOptionsItemSelected(item); + break; } return returnValue; @@ -337,7 +361,7 @@ protected void onPostResume() { @Override public void onPause() { - if (mDownloadFinishReceiver != null){ + if (mDownloadFinishReceiver != null) { localBroadcastManager.unregisterReceiver(mDownloadFinishReceiver); mDownloadFinishReceiver = null; } @@ -380,10 +404,9 @@ public void requestForDownload(OCFile file) { } /** - * This method will be invoked when a new page becomes selected. Animation is not necessarily - * complete. + * This method will be invoked when a new page becomes selected. Animation is not necessarily complete. * - * @param position Position index of the new selected page + * @param position Position index of the new selected page */ @Override public void onPageSelected(int position) { @@ -401,7 +424,7 @@ public void onPageSelected(int position) { setDrawerIndicatorEnabled(false); if (currentFile.isEncrypted() && !currentFile.isDown() && - !mPreviewImagePagerAdapter.pendingErrorAt(position)) { + !mPreviewImagePagerAdapter.pendingErrorAt(position)) { requestForDownload(currentFile); } @@ -413,10 +436,10 @@ public void onPageSelected(int position) { } /** - * Called when the scroll state changes. Useful for discovering when the user begins dragging, - * when the pager is automatically settling to the current page. when it is fully stopped/idle. + * Called when the scroll state changes. Useful for discovering when the user begins dragging, when the pager is + * automatically settling to the current page. when it is fully stopped/idle. * - * @param state The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING + * @param state The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING */ @Override public void onPageScrollStateChanged(int state) { @@ -424,15 +447,13 @@ public void onPageScrollStateChanged(int state) { } /** - * This method will be invoked when the current page is scrolled, either as part of a - * programmatically initiated smooth scroll or a user initiated touch scroll. + * This method will be invoked when the current page is scrolled, either as part of a programmatically initiated + * smooth scroll or a user initiated touch scroll. * - * @param position Position index of the first page currently being displayed. - * Page position+1 will be visible if positionOffset is - * nonzero. - * @param positionOffset Value from [0, 1) indicating the offset from the page - * at position. - * @param positionOffsetPixels Value in pixels indicating the offset from position. + * @param position Position index of the first page currently being displayed. Page position+1 will be + * visible if positionOffset is nonzero. + * @param positionOffset Value from [0, 1) indicating the offset from the page at position. + * @param positionOffsetPixels Value in pixels indicating the offset from position. */ @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { @@ -441,9 +462,9 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse /** * Class waiting for broadcast events from the {@link FileDownloader} service. - * - * Updates the UI when a download is started or finished, provided that it is relevant for the - * folder displayed in the gallery. + *

+ * Updates the UI when a download is started or finished, provided that it is relevant for the folder displayed in + * the gallery. */ private class DownloadFinishReceiver extends BroadcastReceiver { @Override @@ -451,7 +472,7 @@ public void onReceive(Context context, Intent intent) { String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); if (getAccount().name.equals(accountName) && - downloadedRemotePath != null) { + downloadedRemotePath != null) { OCFile file = getStorageManager().getFileByPath(downloadedRemotePath); int position = mPreviewImagePagerAdapter.getFilePosition(file); @@ -480,7 +501,7 @@ public boolean isSystemUIVisible() { public void toggleFullScreen() { boolean visible = (mFullScreenAnchorView.getSystemUiVisibility() - & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; + & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; if (visible) { hideSystemUI(mFullScreenAnchorView); @@ -511,23 +532,39 @@ public void onTransferStateChanged(OCFile file, boolean downloading, boolean upl @SuppressLint("InlinedApi") - private void hideSystemUI(View anchorView) { + private void hideSystemUI(View anchorView) { anchorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hides NAVIGATION BAR; Android >= 4.0 - | View.SYSTEM_UI_FLAG_FULLSCREEN // hides STATUS BAR; Android >= 4.1 - | View.SYSTEM_UI_FLAG_IMMERSIVE // stays interactive; Android >= 4.4 - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE // draw full window; Android >= 4.1 - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // draw full window; Android >= 4.1 - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // draw full window; Android >= 4.1 - ); + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hides NAVIGATION BAR; Android >= 4.0 + | View.SYSTEM_UI_FLAG_FULLSCREEN // hides STATUS BAR; Android >= 4.1 + | View.SYSTEM_UI_FLAG_IMMERSIVE // stays interactive; Android >= 4.4 + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE // draw full window; Android >= 4.1 + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // draw full window; Android >= 4.1 + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // draw full window; Android >= 4.1 + ); } @SuppressLint("InlinedApi") private void showSystemUI(View anchorView) { anchorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE // draw full window; Android >= 4.1 - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // draw full window; Android >= 4.1 - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // draw full window; Android >= 4. - ); + View.SYSTEM_UI_FLAG_LAYOUT_STABLE // draw full window; Android >= 4.1 + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // draw full window; Android >= 4.1 + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // draw full window; Android >= 4. + ); + } + + //add the rotated bitmap to hash map + protected void addBitmap(PreviewImageFragment.LoadImage loadImage) { + bitmapHashMap.put(mSavedPosition, loadImage); + } + + //get bitmap for passed index + protected Bitmap getCurrentBitmap(int index) { + if (bitmapHashMap.size() > 0) { + PreviewImageFragment.LoadImage loadImage = bitmapHashMap.get(index); + if (loadImage != null) { + return loadImage.bitmap; + } + } + return null; } } diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index 3ec44ab3cfd5..01e91473863d 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -25,6 +25,8 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -94,12 +96,10 @@ /** * This fragment shows a preview of a downloaded image. - * - * Trying to get an instance with a NULL {@link OCFile} will produce an - * {@link IllegalStateException}. - * - * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on - * instantiation too. + *

+ * Trying to get an instance with a NULL {@link OCFile} will produce an {@link IllegalStateException}. + *

+ * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. */ public class PreviewImageFragment extends FileFragment implements Injectable { @@ -109,6 +109,7 @@ public class PreviewImageFragment extends FileFragment implements Injectable { private static final String ARG_FILE = "FILE"; private static final String ARG_IGNORE_FIRST = "IGNORE_FIRST"; private static final String ARG_SHOW_RESIZED_IMAGE = "SHOW_RESIZED_IMAGE"; + private static final String ARG_CURRENT_INDEX = "CURRENT_INDEX"; private static final String MIME_TYPE_PNG = "image/png"; private static final String MIME_TYPE_GIF = "image/gif"; private static final String MIME_TYPE_SVG = "image/svg+xml"; @@ -130,29 +131,35 @@ public class PreviewImageFragment extends FileFragment implements Injectable { private PreviewImageFragmentBinding binding; + private OnImageLoadListener onImageLoadListener; + private static final int rotationDegrees = 90; + private long lastRotationEventTs = 0L; + private int currentIndex; + + /** * Public factory method to create a new fragment that previews an image. - * - * Android strongly recommends keep the empty constructor of fragments as the only public - * constructor, and - * use {@link #setArguments(Bundle)} to set the needed arguments. - * + *

+ * Android strongly recommends keep the empty constructor of fragments as the only public constructor, and use + * {@link #setArguments(Bundle)} to set the needed arguments. + *

* This method hides to client objects the need of doing the construction in two steps. * * @param imageFile An {@link OCFile} to preview as an image in the fragment - * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of - * {@link FragmentStatePagerAdapter} - * ; TODO better solution + * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter} ; + * TODO better solution */ public static PreviewImageFragment newInstance(@NonNull OCFile imageFile, boolean ignoreFirstSavedState, - boolean showResizedImage) { + boolean showResizedImage, + int currentIndex) { PreviewImageFragment frag = new PreviewImageFragment(); frag.showResizedImage = showResizedImage; Bundle args = new Bundle(); args.putParcelable(ARG_FILE, imageFile); args.putBoolean(ARG_IGNORE_FIRST, ignoreFirstSavedState); args.putBoolean(ARG_SHOW_RESIZED_IMAGE, showResizedImage); + args.putInt(ARG_CURRENT_INDEX, currentIndex); frag.setArguments(args); return frag; } @@ -170,6 +177,16 @@ public PreviewImageFragment() { ignoreFirstSavedState = false; } + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + onImageLoadListener = (OnImageLoadListener) context; + } catch (Exception ignored) { + + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -185,6 +202,7 @@ public void onCreate(Bundle savedInstanceState) { ignoreFirstSavedState = args.getBoolean(ARG_IGNORE_FIRST); showResizedImage = args.getBoolean(ARG_SHOW_RESIZED_IMAGE); + currentIndex = args.getInt(ARG_CURRENT_INDEX); setHasOptionsMenu(true); } @@ -234,7 +252,28 @@ public void onSaveInstanceState(@NonNull Bundle outState) { @Override public void onStart() { super.onStart(); - if (getFile() != null) { + + if (onImageLoadListener != null) { + onImageLoadListener.onImageLoadCompleted(); + } + + //get the rotated bitmap from hashmap + Bitmap rotatedBitmap = null; + if (requireActivity() instanceof PreviewImageActivity) { + rotatedBitmap = ((PreviewImageActivity) requireActivity()).getCurrentBitmap(currentIndex); + } + + //set the rotated bitmap to image view if user swipes back to already rotated image + if (rotatedBitmap != null) { + binding.image.setImageBitmap(rotatedBitmap); + binding.image.setVisibility(View.VISIBLE); + binding.emptyListView.setVisibility(View.GONE); + binding.emptyListProgress.setVisibility(View.GONE); + + //make copy of rotated bitmap to avoid issue during recycle + bitmap = rotatedBitmap.copy(rotatedBitmap.getConfig(), true); + } else if (getFile() != null) { + binding.image.setTag(getFile().getFileId()); Point screenSize = DisplayUtils.getScreenSize(getActivity()); @@ -263,7 +302,8 @@ public void onStart() { binding.image.setVisibility(View.VISIBLE); binding.emptyListView.setVisibility(View.GONE); binding.emptyListProgress.setVisibility(View.GONE); - binding.image.setBackgroundColor(getResources().getColor(R.color.background_color_inverse)); + //not required as setting to view pager + //binding.image.setBackgroundColor(getResources().getColor(R.color.background_color_inverse)); bitmap = resizedImage; } else { @@ -384,6 +424,13 @@ private void showFileActions(OCFile file) { if (getFile() != null && getFile().isSharedWithMe() && !getFile().canReshare()) { additionalFilter.add(R.id.action_send_share_file); } + + //enable rotate image if image is png or jpg + //we are not rotating svg, gif or any other format of images + if (MimeTypeUtil.isJpgOrPngFile(getFile().getFileName())) { + additionalFilter.add(R.id.action_rotate_image); + } + final FragmentManager fragmentManager = getChildFragmentManager(); FileActionsBottomSheet.newInstance(file, false, additionalFilter) .setResultListener(fragmentManager, this, this::onFileActionChosen) @@ -422,9 +469,39 @@ public void onFileActionChosen(final int itemId) { getContext(), getView(), backgroundJobManager); + } else if (itemId == R.id.action_rotate_image) { + rotate(); } } + /** + * method to rotate the image + */ + private void rotate() { + if (System.currentTimeMillis() - lastRotationEventTs < 350) { + return; + } + + //rotate the image using matrix + Matrix matrix = new Matrix(); + matrix.postRotate(rotationDegrees); + //get the bitmap of rotated image + Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + //make copy of rotated bitmap to avoid issue during recycle + bitmap = rotatedBitmap.copy(rotatedBitmap.getConfig(), true); + //set the rotated bitmap to image view + binding.image.setImageBitmap(bitmap); + + //add the rotated bitmap to hashamp + if (requireActivity() instanceof PreviewImageActivity) { + LoadImage loadImage = new LoadImage(rotatedBitmap, null, getFile()); + ((PreviewImageActivity) requireActivity()).addBitmap(loadImage); + } + + lastRotationEventTs = System.currentTimeMillis(); + } + + private void seeDetails() { containerActivity.showDetails(getFile()); } @@ -454,9 +531,9 @@ private class LoadBitmapTask extends AsyncTask { /** * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. - * - * Using a weak reference will avoid memory leaks if the target ImageView is retired from - * memory before the load finishes. + *

+ * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load + * finishes. */ private final WeakReference imageViewRef; private final WeakReference infoViewRef; @@ -523,7 +600,7 @@ protected LoadImage doInBackground(OCFile... params) { try { bitmapResult = BitmapUtils.decodeSampledBitmapFromFile(storagePath, minWidth, - minHeight); + minHeight); if (isCancelled()) { return new LoadImage(bitmapResult, null, ocFile); @@ -561,7 +638,7 @@ protected LoadImage doInBackground(OCFile... params) { } catch (NoSuchFieldError e) { mErrorMessageId = R.string.common_error_unknown; Log_OC.e(TAG, "Error from access to non-existing field despite protection; file " - + storagePath, e); + + storagePath, e); } catch (Throwable t) { mErrorMessageId = R.string.common_error_unknown; @@ -600,7 +677,7 @@ private void showLoadedImage(LoadImage result) { if (imageView != null) { if (bitmap != null) { Log_OC.d(TAG, "Showing image with resolution " + bitmap.getWidth() + "x" + - bitmap.getHeight()); + bitmap.getHeight()); if (MIME_TYPE_PNG.equalsIgnoreCase(result.ocFile.getMimeType()) || MIME_TYPE_GIF.equalsIgnoreCase(result.ocFile.getMimeType())) { @@ -627,7 +704,8 @@ private void showLoadedImage(LoadImage result) { if (progressView != null) { progressView.setVisibility(View.GONE); } - imageView.setBackgroundColor(getResources().getColor(R.color.background_color_inverse)); + //not required as setting to view pager + //imageView.setBackgroundColor(getResources().getColor(R.color.background_color_inverse)); imageView.setVisibility(View.VISIBLE); } } @@ -715,7 +793,7 @@ public void setErrorPreviewMessage() { Snackbar.LENGTH_INDEFINITE).show(); } } - ).show(); + ).show(); } } catch (IllegalArgumentException e) { Log_OC.d(TAG, e.getMessage()); @@ -731,8 +809,7 @@ public void setNoConnectionErrorMessage() { } /** - * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} - * to be previewed. + * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} to be previewed. * * @param file File to test if can be previewed. * @return 'True' if the file can be handled by the fragment. @@ -794,10 +871,10 @@ public PhotoView getImageView() { return binding.image; } - private class LoadImage { - private final Bitmap bitmap; + public static class LoadImage { + public final Bitmap bitmap; private final Drawable drawable; - private final OCFile ocFile; + public final OCFile ocFile; LoadImage(Bitmap bitmap, Drawable drawable, OCFile ocFile) { this.bitmap = bitmap; @@ -805,4 +882,9 @@ private class LoadImage { this.ocFile = ocFile; } } + + + public interface OnImageLoadListener { + void onImageLoadCompleted(); + } } diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java index eff74ea89555..7907ad5ea3ce 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java @@ -157,7 +157,7 @@ public Fragment getItem(int i) { fragment = PreviewImageErrorFragment.newInstance(); } else if (file.isDown()) { - fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), false); + fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), false, i); } else { if (mDownloadErrors.remove(i)) { fragment = FileDownloadFragment.newInstance(file, user, true); @@ -168,7 +168,7 @@ public Fragment getItem(int i) { } else if (PreviewMediaFragment.canBePreviewed(file)) { fragment = PreviewMediaFragment.newInstance(file, user, 0, false); } else { - fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), true); + fragment = PreviewImageFragment.newInstance(file, mObsoletePositions.contains(i), true, i); } } } diff --git a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java index f6790d5bc3ab..72facddc5b87 100644 --- a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java +++ b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java @@ -218,6 +218,12 @@ public static boolean isImageOrVideo(ServerFileInterface file) { return isImage(file) || isVideo(file); } + //check if file is png or jpg image + public static boolean isJpgOrPngFile(String fileName) { + String extension = fileName.substring(fileName.lastIndexOf(".")); + return extension.equalsIgnoreCase(".png") || extension.equalsIgnoreCase(".jpg"); + } + /** * @return 'True' if the mime type defines image */ diff --git a/app/src/main/res/drawable/ic_rotate_right.xml b/app/src/main/res/drawable/ic_rotate_right.xml new file mode 100644 index 000000000000..f16133f9bb82 --- /dev/null +++ b/app/src/main/res/drawable/ic_rotate_right.xml @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/preview_image_activity.xml b/app/src/main/res/layout/preview_image_activity.xml index 8f7106ae9d3e..110f99ed97da 100644 --- a/app/src/main/res/layout/preview_image_activity.xml +++ b/app/src/main/res/layout/preview_image_activity.xml @@ -26,6 +26,7 @@ android:id="@+id/fragmentPager" android:layout_width="match_parent" android:layout_height="match_parent" + android:paddingTop="?attr/actionBarSize" /> diff --git a/app/src/main/res/values-b+en+001/strings.xml b/app/src/main/res/values-b+en+001/strings.xml index 6df4339f7a25..dfe990eaa1ae 100644 --- a/app/src/main/res/values-b+en+001/strings.xml +++ b/app/src/main/res/values-b+en+001/strings.xml @@ -977,4 +977,45 @@ %d selected %d selected + + Do not move + Move closer + Perspective + No document + Background too noisy + Wrong aspect ratio.\nRotate your device. + Poor light + + %d of %d + Edit Scan + Crop Scan + Reset Crop + Detect Document + Apply Filter + No Filter + Whiteboard + Photo Filter + Black & White + Document Filter + Grayscale + Automatic + Flash + Save as + Filename + Location + /Root folder + File type + Save without text recognition + Save with text recognition + Textfile (txt) + PDF-Password + Set password + Please select at least one filetype + You can save the file with or without text recognition. Multiple + selection is allowed. + Please navigate to App info in settings and give permission manually. + You cannot scan document without camera permission. + Search for a file (at least 2 characters) + Rotate + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 25a17c5cada1..70fec2c07bb4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -392,6 +392,7 @@ diese Datei umzubenennen Dateien werden heruntergeladen… Dateien werden hochgeladen… + Dateien werden gespeichert... Einige Dateien konnten nicht verschoben werden Lokal: %1$s Alle verschieben @@ -978,4 +979,42 @@ %d ausgewählt %d ausgewählt + + Nicht bewegen + Näher heranbewegen + Perspektive + Kein Dokument + Hintergrund zu unruhig + Falsches Bildformat.\nDrehen Sie Ihr Gerät. + Schwaches Licht + %d von %d + Scam Bearbeiten + Scan beschneiden + Rahmen zurücksetzen + Dokument erkenne + Filter anwenden + Kein Filter + Whiteboard + Foto Filter + Black & White + Dokument Filter + Grau + Automatisch + Blitz + Speichern unter + Dateiname + Speicherort + /Hauptverzeichnis + Dateityp + Speichern ohne Texterkennung + Speichern mit Texterkennung + Textdokument (txt) + PDF-Passwort + Passwort setzen + Selectieren Sie mindestens einen Dateityp + Sie können die Datei mit oder ohne Texterkennung abspeichern. Mehrfachauswahl ist erlaubt. + Bitte geben Sie unter Apps & Benachrichtigungen in den Einstellungen manuell die Erlaubnis. + Sie können keine Dokumente scannen ohne die Erlaubnis die Kamera zu verwenden. + Eine Datei suchen (mindestens 2 Zeichen) + Drehen diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 60e85c4e8b1c..12b025acf0d3 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -47,4 +47,5 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8af02a33a2e5..6e9cd5b7e231 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -627,6 +627,7 @@ Uploading files… Downloading files… + Saving files… Get source code License @@ -1079,6 +1080,10 @@ Multiple images Cannot create local file Invalid filename for local file + Please navigate to App info in settings and give permission manually. + You cannot scan document without camera permission. + Search for a file (at least 2 characters) + Rotate Groupfolders +%1$d Unable to open password-protected PDF. Please use an external PDF viewer. From 1790d84a15d977fab34b8ae7c517a5a50d8920c5 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Fri, 23 Jul 2021 16:04:13 +0530 Subject: [PATCH 02/12] Cherry pick commit id - 3835660 from branch feature/NMCLOUD-438. --- .../ui/preview/PreviewImageActivity.java | 9 ++- .../ui/preview/PreviewImageFragment.java | 70 ++++++++++++++----- .../res/layout/preview_image_fragment.xml | 10 +++ 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java index 3405ed3ebe98..06ada46f1d34 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -90,6 +90,7 @@ public class PreviewImageActivity extends FileActivity implements public static final String EXTRA_VIRTUAL_TYPE = "EXTRA_VIRTUAL_TYPE"; private static final String KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER"; private static final String KEY_SYSTEM_VISIBLE = "TRUE"; + private static final String KEY_ROTATED_IMAGES_SIZE = "ROTATED_IMAGES_MAP_SIZE"; private ViewPager mViewPager; private PreviewImagePagerAdapter mPreviewImagePagerAdapter; @@ -127,7 +128,11 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.preview_image_activity); //clear the static bitmap hashmap on every on create to clear old data - bitmapHashMap.clear(); + //if saved instance is not null that means device is rotated and hashmap may have some data so no need to + // reset it + if (savedInstanceState == null || !savedInstanceState.containsKey(KEY_ROTATED_IMAGES_SIZE)) { + bitmapHashMap.clear(); + } // Navigation Drawer setupDrawer(); @@ -236,6 +241,8 @@ protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(KEY_WAITING_FOR_BINDER, mRequestWaitingForBinder); outState.putBoolean(KEY_SYSTEM_VISIBLE, isSystemUIVisible()); + //set hashmap size when config changes to maintain the hashmap + outState.putInt(KEY_ROTATED_IMAGES_SIZE, bitmapHashMap.size()); } @Override diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index 01e91473863d..59144b9adf93 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -34,6 +34,8 @@ import android.graphics.drawable.PictureDrawable; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.Process; import android.util.DisplayMetrics; import android.view.LayoutInflater; @@ -71,6 +73,8 @@ import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.theme.ViewThemeUtils; +import org.jetbrains.annotations.NotNull; + import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -78,6 +82,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import javax.annotation.Nullable; import javax.inject.Inject; @@ -87,6 +93,7 @@ import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; import androidx.fragment.app.FragmentManager; +import androidx.core.os.HandlerCompat; import androidx.fragment.app.FragmentStatePagerAdapter; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import pl.droidsonroids.gif.GifDrawable; @@ -135,7 +142,10 @@ public class PreviewImageFragment extends FileFragment implements Injectable { private static final int rotationDegrees = 90; private long lastRotationEventTs = 0L; private int currentIndex; - + private boolean isRotationInProgress; + private boolean isImageLoadingFailed;//flag to check if image loading is failed or not + private ExecutorService rotationExecutorService = Executors.newSingleThreadExecutor(); + private Handler rotationHandler = HandlerCompat.createAsync(Looper.getMainLooper()); /** * Public factory method to create a new fragment that previews an image. @@ -265,15 +275,17 @@ public void onStart() { //set the rotated bitmap to image view if user swipes back to already rotated image if (rotatedBitmap != null) { + isImageLoadingFailed = false; binding.image.setImageBitmap(rotatedBitmap); binding.image.setVisibility(View.VISIBLE); binding.emptyListView.setVisibility(View.GONE); binding.emptyListProgress.setVisibility(View.GONE); + binding.image.setBackgroundColor(getResources().getColor(R.color.background_color_inverse)); //make copy of rotated bitmap to avoid issue during recycle bitmap = rotatedBitmap.copy(rotatedBitmap.getConfig(), true); } else if (getFile() != null) { - + isImageLoadingFailed = false; binding.image.setTag(getFile().getFileId()); Point screenSize = DisplayUtils.getScreenSize(getActivity()); @@ -393,6 +405,9 @@ public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflat @Override public boolean onOptionsItemSelected(MenuItem item) { + if (isRotationInProgress) { + return true; + } if (item.getItemId() == R.id.custom_menu_placeholder_item) { final OCFile file = getFile(); if (containerActivity.getStorageManager() != null && file != null) { @@ -427,6 +442,7 @@ private void showFileActions(OCFile file) { //enable rotate image if image is png or jpg //we are not rotating svg, gif or any other format of images + //image loading should not be failed to show rotate images if (MimeTypeUtil.isJpgOrPngFile(getFile().getFileName())) { additionalFilter.add(R.id.action_rotate_image); } @@ -482,25 +498,40 @@ private void rotate() { return; } - //rotate the image using matrix - Matrix matrix = new Matrix(); - matrix.postRotate(rotationDegrees); - //get the bitmap of rotated image - Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - //make copy of rotated bitmap to avoid issue during recycle - bitmap = rotatedBitmap.copy(rotatedBitmap.getConfig(), true); - //set the rotated bitmap to image view - binding.image.setImageBitmap(bitmap); - - //add the rotated bitmap to hashamp - if (requireActivity() instanceof PreviewImageActivity) { - LoadImage loadImage = new LoadImage(rotatedBitmap, null, getFile()); - ((PreviewImageActivity) requireActivity()).addBitmap(loadImage); - } + showHideViewDuringRotation(true); - lastRotationEventTs = System.currentTimeMillis(); + //execute the rotation task in background + rotationExecutorService.execute(() -> { + //rotate the image using matrix + Matrix matrix = new Matrix(); + matrix.postRotate(rotationDegrees); + //get the bitmap of rotated image + Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + //make copy of rotated bitmap to avoid issue during recycle + bitmap = rotatedBitmap.copy(rotatedBitmap.getConfig(), true); + + rotationHandler.post(() -> { + //set the rotated bitmap to image view + binding.image.setImageBitmap(bitmap); + + //add the rotated bitmap to hashmap + if (requireActivity() instanceof PreviewImageActivity) { + LoadImage loadImage = new LoadImage(rotatedBitmap, null, getFile()); + ((PreviewImageActivity) requireActivity()).addBitmap(loadImage); + } + + lastRotationEventTs = System.currentTimeMillis(); + + showHideViewDuringRotation(false); + }); + }); } + private void showHideViewDuringRotation(boolean isRotationInProgress) { + this.isRotationInProgress = isRotationInProgress; + binding.progressBar.setVisibility(isRotationInProgress ? View.VISIBLE : View.GONE); + binding.image.setVisibility(isRotationInProgress ? View.INVISIBLE : View.VISIBLE); + } private void seeDetails() { containerActivity.showDetails(getFile()); @@ -659,6 +690,7 @@ protected void onCancelled(LoadImage result) { @Override protected void onPostExecute(LoadImage result) { if (result.bitmap != null || result.drawable != null) { + isImageLoadingFailed = false; showLoadedImage(result); } else { showErrorMessage(mErrorMessageId); @@ -775,6 +807,8 @@ private void setSorryMessageForMultiList(@StringRes int message) { binding.image.setVisibility(View.GONE); binding.emptyListView.setVisibility(View.VISIBLE); binding.emptyListProgress.setVisibility(View.GONE); + isImageLoadingFailed = true; + requireActivity().invalidateOptionsMenu(); } public void setErrorPreviewMessage() { diff --git a/app/src/main/res/layout/preview_image_fragment.xml b/app/src/main/res/layout/preview_image_fragment.xml index bcde3c6c7ab5..cced150adda3 100644 --- a/app/src/main/res/layout/preview_image_fragment.xml +++ b/app/src/main/res/layout/preview_image_fragment.xml @@ -18,6 +18,7 @@ --> + + + From 01dd6e1de02e58c2d0cd744db93589e13b544597 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Thu, 11 Nov 2021 20:26:35 +0530 Subject: [PATCH 03/12] Cherry pick commit id - 5794a58 from branch feature/NMCLOUD-781. --- .../nmc/android/jobs/UploadImagesWorker.kt | 20 ++++--- .../java/com/nmc/android/utils/FileUtils.java | 33 ++++++----- .../ui/preview/PreviewImageActivity.java | 10 +++- .../ui/preview/PreviewImageFragment.java | 10 ++++ .../owncloud/android/utils/MimeTypeUtil.java | 4 +- app/src/main/res/values-de/strings.xml | 58 +++++++++++++++++++ 6 files changed, 110 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt index af07aa686417..8a63771c357c 100644 --- a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt +++ b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt @@ -9,7 +9,6 @@ import androidx.work.WorkerParameters import com.nextcloud.client.account.UserAccountManager import com.nmc.android.utils.FileUtils import com.owncloud.android.R -import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.files.services.FileUploader import com.owncloud.android.files.services.NameCollisionPolicy import com.owncloud.android.operations.UploadFileOperation @@ -35,11 +34,16 @@ class UploadImagesWorker constructor( companion object { const val TAG = "UploadImagesWorkerJob" + const val IMAGE_COMPRESSION_PERCENTAGE = 100 } override fun doWork(): Result { - val bitmapHashMap: HashMap = PreviewImageActivity.bitmapHashMap + + val bitmapHashMap: HashMap = HashMap(PreviewImageActivity.bitmapHashMap) + + //clear the static bitmap once the images are stored in work manager instance + PreviewImageActivity.bitmapHashMap.clear() val randomId = SecureRandom() val pushNotificationId = randomId.nextInt() @@ -53,28 +57,26 @@ class UploadImagesWorker constructor( val fileNameWithoutExt: String = fileName.replace(extension, "") //if extension is jpg then save the image as jpg - if (extension == ".jpg") { - val jpgFile = FileUtils.saveJpgImage(context, value.bitmap, fileNameWithoutExt) + if (extension == ".jpg" || extension == ".jpeg") { + val jpgFile = FileUtils.saveJpgImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) //if file is available on local then rewrite the file as well if (value.ocFile.isDown) { - FileUtils.saveJpgImage(context, value.bitmap, File(value.ocFile.storagePath)) + FileUtils.saveJpgImage(context, value.bitmap, File(value.ocFile.storagePath), IMAGE_COMPRESSION_PERCENTAGE) } onImageSaveSuccess(value, jpgFile) //if extension is png then save the image as png } else if (extension == ".png") { - val pngFile = FileUtils.savePngImage(context, value.bitmap, fileNameWithoutExt) + val pngFile = FileUtils.savePngImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) //if file is available on local then rewrite the file as well if (value.ocFile.isDown) { - FileUtils.savePngImage(context, value.bitmap, File(value.ocFile.storagePath)) + FileUtils.savePngImage(context, value.bitmap, File(value.ocFile.storagePath), IMAGE_COMPRESSION_PERCENTAGE) } onImageSaveSuccess(value, pngFile) } - //remove the cache for the existing image - ThumbnailsCacheManager.removeBitmapFromCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + value.ocFile.remoteId) } notificationManager.cancel(pushNotificationId) diff --git a/app/src/main/java/com/nmc/android/utils/FileUtils.java b/app/src/main/java/com/nmc/android/utils/FileUtils.java index 9184efdd53b2..e1e097e2523d 100644 --- a/app/src/main/java/com/nmc/android/utils/FileUtils.java +++ b/app/src/main/java/com/nmc/android/utils/FileUtils.java @@ -11,6 +11,7 @@ import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.helpers.FileOperationsHelper; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -19,8 +20,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import java.util.Locale; public class FileUtils { @@ -30,33 +33,37 @@ public class FileUtils { private static final int JPG_FILE_TYPE = 1; private static final int PNG_FILE_TYPE = 2; - public static File saveJpgImage(Context context, Bitmap bitmap, String imageName) { - return createFileAndSaveImage(context, bitmap, imageName, JPG_FILE_TYPE); + public static File saveJpgImage(Context context, Bitmap bitmap, String imageName, int quality) { + return createFileAndSaveImage(context, bitmap, imageName, quality, JPG_FILE_TYPE); } - public static File savePngImage(Context context, Bitmap bitmap, String imageName) { - return createFileAndSaveImage(context, bitmap, imageName, PNG_FILE_TYPE); + public static File savePngImage(Context context, Bitmap bitmap, String imageName, int quality) { + return createFileAndSaveImage(context, bitmap, imageName, quality, PNG_FILE_TYPE); } - public static File saveJpgImage(Context context, Bitmap bitmap, File file) { - return saveImage(file, bitmap, JPG_FILE_TYPE); + public static File saveJpgImage(Context context, Bitmap bitmap, File file, int quality) { + return saveImage(file, bitmap, quality, JPG_FILE_TYPE); } - public static File savePngImage(Context context, Bitmap bitmap, File file) { - return saveImage(file, bitmap, PNG_FILE_TYPE); + public static File savePngImage(Context context, Bitmap bitmap, File file, int quality) { + return saveImage(file, bitmap, quality, PNG_FILE_TYPE); } - private static File createFileAndSaveImage(Context context, Bitmap bitmap, String imageName, int fileType) { + private static File createFileAndSaveImage(Context context, Bitmap bitmap, String imageName, int quality, + int fileType) { File file = fileType == PNG_FILE_TYPE ? getPngImageName(context, imageName) : getJpgImageName(context, imageName); - return saveImage(file, bitmap, fileType); + return saveImage(file, bitmap, quality, fileType); } - private static File saveImage(File file, Bitmap bitmap, int fileType) { + private static File saveImage(File file, Bitmap bitmap, int quality, int fileType) { try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos); + byte[] bitmapData = bos.toByteArray(); + FileOutputStream fileOutputStream = new FileOutputStream(file); - bitmap.compress(fileType == PNG_FILE_TYPE ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, 100, - fileOutputStream); + fileOutputStream.write(bitmapData); fileOutputStream.flush(); fileOutputStream.close(); return file; diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java index 06ada46f1d34..86236bc55d0b 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -107,6 +107,10 @@ public class PreviewImageActivity extends FileActivity implements //LoadImage use is used because it will hold bitmap and OCFile which will later use for uploading public static final HashMap bitmapHashMap = new HashMap<>(); + //this hashmap will hold the rotated image bitmap so that when device rotated we can show the + //rotated bitmap + private static final HashMap localBitmapHashMap = new HashMap<>(); + public static Intent previewFileIntent(Context context, User user, OCFile file) { final Intent intent = new Intent(context, PreviewImageActivity.class); intent.putExtra(FileActivity.EXTRA_FILE, file); @@ -132,6 +136,7 @@ protected void onCreate(Bundle savedInstanceState) { // reset it if (savedInstanceState == null || !savedInstanceState.containsKey(KEY_ROTATED_IMAGES_SIZE)) { bitmapHashMap.clear(); + localBitmapHashMap.clear(); } // Navigation Drawer setupDrawer(); @@ -562,12 +567,13 @@ private void showSystemUI(View anchorView) { //add the rotated bitmap to hash map protected void addBitmap(PreviewImageFragment.LoadImage loadImage) { bitmapHashMap.put(mSavedPosition, loadImage); + localBitmapHashMap.put(mSavedPosition, loadImage); } //get bitmap for passed index protected Bitmap getCurrentBitmap(int index) { - if (bitmapHashMap.size() > 0) { - PreviewImageFragment.LoadImage loadImage = bitmapHashMap.get(index); + if (localBitmapHashMap.size() > 0) { + PreviewImageFragment.LoadImage loadImage = localBitmapHashMap.get(index); if (loadImage != null) { return loadImage.bitmap; } diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index 59144b9adf93..69bd63b7aa34 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -440,6 +440,16 @@ private void showFileActions(OCFile file) { additionalFilter.add(R.id.action_send_share_file); } + //this condition will only run when image is rotated + //get the rotated bitmap from hashmap + if (requireActivity() instanceof PreviewImageActivity) { + Bitmap rotatedBitmap = ((PreviewImageActivity) requireActivity()).getCurrentBitmap(currentIndex); + //check if it should not be null + if (rotatedBitmap != null) { + additionalFilter.add(R.id.action_send_share_file); + } + } + //enable rotate image if image is png or jpg //we are not rotating svg, gif or any other format of images //image loading should not be failed to show rotate images diff --git a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java index 72facddc5b87..61313f8784bf 100644 --- a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java +++ b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java @@ -221,7 +221,9 @@ public static boolean isImageOrVideo(ServerFileInterface file) { //check if file is png or jpg image public static boolean isJpgOrPngFile(String fileName) { String extension = fileName.substring(fileName.lastIndexOf(".")); - return extension.equalsIgnoreCase(".png") || extension.equalsIgnoreCase(".jpg"); + return extension.equalsIgnoreCase(".png") + || extension.equalsIgnoreCase(".jpg") + || extension.equalsIgnoreCase(".jpeg"); } /** diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 70fec2c07bb4..3b096fc7f821 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1017,4 +1017,62 @@ Sie können keine Dokumente scannen ohne die Erlaubnis die Kamera zu verwenden. Eine Datei suchen (mindestens 2 Zeichen) Drehen + Weiteres Dokument hinzufügen + Gescanntes Dokument zuschneiden + Gescanntes Dokument filtern + Gescanntes Dokument drehen + Gescanntes Dokument löschen + Scan-Dateinamen bearbeiten + Scan-Speicherort bearbeiten + Ersetzen + Alle ersetzen + Beide behalten + Beide Versionen für alle behalten + Mehr Details + Abbrechen und bestehende Datei behalten + Dateikonflikt + %d Dateikonflikte + %1$s ist im Zielordner bereits vorhanden. Möchten Sie die bestehende Datei behalten oder überschreiben? + Die Dateien sind im Zielordner bereits vorhanden. Möchten Sie die bestehenden Dateien behalten oder überschreiben? + Info + Sie können Links erstellen oder Freigaben per Mail versenden. Wenn Sie MagentaCloud Nutzer einladen, bieten sich Ihnen mehr Möglichkeiten der Zusammenarbeit. + Ihre Nachricht + persönliche Freigabe per Emai + Neuen Link erstellen + Ihre Freigaben + Link Label + Your custom link label + Bei der Sammelbox ist nur das Hochladen erlaubt. Nur Sie sehen Dateien und Ordner die hochgeladen worden sind. + Password Protection + Expiration Date + Noch keine Freigaben erstellt. + Datenschutz + Datenschutz + Datenschutzbestimmungen + Verwendete OpenSource Software + Zur Optimierung unserer App erfassen wir anonymisierte Daten. Hierzu nutzen wir Software Lösungen verschiedener Partner. Wir möchten Ihnen volle Transparenz und Entscheidungsgewalt über die Verarbeitung und Erfassung Ihrer anonymisierten Nutzungsdaten geben. Ihre Einstellungen können Sie auch später jederzeit in den Einstellungen unter Datenschutz ändern. Bitte beachten Sie jedoch, dass die Datenerfassungen einen erheblichen Beitrag zur Optimierung dieser App leisten und Sie diese Optimierungen durch die Unterbindung der Datenübermittlung verhindern. + Erfoderliche Datenerfassung + Die Erfassung dieser Daten ist notwendig, um wesentliche Funktionen der App nutzen zu können. + Analyse-Datenerfassung zur bedarfsgerechten Gestaltung + Diese Daten helfen uns, die App Nutzung für Sie zu optimieren und Systemabstürze und Fehler schneller zu identifizieren. + Bedienung + Sie teilen mit einer/einem MagentaCLOUD Nutzer(in). Sie können ihr oder ihm erlauben, den Ordner oder die Dateien weiterzuteilen. + Diese App verwendet Cookies und ähnliche Technologien (Tools). + Mit einem Klick auf Zustimmen akzeptieren Sie die Verarbeitung und auch die Weitergabe Ihrer Daten an + Drittanbieter. Die Daten werden für Analysen, Retargeting und zur Ausspielung von personalisierten Inhalten + und Werbung auf Seiten der Telekom, sowie auf Drittanbieterseiten genutzt. Weitere Informationen, auch zur + Datenverarbeitung durch Drittanbieter, finden Sie in den Einstellungen sowie in unseren %s. Sie können die + Verwendung der Tools %s oder jederzeit über ihre %s anpassen. + Datenschutz-Einstellungen + Akzeptieren + Einstellungen speichern + Datenschutzhinweise + ablehnen + Einstellungen + Neuste Dateien + Okay + Das Speichern kann einige Minuten in Anspruch nehmen, insbesondere wenn Sie mehrere Seiten und Dateiformate ausgewählt haben. + Der Passwortschutz ist aktiviert. Sie müssen dem Empfänger das Passwort + selbst mitteilen.\n\nWenn Sie die Freigabe über die MagentaCLOUD verschicken und das Passwort in den + Nachrichtentext eintragen, wird es unverschlüsselt im Klartext übertragen. From afcf300d81fdd0a2c4ab596673855ac4df513571 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Wed, 16 Nov 2022 11:24:29 +0530 Subject: [PATCH 04/12] Cherry pick commit id - c55164b from branch bug/NMC-1441. --- .../owncloud/android/ui/preview/PreviewImageFragment.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index 69bd63b7aa34..62d0c8fecc1a 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -450,10 +450,12 @@ private void showFileActions(OCFile file) { } } - //enable rotate image if image is png or jpg + //rotate functionality will not be available for encrypted files + //enable rotate functionality if image is png or jpg //we are not rotating svg, gif or any other format of images //image loading should not be failed to show rotate images - if (MimeTypeUtil.isJpgOrPngFile(getFile().getFileName())) { + if (!getFile().isEncrypted() && MimeTypeUtil.isJpgOrPngFile(getFile().getFileName()) + && !isImageLoadingFailed && binding.emptyListProgress.getVisibility() == View.GONE) { additionalFilter.add(R.id.action_rotate_image); } From 8f79c876a37313822417261a39974c102cfacf32 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Wed, 6 Jul 2022 15:21:03 +0530 Subject: [PATCH 05/12] Cherry pick commit id - 0c0a98d. --- .../documentscan/DocumentScanViewModel.kt | 3 +- .../client/files/downloader/UploadTask.kt | 1 + .../nextcloud/client/jobs/FilesSyncWork.kt | 3 +- .../client/jobs/FilesUploadWorker.kt | 1 + .../nmc/android/jobs/UploadImagesWorker.kt | 3 +- .../com/owncloud/android/db/OCUpload.java | 18 ++++- .../android/files/services/FileUploader.java | 69 ++++++++++--------- .../operations/UploadFileOperation.java | 27 +++++++- .../ui/activity/FileDisplayActivity.java | 6 +- 9 files changed, 89 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt b/app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt index 31e5c348194e..a46081908761 100644 --- a/app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt +++ b/app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt @@ -199,7 +199,8 @@ class DocumentScanViewModel @Inject constructor( UploadFileOperation.CREATED_BY_USER, false, false, - NameCollisionPolicy.ASK_USER + NameCollisionPolicy.ASK_USER, + false ) } diff --git a/app/src/main/java/com/nextcloud/client/files/downloader/UploadTask.kt b/app/src/main/java/com/nextcloud/client/files/downloader/UploadTask.kt index 6c13436a5438..b75937df9d31 100644 --- a/app/src/main/java/com/nextcloud/client/files/downloader/UploadTask.kt +++ b/app/src/main/java/com/nextcloud/client/files/downloader/UploadTask.kt @@ -87,6 +87,7 @@ class UploadTask( upload.isUseWifiOnly, upload.isWhileChargingOnly, false, + false, fileDataStorageManager ) val client = clientProvider() diff --git a/app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt b/app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt index 99f8cf78cd6b..8fc1a48ad73a 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt @@ -180,7 +180,8 @@ class FilesSyncWork( UploadFileOperation.CREATED_AS_INSTANT_PICTURE, needsWifi, needsCharging, - syncedFolder.nameCollisionPolicy + syncedFolder.nameCollisionPolicy, + false ) for (path in paths) { diff --git a/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt index 3e57d5ba9ced..6d69d7ca5187 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt @@ -142,6 +142,7 @@ class FilesUploadWorker( upload.isUseWifiOnly, upload.isWhileChargingOnly, true, + false, FileDataStorageManager(user, context.contentResolver) ).apply { addDataTransferProgressListener(this@FilesUploadWorker) diff --git a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt index 8a63771c357c..bb43a06b9e76 100644 --- a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt +++ b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt @@ -122,7 +122,8 @@ class UploadImagesWorker constructor( UploadFileOperation.CREATED_BY_USER, false, false, - NameCollisionPolicy.OVERWRITE //overwrite the images + NameCollisionPolicy.OVERWRITE, //overwrite the images + true ) } } diff --git a/app/src/main/java/com/owncloud/android/db/OCUpload.java b/app/src/main/java/com/owncloud/android/db/OCUpload.java index cf1917f64dc1..298aba9d28d1 100644 --- a/app/src/main/java/com/owncloud/android/db/OCUpload.java +++ b/app/src/main/java/com/owncloud/android/db/OCUpload.java @@ -129,6 +129,8 @@ public class OCUpload implements Parcelable { private long fixedUploadEndTimeStamp; private long fixedUploadId; + private boolean isRotatedImages; + /** * Main constructor. * @@ -180,6 +182,7 @@ private void resetData() { useWifiOnly = true; whileChargingOnly = false; folderUnlockToken = ""; + isRotatedImages = false; } public void setDataFixed(FileUploader.FileUploaderBinder binder) { @@ -291,6 +294,7 @@ private void readFromParcel(Parcel source) { useWifiOnly = source.readInt() == 1; whileChargingOnly = source.readInt() == 1; folderUnlockToken = source.readString(); + isRotatedImages = source.readInt() == 1; } @Override @@ -316,7 +320,8 @@ public boolean isSame(@Nullable Object obj) { createdBy == other.createdBy && useWifiOnly == other.useWifiOnly && whileChargingOnly == other.whileChargingOnly && - folderUnlockToken.equals(other.folderUnlockToken); + folderUnlockToken.equals(other.folderUnlockToken) && + isRotatedImages == other.isRotatedImages; } @Override @@ -335,6 +340,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(useWifiOnly ? 1 : 0); dest.writeInt(whileChargingOnly ? 1 : 0); dest.writeString(folderUnlockToken); + dest.writeInt(isRotatedImages ? 1 : 0); } public long getUploadId() { @@ -444,4 +450,14 @@ public void setWhileChargingOnly(boolean whileChargingOnly) { public void setFolderUnlockToken(String folderUnlockToken) { this.folderUnlockToken = folderUnlockToken; } + + public boolean isRotatedImages() { + return isRotatedImages; + } + + public void setRotatedImages(boolean rotatedImages) { + isRotatedImages = rotatedImages; + } + + enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR} } diff --git a/app/src/main/java/com/owncloud/android/files/services/FileUploader.java b/app/src/main/java/com/owncloud/android/files/services/FileUploader.java index c5a3de0cf382..eb67e70b5fc7 100644 --- a/app/src/main/java/com/owncloud/android/files/services/FileUploader.java +++ b/app/src/main/java/com/owncloud/android/files/services/FileUploader.java @@ -186,6 +186,11 @@ public class FileUploader extends Service */ public static final String KEY_DISABLE_RETRIES = "DISABLE_RETRIES"; + /** + * Set to true if the image files are uploading after rotating them + */ + public static final String KEY_ROTATED_IMAGE = "ROTATED_IMAGE"; + public static final int LOCAL_BEHAVIOUR_COPY = 0; public static final int LOCAL_BEHAVIOUR_MOVE = 1; public static final int LOCAL_BEHAVIOUR_FORGET = 2; @@ -431,6 +436,7 @@ private Integer gatherAndStartNewUploads( boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false); int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER); boolean disableRetries = intent.getBooleanExtra(KEY_DISABLE_RETRIES, true); + boolean isRotatedImages = intent.getBooleanExtra(KEY_ROTATED_IMAGE, false); try { for (OCFile file : files) { startNewUpload( @@ -443,7 +449,8 @@ private Integer gatherAndStartNewUploads( isCreateRemoteFolder, createdBy, file, - disableRetries + disableRetries, + isRotatedImages ); } } catch (IllegalArgumentException e) { @@ -473,7 +480,8 @@ private void startNewUpload( boolean isCreateRemoteFolder, int createdBy, OCFile file, - boolean disableRetries + boolean disableRetries, + boolean isRotatedImages ) { OCUpload ocUpload = new OCUpload(file, user); ocUpload.setFileSize(file.getFileLength()); @@ -484,6 +492,7 @@ private void startNewUpload( ocUpload.setUseWifiOnly(onWifiOnly); ocUpload.setWhileChargingOnly(whileChargingOnly); ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS); + ocUpload.setRotatedImages(isRotatedImages); UploadFileOperation newUpload = new UploadFileOperation( mUploadsStorageManager, @@ -498,6 +507,7 @@ private void startNewUpload( onWifiOnly, whileChargingOnly, disableRetries, + isRotatedImages, new FileDataStorageManager(user, getContentResolver()) ); newUpload.setCreatedBy(createdBy); @@ -534,6 +544,7 @@ private void retryUploads(Intent intent, User user, List requestedUpload onWifiOnly = upload.isUseWifiOnly(); whileChargingOnly = upload.isWhileChargingOnly(); + boolean isRotateImages = upload.isRotatedImages(); UploadFileOperation newUpload = new UploadFileOperation( mUploadsStorageManager, @@ -548,6 +559,7 @@ private void retryUploads(Intent intent, User user, List requestedUpload onWifiOnly, whileChargingOnly, true, + isRotateImages, new FileDataStorageManager(user, getContentResolver()) ); @@ -895,8 +907,9 @@ public static void uploadNewFile( createdBy, requiresWifi, requiresCharging, - nameCollisionPolicy - ); + nameCollisionPolicy, + false + ); } /** @@ -913,40 +926,28 @@ public static void uploadNewFile( int createdBy, boolean requiresWifi, boolean requiresCharging, - NameCollisionPolicy nameCollisionPolicy + NameCollisionPolicy nameCollisionPolicy, + boolean isRotatedImages ) { + Intent intent = new Intent(context, FileUploader.class); + intent.putExtra(FileUploader.KEY_ACCOUNT, user.toPlatformAccount()); + intent.putExtra(FileUploader.KEY_USER, user); + intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths); + intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths); + intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes); + intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour); + intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder); + intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy); + intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi); + intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging); + intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy); + intent.putExtra(FileUploader.KEY_ROTATED_IMAGE, isRotatedImages); - if (useFilesUploadWorker(context)) { - new FilesUploadHelper().uploadNewFiles(user, - localPaths, - remotePaths, - createRemoteFolder, - createdBy, - requiresWifi, - requiresCharging, - nameCollisionPolicy, - behaviour); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.startForegroundService(intent); } else { - Intent intent = new Intent(context, FileUploader.class); - - intent.putExtra(FileUploader.KEY_ACCOUNT, user.toPlatformAccount()); - intent.putExtra(FileUploader.KEY_USER, user); - intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths); - intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths); - intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes); - intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour); - intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder); - intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy); - intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi); - intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging); - intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.startForegroundService(intent); - } else { - context.startService(intent); - } + context.startService(intent); } } diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index b2d92a156db7..6bea6376765c 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -121,6 +121,7 @@ public class UploadFileOperation extends SyncOperation { private boolean mWhileChargingOnly; private boolean mIgnoringPowerSaveMode; private final boolean mDisableRetries; + private final boolean mIsRotatedImages; private boolean mWasRenamed; private long mOCUploadId; @@ -196,6 +197,7 @@ public UploadFileOperation(UploadsStorageManager uploadsStorageManager, onWifiOnly, whileChargingOnly, true, + false, storageManager); } @@ -211,6 +213,7 @@ public UploadFileOperation(UploadsStorageManager uploadsStorageManager, boolean onWifiOnly, boolean whileChargingOnly, boolean disableRetries, + boolean isRotatedImages, FileDataStorageManager storageManager) { super(storageManager); @@ -251,6 +254,7 @@ public UploadFileOperation(UploadsStorageManager uploadsStorageManager, mIgnoringPowerSaveMode = mCreatedBy == CREATED_BY_USER; mFolderUnlockToken = upload.getFolderUnlockToken(); mDisableRetries = disableRetries; + mIsRotatedImages = isRotatedImages; } public boolean isWifiRequired() { @@ -1367,7 +1371,28 @@ private void saveUploadedFile(OwnCloudClient client) { // coincidence; nothing else is needed, the storagePath is right // in the instance returned by mCurrentUpload.getFile() } - file.setUpdateThumbnailNeeded(true); + + //if uploaded file is image then set the preview available as true + //this will be used to update the rotated image thumbnail for media view + //since media view listing depends upon then storage manager + //so we have to update this flag in local db + if (MimeTypeUtil.isImage(file.getMimeType())) { + //preview available true is required so that thumbnail gets updated automatically + //and user doesn't has to refresh the list manually + file.setPreviewAvailable(true); + } + + //setUpdateThumbnailNeeded FALSE + //if the file is media file set thumbnail needed flag as false + //as this will avoid showing shimmer in OCFileListAdapter on refresh + //as we are creating thumbnail over here as well and also in OCFileListAdapter if not available + //this is specially for MediaView + + //setUpdateThumbnailNeeded TRUE + //for other files we can set the thumbnail needed true + //this is the previous logic from NextCloud + file.setUpdateThumbnailNeeded(!mIsRotatedImages); + getStorageManager().saveFile(file); getStorageManager().saveConflict(file, null); diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index dcbfde829a8c..eefa0cd95343 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -79,7 +79,6 @@ import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; import com.owncloud.android.files.services.NameCollisionPolicy; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperation; @@ -957,7 +956,8 @@ private void requestUploadOfFilesFromFileSystem(String localBasePath, String[] f UploadFileOperation.CREATED_BY_USER, false, false, - NameCollisionPolicy.ASK_USER + NameCollisionPolicy.ASK_USER, + false ); } else { @@ -1657,7 +1657,7 @@ public void onServiceConnected(ComponentName component, IBinder service) { } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { Log_OC.d(TAG, "Upload service connected"); - mUploaderBinder = (FileUploaderBinder) service; + mUploaderBinder = (FileUploader.FileUploaderBinder) service; } else { return; } From 8451d72db23146b80378ecb11e2d8ab26e11eaba Mon Sep 17 00:00:00 2001 From: A117870935 Date: Fri, 24 Mar 2023 15:09:02 +0530 Subject: [PATCH 06/12] Fixed rotate image functionality with new NC code changes. --- .../client/jobs/BackgroundJobFactory.kt | 1 + .../nmc/android/jobs/UploadImagesWorker.kt | 6 ++-- .../java/com/owncloud/android/MainApp.java | 4 +++ .../ui/fragment/OCFileListFragment.java | 8 +++++ .../ui/preview/PreviewImageActivity.java | 4 ++- .../ui/preview/PreviewImageFragment.java | 30 +++++++++++++++---- app/src/main/res/values-de/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 8 files changed, 47 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt index 1e185d32189f..970c3f4ddf09 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -107,6 +107,7 @@ class BackgroundJobFactory @Inject constructor( FilesExportWork::class -> createFilesExportWork(context, workerParameters) FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters) GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters) + UploadImagesWorker::class -> createUploadImagesWork(context, workerParameters) else -> null // caller falls back to default factory } } diff --git a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt index bb43a06b9e76..edc749d93799 100644 --- a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt +++ b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt @@ -57,7 +57,7 @@ class UploadImagesWorker constructor( val fileNameWithoutExt: String = fileName.replace(extension, "") //if extension is jpg then save the image as jpg - if (extension == ".jpg" || extension == ".jpeg") { + if (extension.lowercase() == ".jpg" || extension.lowercase() == ".jpeg") { val jpgFile = FileUtils.saveJpgImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) //if file is available on local then rewrite the file as well @@ -67,7 +67,7 @@ class UploadImagesWorker constructor( onImageSaveSuccess(value, jpgFile) //if extension is png then save the image as png - } else if (extension == ".png") { + } else if (extension.lowercase() == ".png") { val pngFile = FileUtils.savePngImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) //if file is available on local then rewrite the file as well @@ -82,7 +82,7 @@ class UploadImagesWorker constructor( notificationManager.cancel(pushNotificationId) //upload image files - if (!savedFiles.isNullOrEmpty() && !remotePaths.isNullOrEmpty()) { + if (savedFiles.isNotEmpty() && remotePaths.isNotEmpty()) { uploadImageFiles() } diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java index 5cfe64a065ac..dc3156614297 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.java +++ b/app/src/main/java/com/owncloud/android/MainApp.java @@ -607,6 +607,10 @@ public static void notificationChannels() { createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL, R.string .notification_channel_general_name, R.string.notification_channel_general_description, context, NotificationManager.IMPORTANCE_DEFAULT); + + createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_SCAN_DOC_SAVE, + R.string.notification_channel_scan_doc_save, + R.string.notification_channel_scan_doc_save_description, context); } else { Log_OC.e(TAG, "Notification manager is null"); } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 32c2d5338903..d6b5f5402375 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -1379,6 +1379,14 @@ public void listDirectory(OCFile directory, OCFile file, boolean onlyOnDevice, b handleSearchEvent(searchEvent); mRefreshListLayout.setRefreshing(false); } + + //Notify the adapter only for Gallery + //this will be used when user rotated the image and come back + //so we have to update the thumbnail of the rotated image + //this method will also be called when uploading of the any file (rotated image) is finished + if (searchEvent != null && searchEvent.getSearchType() == SearchRemoteOperation.SearchType.PHOTO_SEARCH && getRecyclerView().getAdapter() != null){ + getRecyclerView().getAdapter().notifyDataSetChanged(); + } } public void updateOCFile(OCFile file) { diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java index 86236bc55d0b..90574931947d 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -274,7 +274,9 @@ protected ServiceConnection newTransferenceServiceConnection() { @Override public void onImageLoadCompleted() { - mViewPager.setBackgroundColor(getResources().getColor(R.color.background_color_inverse)); + if (mViewPager != null) { + mViewPager.setBackgroundColor(getResources().getColor(R.color.background_color_inverse)); + } } /** diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index 62d0c8fecc1a..b42b29ab8530 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -144,8 +144,8 @@ public class PreviewImageFragment extends FileFragment implements Injectable { private int currentIndex; private boolean isRotationInProgress; private boolean isImageLoadingFailed;//flag to check if image loading is failed or not - private ExecutorService rotationExecutorService = Executors.newSingleThreadExecutor(); - private Handler rotationHandler = HandlerCompat.createAsync(Looper.getMainLooper()); + private final ExecutorService rotationExecutorService = Executors.newSingleThreadExecutor(); + private final Handler rotationHandler = HandlerCompat.createAsync(Looper.getMainLooper()); /** * Public factory method to create a new fragment that previews an image. @@ -444,8 +444,8 @@ private void showFileActions(OCFile file) { //get the rotated bitmap from hashmap if (requireActivity() instanceof PreviewImageActivity) { Bitmap rotatedBitmap = ((PreviewImageActivity) requireActivity()).getCurrentBitmap(currentIndex); - //check if it should not be null - if (rotatedBitmap != null) { + //check if bitmap is null then hide the share menu + if (rotatedBitmap == null) { additionalFilter.add(R.id.action_send_share_file); } } @@ -454,8 +454,8 @@ private void showFileActions(OCFile file) { //enable rotate functionality if image is png or jpg //we are not rotating svg, gif or any other format of images //image loading should not be failed to show rotate images - if (!getFile().isEncrypted() && MimeTypeUtil.isJpgOrPngFile(getFile().getFileName()) - && !isImageLoadingFailed && binding.emptyListProgress.getVisibility() == View.GONE) { + if (getFile().isEncrypted() || !MimeTypeUtil.isJpgOrPngFile(getFile().getFileName()) + || isImageLoadingFailed || binding.emptyListProgress.getVisibility() == View.VISIBLE) { additionalFilter.add(R.id.action_rotate_image); } @@ -522,6 +522,24 @@ private void rotate() { //make copy of rotated bitmap to avoid issue during recycle bitmap = rotatedBitmap.copy(rotatedBitmap.getConfig(), true); + //rotate the cached thumbnail for this image + + //1. Get the thumbnail + Bitmap thumbnailBitmap = getThumbnailBitmap(getFile()); + if (thumbnailBitmap != null) { + //2. Rotate the thumbnail + Bitmap rotatedThumbBitmap = Bitmap.createBitmap(thumbnailBitmap, 0, 0, thumbnailBitmap.getWidth(), thumbnailBitmap.getHeight(), + matrix, true); + + //3. Add the rotated thumbnail back to cache + ThumbnailsCacheManager.addBitmapToCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId(), rotatedThumbBitmap); + } + + //if image resized is enabled then add the current rotated bitmap image to cache + if (showResizedImage) { + ThumbnailsCacheManager.addBitmapToCache(ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId(), rotatedBitmap); + } + rotationHandler.post(() -> { //set the rotated bitmap to image view binding.image.setImageBitmap(bitmap); diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3b096fc7f821..2123becc70e8 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -508,6 +508,8 @@ Push-Benachrichtigungen Zeigt den Hochlade-Fortschritt an Uploads + Gescannte Documente speichern Benachrichtigungskanal + Speicherprozess zeigen Benachrichtigungssymbol Keine Benachrichtigungen Bitte später noch einmal nachsehen. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e9cd5b7e231..77042dd9e5d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -688,6 +688,8 @@ Music player progress File sync Shows file sync progress and results + Save scanned documents notification channel + Shows save progress Account not found! From 69dc8caa436e4cd1cead35ed702dea1ae9b6e4ac Mon Sep 17 00:00:00 2001 From: A117870935 Date: Fri, 24 Mar 2023 15:29:37 +0530 Subject: [PATCH 07/12] Rotate menu hidden for other overflow options except image preview. --- .../main/java/com/owncloud/android/files/FileMenuFilter.java | 2 ++ .../com/owncloud/android/ui/fragment/FileDetailFragment.java | 3 ++- .../com/owncloud/android/ui/preview/PreviewMediaFragment.java | 3 ++- .../owncloud/android/ui/preview/PreviewTextFileFragment.java | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java index 58dc009e5bb0..b2f8b99424d1 100644 --- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -180,6 +180,8 @@ private List filter(boolean inSingleFileFragment) { filterLock(toHide, fileLockingEnabled); filterUnlock(toHide, fileLockingEnabled); filterPinToHome(toHide); + //always hidden for other overflow options + toHide.add(R.id.action_rotate_image); return toHide; } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index f4034efa1f23..c1646ed00596 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -282,7 +282,8 @@ private void onOverflowIconClicked() { R.id.action_copy, R.id.action_stream_media, R.id.action_send_share_file, - R.id.action_select_all_action_menu)); + R.id.action_select_all_action_menu, + R.id.action_rotate_image)); if (getFile().isFolder()) { additionalFilter.add(R.id.action_send_file); additionalFilter.add(R.id.action_sync_file); diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java index f8b012551d3d..25f871362c66 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -409,7 +409,8 @@ private void showFileActions(OCFile file) { R.id.action_move, R.id.action_copy, R.id.action_favorite, - R.id.action_unset_favorite + R.id.action_unset_favorite, + R.id.action_rotate_image )); if (getFile() != null && getFile().isSharedWithMe() && !getFile().canReshare()) { additionalFilter.add(R.id.action_send_share_file); diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java index a26988735df6..3b08739645ff 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java @@ -304,7 +304,8 @@ private void showFileActions(OCFile file) { R.id.action_move, R.id.action_copy, R.id.action_favorite, - R.id.action_unset_favorite + R.id.action_unset_favorite, + R.id.action_rotate_image )); if (getFile() != null && getFile().isSharedWithMe() && !getFile().canReshare()) { additionalFilter.add(R.id.action_send_share_file); From 87d5ba762ea58a4f02940e198e585b4440fc6799 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Fri, 24 Mar 2023 18:26:20 +0530 Subject: [PATCH 08/12] Fixed rotate option visibility. --- .../java/com/owncloud/android/files/FileMenuFilter.java | 2 -- .../owncloud/android/ui/fragment/OCFileListFragment.java | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java index b2f8b99424d1..58dc009e5bb0 100644 --- a/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/app/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -180,8 +180,6 @@ private List filter(boolean inSingleFileFragment) { filterLock(toHide, fileLockingEnabled); filterUnlock(toHide, fileLockingEnabled); filterPinToHome(toHide); - //always hidden for other overflow options - toHide.add(R.id.action_rotate_image); return toHide; } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index d6b5f5402375..e9d95a4123fe 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -124,7 +124,9 @@ import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -609,8 +611,10 @@ public void onOverflowIconClicked(OCFile file, View view) { public void openActionsMenu(final int filesCount, final Set checkedFiles, final boolean isOverflow) { throttler.run("overflowClick", () -> { + final List additionalFilter = new ArrayList<>( + Collections.singletonList(R.id.action_rotate_image)); final FragmentManager childFragmentManager = getChildFragmentManager(); - FileActionsBottomSheet.newInstance(filesCount, checkedFiles, isOverflow) + FileActionsBottomSheet.newInstance(filesCount, checkedFiles, isOverflow, additionalFilter) .setResultListener(childFragmentManager, this, (id) -> { onFileActionChosen(id, checkedFiles); }) From 36e6c0b68a2f864fbcb2fc33c60cd3bacaf89c10 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Fri, 7 Apr 2023 14:25:42 +0530 Subject: [PATCH 09/12] Added test case. --- .../com/nmc/android/utils/MimeTypeUtilTest.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 app/src/test/java/com/nmc/android/utils/MimeTypeUtilTest.kt diff --git a/app/src/test/java/com/nmc/android/utils/MimeTypeUtilTest.kt b/app/src/test/java/com/nmc/android/utils/MimeTypeUtilTest.kt new file mode 100644 index 000000000000..8f302bd618da --- /dev/null +++ b/app/src/test/java/com/nmc/android/utils/MimeTypeUtilTest.kt @@ -0,0 +1,26 @@ +package com.nmc.android.utils + +import com.owncloud.android.utils.MimeTypeUtil +import org.junit.Assert.assertEquals +import org.junit.Test + +class MimeTypeUtilTest { + + @Test + fun isJpgOrPngFileTest() { + assertEquals(true, MimeTypeUtil.isJpgOrPngFile(".jpg")) + assertEquals(true, MimeTypeUtil.isJpgOrPngFile(".png")) + assertEquals(true, MimeTypeUtil.isJpgOrPngFile(".jpeg")) + + assertEquals(true, MimeTypeUtil.isJpgOrPngFile("example.jpg")) + assertEquals(true, MimeTypeUtil.isJpgOrPngFile("example.png")) + assertEquals(true, MimeTypeUtil.isJpgOrPngFile("example.jpeg")) + + assertEquals(true, MimeTypeUtil.isJpgOrPngFile("example.JPG")) + assertEquals(true, MimeTypeUtil.isJpgOrPngFile("example.PNG")) + assertEquals(true, MimeTypeUtil.isJpgOrPngFile("example.JPEG")) + + assertEquals(false, MimeTypeUtil.isJpgOrPngFile(".gif")) + assertEquals(false, MimeTypeUtil.isJpgOrPngFile("example.gif")) + } +} \ No newline at end of file From 9e099ddd190d70ab929d2a1762100ef25a809dea Mon Sep 17 00:00:00 2001 From: A117870935 Date: Thu, 25 May 2023 21:48:31 +0530 Subject: [PATCH 10/12] Rotate function extended to all possible images. --- .../com/nmc/android/jobs/UploadImagesWorker.kt | 14 ++++++++++---- .../android/ui/preview/PreviewImageFragment.java | 10 +++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt index edc749d93799..edd5e00ccfb1 100644 --- a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt +++ b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt @@ -66,13 +66,19 @@ class UploadImagesWorker constructor( } onImageSaveSuccess(value, jpgFile) - //if extension is png then save the image as png - } else if (extension.lowercase() == ".png") { - val pngFile = FileUtils.savePngImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) + //if extension is other than jpg/jpeg save the image as png + } else { + val pngFile = + FileUtils.savePngImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) //if file is available on local then rewrite the file as well if (value.ocFile.isDown) { - FileUtils.savePngImage(context, value.bitmap, File(value.ocFile.storagePath), IMAGE_COMPRESSION_PERCENTAGE) + FileUtils.savePngImage( + context, + value.bitmap, + File(value.ocFile.storagePath), + IMAGE_COMPRESSION_PERCENTAGE + ) } onImageSaveSuccess(value, pngFile) } diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index b42b29ab8530..b251229211d1 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -25,7 +25,6 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; @@ -73,8 +72,6 @@ import com.owncloud.android.utils.MimeTypeUtil; import com.owncloud.android.utils.theme.ViewThemeUtils; -import org.jetbrains.annotations.NotNull; - import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -451,10 +448,10 @@ private void showFileActions(OCFile file) { } //rotate functionality will not be available for encrypted files - //enable rotate functionality if image is png or jpg - //we are not rotating svg, gif or any other format of images + //enable rotate functionality if mime type is image + //bitmap should be available to rotate the image //image loading should not be failed to show rotate images - if (getFile().isEncrypted() || !MimeTypeUtil.isJpgOrPngFile(getFile().getFileName()) + if (bitmap == null || getFile().isEncrypted() || !MimeTypeUtil.isImage(getFile()) || isImageLoadingFailed || binding.emptyListProgress.getVisibility() == View.VISIBLE) { additionalFilter.add(R.id.action_rotate_image); } @@ -509,7 +506,6 @@ private void rotate() { if (System.currentTimeMillis() - lastRotationEventTs < 350) { return; } - showHideViewDuringRotation(true); //execute the rotation task in background From 3682d1160be96e0fdcc2717f59ff651f0f6b3e55 Mon Sep 17 00:00:00 2001 From: A117870935 Date: Thu, 25 May 2023 22:26:16 +0530 Subject: [PATCH 11/12] Hide rotate option for online files. --- app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt | 2 +- .../com/owncloud/android/ui/preview/PreviewImageFragment.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt index edd5e00ccfb1..145e11851371 100644 --- a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt +++ b/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt @@ -123,7 +123,7 @@ class UploadImagesWorker constructor( savedFiles.toTypedArray(), remotePaths.toTypedArray(), null, // MIME type will be detected from file name - FileUploader.LOCAL_BEHAVIOUR_DELETE, + FileUploader.LOCAL_BEHAVIOUR_MOVE, //move the local file to make file offline available false, // do not create parent folder if not existent UploadFileOperation.CREATED_BY_USER, false, diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index b251229211d1..84258a40b5af 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -451,8 +451,10 @@ private void showFileActions(OCFile file) { //enable rotate functionality if mime type is image //bitmap should be available to rotate the image //image loading should not be failed to show rotate images + //only applicable to files which are available offline if (bitmap == null || getFile().isEncrypted() || !MimeTypeUtil.isImage(getFile()) - || isImageLoadingFailed || binding.emptyListProgress.getVisibility() == View.VISIBLE) { + || isImageLoadingFailed || binding.emptyListProgress.getVisibility() == View.VISIBLE + || !getFile().isDown()) { additionalFilter.add(R.id.action_rotate_image); } From 62b1be85f83a5ffccee8c2422e761e9e597766ff Mon Sep 17 00:00:00 2001 From: A117870935 Date: Fri, 23 Jun 2023 18:07:20 +0530 Subject: [PATCH 12/12] Code optimisation, copyright added and rebased. --- .../documentscan/DocumentScanViewModel.kt | 2 + .../client/files/downloader/UploadTask.kt | 2 + .../client/jobs/BackgroundJobFactory.kt | 24 +-- .../client/jobs/BackgroundJobManager.kt | 5 +- .../client/jobs/BackgroundJobManagerImpl.kt | 22 +-- .../nextcloud/client/jobs/FilesSyncWork.kt | 2 + .../client/jobs/FilesUploadWorker.kt | 2 + .../client}/jobs/UploadImagesWorker.kt | 67 +++++--- .../nextcloud/ui/fileactions/FileAction.kt | 2 + .../java/com/nmc/android/utils/FileUtils.java | 146 ------------------ .../java/com/owncloud/android/MainApp.java | 6 +- .../datamodel/ThumbnailsCacheManager.java | 2 + .../com/owncloud/android/db/OCUpload.java | 2 + .../android/files/services/FileUploader.java | 2 + .../operations/UploadFileOperation.java | 2 + .../ui/activity/FileDisplayActivity.java | 9 +- .../ui/fragment/FileDetailFragment.java | 2 +- .../ui/fragment/OCFileListFragment.java | 12 +- .../ui/notifications/NotificationUtils.java | 5 +- .../ui/preview/PreviewImageActivity.java | 117 +++++++------- .../ui/preview/PreviewImageFragment.java | 49 +++--- .../ui/preview/PreviewImagePagerAdapter.java | 2 + .../ui/preview/PreviewMediaFragment.java | 6 +- .../ui/preview/PreviewTextFileFragment.java | 6 +- .../com/owncloud/android/utils/FileUtil.java | 79 ++++++++++ .../owncloud/android/utils/MimeTypeUtil.java | 8 - app/src/main/res/drawable/ic_rotate_right.xml | 1 + .../res/layout/preview_image_activity.xml | 1 - .../res/layout/preview_image_fragment.xml | 4 +- app/src/main/res/values-b+en+001/strings.xml | 41 ----- app/src/main/res/values-de/strings.xml | 101 +----------- app/src/main/res/values/strings.xml | 9 +- .../com/nmc/android/utils/MimeTypeUtilTest.kt | 26 ---- 33 files changed, 299 insertions(+), 467 deletions(-) rename app/src/main/java/com/{nmc/android => nextcloud/client}/jobs/UploadImagesWorker.kt (63%) delete mode 100644 app/src/main/java/com/nmc/android/utils/FileUtils.java delete mode 100644 app/src/test/java/com/nmc/android/utils/MimeTypeUtilTest.kt diff --git a/app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt b/app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt index a46081908761..00a1bd9a4871 100644 --- a/app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt +++ b/app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt @@ -2,8 +2,10 @@ * Nextcloud Android client application * * @author Álvaro Brey + * @author TSI-mc * Copyright (C) 2022 Álvaro Brey * Copyright (C) 2022 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 diff --git a/app/src/main/java/com/nextcloud/client/files/downloader/UploadTask.kt b/app/src/main/java/com/nextcloud/client/files/downloader/UploadTask.kt index b75937df9d31..da99bde1dc43 100644 --- a/app/src/main/java/com/nextcloud/client/files/downloader/UploadTask.kt +++ b/app/src/main/java/com/nextcloud/client/files/downloader/UploadTask.kt @@ -2,7 +2,9 @@ * Nextcloud Android client application * * @author Chris Narkiewicz + * @author TSI-mc * Copyright (C) 2021 Chris Narkiewicz + * 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 diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt index 970c3f4ddf09..6b5ca09391e7 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -2,7 +2,9 @@ * Nextcloud Android client application * * @author Chris Narkiewicz + * @author TSI-mc * Copyright (C) 2020 Chris Narkiewicz + * 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 @@ -40,7 +42,6 @@ import com.nextcloud.client.integrations.deck.DeckApi import com.nextcloud.client.logger.Logger import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.AppPreferences -import com.nmc.android.jobs.UploadImagesWorker import com.owncloud.android.datamodel.ArbitraryDataProvider import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.UploadsStorageManager @@ -106,8 +107,8 @@ class BackgroundJobFactory @Inject constructor( CalendarImportWork::class -> createCalendarImportWork(context, workerParameters) FilesExportWork::class -> createFilesExportWork(context, workerParameters) FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters) - GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters) UploadImagesWorker::class -> createUploadImagesWork(context, workerParameters) + GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters) else -> null // caller falls back to default factory } } @@ -259,6 +260,16 @@ class BackgroundJobFactory @Inject constructor( ) } + private fun createUploadImagesWork(context: Context, params: WorkerParameters): UploadImagesWorker { + return UploadImagesWorker( + context = context, + params = params, + notificationManager, + accountManager, + viewThemeUtils.get() + ) + } + private fun createPDFGenerateWork(context: Context, params: WorkerParameters): GeneratePdfFromImagesWork { return GeneratePdfFromImagesWork( appContext = context, @@ -270,13 +281,4 @@ class BackgroundJobFactory @Inject constructor( params = params ) } - - private fun createUploadImagesWork(context: Context, params: WorkerParameters): UploadImagesWorker { - return UploadImagesWorker( - context = context, - params = params, - notificationManager, - accountManager - ) - } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt index cdb1fcd84083..0adbbecd5862 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt @@ -2,7 +2,9 @@ * Nextcloud Android client application * * @author Chris Narkiewicz + * @author TSI-mc * Copyright (C) 2020 Chris Narkiewicz + * 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 @@ -140,11 +142,10 @@ interface BackgroundJobManager { fun startAccountRemovalJob(accountName: String, remoteWipe: Boolean) fun startFilesUploadJob(user: User) fun getFileUploads(user: User): LiveData> + fun scheduleImmediateUploadImagesJob(): LiveData fun startPdfGenerateAndUploadWork(user: User, uploadFolder: String, imagePaths: List, pdfPath: String) - fun scheduleImmediateUploadImagesJob(): LiveData - fun scheduleTestJob() fun startImmediateTestJob() fun cancelTestJob() diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt index aab8ba460c17..2243ca18a20c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt @@ -2,7 +2,9 @@ * Nextcloud Android client application * * @author Chris Narkiewicz + * @author TSI-mc * Copyright (C) 2020 Chris Narkiewicz + * 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 @@ -40,7 +42,6 @@ import com.nextcloud.client.account.User import com.nextcloud.client.core.Clock import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork import com.owncloud.android.datamodel.OCFile -import com.nmc.android.jobs.UploadImagesWorker import java.util.Date import java.util.UUID import java.util.concurrent.TimeUnit @@ -83,10 +84,9 @@ internal class BackgroundJobManagerImpl( const val JOB_ACCOUNT_REMOVAL = "account_removal" const val JOB_FILES_UPLOAD = "files_upload" const val JOB_PDF_GENERATION = "pdf_generation" + const val JOB_IMAGE_FILES_UPLOAD = "immediate_image_files_upload" const val JOB_IMMEDIATE_CALENDAR_BACKUP = "immediate_calendar_backup" const val JOB_IMMEDIATE_FILES_EXPORT = "immediate_files_export" - const val JOB_IMMEDIATE_SCAN_DOC_UPLOAD = "immediate_scan_doc_upload" - const val JOB_IMAGE_FILES_UPLOAD = "immediate_image_files_upload" const val JOB_TEST = "test_job" @@ -467,6 +467,14 @@ internal class BackgroundJobManagerImpl( return workInfo.map { it -> it.map { fromWorkInfo(it) ?: JobInfo() } } } + override fun scheduleImmediateUploadImagesJob(): LiveData { + val request = oneTimeRequestBuilder(UploadImagesWorker::class, JOB_IMAGE_FILES_UPLOAD) + .build() + + workManager.enqueueUniqueWork(JOB_IMAGE_FILES_UPLOAD, ExistingWorkPolicy.APPEND_OR_REPLACE, request) + return workManager.getJobInfo(request.id) + } + override fun startPdfGenerateAndUploadWork( user: User, uploadFolder: String, @@ -485,14 +493,6 @@ internal class BackgroundJobManagerImpl( workManager.enqueue(request) } - override fun scheduleImmediateUploadImagesJob(): LiveData { - val request = oneTimeRequestBuilder(UploadImagesWorker::class, JOB_IMAGE_FILES_UPLOAD) - .build() - - workManager.enqueueUniqueWork(JOB_IMAGE_FILES_UPLOAD, ExistingWorkPolicy.APPEND_OR_REPLACE, request) - return workManager.getJobInfo(request.id) - } - override fun scheduleTestJob() { val request = periodicRequestBuilder(TestJob::class, JOB_TEST) .setInitialDelay(DEFAULT_IMMEDIATE_JOB_DELAY_SEC, TimeUnit.SECONDS) diff --git a/app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt b/app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt index 8fc1a48ad73a..4efc6d171c79 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt @@ -3,9 +3,11 @@ * * @author Mario Danic * @author Chris Narkiewicz + * @author TSI-mc * Copyright (C) 2017 Mario Danic * Copyright (C) 2017 Nextcloud * Copyright (C) 2020 Chris Narkiewicz + * 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 diff --git a/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt index 6d69d7ca5187..cbf6cfea95c1 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt @@ -3,8 +3,10 @@ * Nextcloud Android client application * * @author Tobias Kaminsky + * @author TSI-mc * Copyright (C) 2022 Tobias Kaminsky * Copyright (C) 2022 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 diff --git a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/UploadImagesWorker.kt similarity index 63% rename from app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt rename to app/src/main/java/com/nextcloud/client/jobs/UploadImagesWorker.kt index 145e11851371..34a1e4b8c412 100644 --- a/app/src/main/java/com/nmc/android/jobs/UploadImagesWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/UploadImagesWorker.kt @@ -1,13 +1,34 @@ -package com.nmc.android.jobs +/* + * Nextcloud Android client application + * + * @author TSI-mc + * Copyright (C) 2023 TSI-mc + * Copyright (C) 2023 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 . + */ +package com.nextcloud.client.jobs import android.app.NotificationManager import android.content.Context import android.graphics.BitmapFactory +import android.os.Build import androidx.core.app.NotificationCompat import androidx.work.Worker import androidx.work.WorkerParameters import com.nextcloud.client.account.UserAccountManager -import com.nmc.android.utils.FileUtils +import com.owncloud.android.utils.FileUtil import com.owncloud.android.R import com.owncloud.android.files.services.FileUploader import com.owncloud.android.files.services.NameCollisionPolicy @@ -15,6 +36,7 @@ import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.ui.notifications.NotificationUtils import com.owncloud.android.ui.preview.PreviewImageActivity import com.owncloud.android.ui.preview.PreviewImageFragment +import com.owncloud.android.utils.theme.ViewThemeUtils import java.io.File import java.security.SecureRandom @@ -26,9 +48,12 @@ class UploadImagesWorker constructor( private val context: Context, params: WorkerParameters, private val notificationManager: NotificationManager, - private val accountManager: UserAccountManager + private val accountManager: UserAccountManager, + val viewThemeUtils: ViewThemeUtils ) : Worker(context, params) { + private val notificationBuilder: NotificationCompat.Builder = + NotificationUtils.newNotificationBuilder(context, viewThemeUtils) private val savedFiles = mutableListOf() private val remotePaths = mutableListOf() @@ -39,7 +64,6 @@ class UploadImagesWorker constructor( override fun doWork(): Result { - val bitmapHashMap: HashMap = HashMap(PreviewImageActivity.bitmapHashMap) //clear the static bitmap once the images are stored in work manager instance @@ -57,24 +81,28 @@ class UploadImagesWorker constructor( val fileNameWithoutExt: String = fileName.replace(extension, "") //if extension is jpg then save the image as jpg - if (extension.lowercase() == ".jpg" || extension.lowercase() == ".jpeg") { - val jpgFile = FileUtils.saveJpgImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) + if (extension.lowercase() == ".jpg" || extension.lowercase() == ".jpeg") { + val jpgFile = + FileUtil.saveJpgImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) //if file is available on local then rewrite the file as well if (value.ocFile.isDown) { - FileUtils.saveJpgImage(context, value.bitmap, File(value.ocFile.storagePath), IMAGE_COMPRESSION_PERCENTAGE) + FileUtil.saveJpgImage( + value.bitmap, + File(value.ocFile.storagePath), + IMAGE_COMPRESSION_PERCENTAGE + ) } onImageSaveSuccess(value, jpgFile) //if extension is other than jpg/jpeg save the image as png } else { val pngFile = - FileUtils.savePngImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) + FileUtil.savePngImage(context, value.bitmap, fileNameWithoutExt, IMAGE_COMPRESSION_PERCENTAGE) //if file is available on local then rewrite the file as well if (value.ocFile.isDown) { - FileUtils.savePngImage( - context, + FileUtil.savePngImage( value.bitmap, File(value.ocFile.storagePath), IMAGE_COMPRESSION_PERCENTAGE @@ -82,7 +110,6 @@ class UploadImagesWorker constructor( } onImageSaveSuccess(value, pngFile) } - } notificationManager.cancel(pushNotificationId) @@ -104,14 +131,16 @@ class UploadImagesWorker constructor( } private fun showNotification(pushNotificationId: Int) { - val notificationBuilder = - NotificationCompat.Builder(context, NotificationUtils.NOTIFICATION_CHANNEL_SCAN_DOC_SAVE) - .setSmallIcon(R.drawable.notification_icon) - .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon)) - //.setColor(ThemeUtils.primaryColor(context, true)) - .setContentTitle(context.resources.getString(R.string.app_name)) - .setContentText(context.resources.getString(R.string.foreground_service_save)) - .setAutoCancel(false) + notificationBuilder + .setSmallIcon(R.drawable.notification_icon) + .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon)) + .setContentTitle(context.resources.getString(R.string.app_name)) + .setContentText(context.resources.getString(R.string.foreground_service_save)) + .setAutoCancel(false) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_IMAGE_SAVE) + } notificationManager.notify(pushNotificationId, notificationBuilder.build()) } diff --git a/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt b/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt index 63847246fc31..c75d90162e96 100644 --- a/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt +++ b/app/src/main/java/com/nextcloud/ui/fileactions/FileAction.kt @@ -2,8 +2,10 @@ * Nextcloud Android client application * * @author Álvaro Brey + * @author TSI-mc * Copyright (C) 2022 Álvaro Brey * Copyright (C) 2022 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 diff --git a/app/src/main/java/com/nmc/android/utils/FileUtils.java b/app/src/main/java/com/nmc/android/utils/FileUtils.java deleted file mode 100644 index e1e097e2523d..000000000000 --- a/app/src/main/java/com/nmc/android/utils/FileUtils.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.nmc.android.utils; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Environment; -import android.text.TextUtils; -import android.util.Log; - -import com.owncloud.android.MainApp; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.helpers.FileOperationsHelper; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Locale; - -public class FileUtils { - private static final String TAG = FileUtils.class.getSimpleName(); - - private static final String SCANNED_FILE_PREFIX = "scan_"; - private static final int JPG_FILE_TYPE = 1; - private static final int PNG_FILE_TYPE = 2; - - public static File saveJpgImage(Context context, Bitmap bitmap, String imageName, int quality) { - return createFileAndSaveImage(context, bitmap, imageName, quality, JPG_FILE_TYPE); - } - - public static File savePngImage(Context context, Bitmap bitmap, String imageName, int quality) { - return createFileAndSaveImage(context, bitmap, imageName, quality, PNG_FILE_TYPE); - } - - public static File saveJpgImage(Context context, Bitmap bitmap, File file, int quality) { - return saveImage(file, bitmap, quality, JPG_FILE_TYPE); - } - - public static File savePngImage(Context context, Bitmap bitmap, File file, int quality) { - return saveImage(file, bitmap, quality, PNG_FILE_TYPE); - } - - private static File createFileAndSaveImage(Context context, Bitmap bitmap, String imageName, int quality, - int fileType) { - File file = fileType == PNG_FILE_TYPE ? getPngImageName(context, imageName) : getJpgImageName(context, - imageName); - return saveImage(file, bitmap, quality, fileType); - } - - private static File saveImage(File file, Bitmap bitmap, int quality, int fileType) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos); - byte[] bitmapData = bos.toByteArray(); - - FileOutputStream fileOutputStream = new FileOutputStream(file); - fileOutputStream.write(bitmapData); - fileOutputStream.flush(); - fileOutputStream.close(); - return file; - } catch (Exception e) { - Log_OC.e(TAG, " Failed to save image : " + e.getLocalizedMessage()); - return null; - } - } - - private static File getJpgImageName(Context context, String imageName) { - File imageFile = getOutputMediaFile(context); - if (!TextUtils.isEmpty(imageName)) { - return new File(imageFile.getPath() + File.separator + imageName + ".jpg"); - } else { - return new File(imageFile.getPath() + File.separator + "IMG_" + FileOperationsHelper.getCapturedImageName()); - } - } - - private static File getPngImageName(Context context, String imageName) { - File imageFile = getOutputMediaFile(context); - if (!TextUtils.isEmpty(imageName)) { - return new File(imageFile.getPath() + File.separator + imageName + ".png"); - } else { - return new File(imageFile.getPath() + File.separator + "IMG_" + FileOperationsHelper.getCapturedImageName().replace(".jpg", ".png")); - } - } - - private static File getTextFileName(Context context, String fileName) { - File txtFileName = getOutputMediaFile(context); - if (!TextUtils.isEmpty(fileName)) { - return new File(txtFileName.getPath() + File.separator + fileName + ".txt"); - } else { - return new File(txtFileName.getPath() + File.separator + FileOperationsHelper.getCapturedImageName().replace(".jpg", ".txt")); - } - } - - private static File getPdfFileName(Context context, String fileName) { - File pdfFileName = getOutputMediaFile(context); - if (!TextUtils.isEmpty(fileName)) { - return new File(pdfFileName.getPath() + File.separator + fileName + ".pdf"); - } else { - return new File(pdfFileName.getPath() + File.separator + FileOperationsHelper.getCapturedImageName().replace(".pdf", ".txt")); - } - } - - public static String scannedFileName() { - return SCANNED_FILE_PREFIX + new SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US).format(new Date()); - } - - public static File getOutputMediaFile(Context context) { - File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), ""); - if (!file.exists()) { - file.mkdir(); - } - return file; - } - - public static Bitmap convertFileToBitmap(File file) { - String filePath = file.getPath(); - Bitmap bitmap = BitmapFactory.decodeFile(filePath); - return bitmap; - } - - public static File writeTextToFile(Context context, String textToWrite, String fileName) { - File file = getTextFileName(context, fileName); - try { - FileWriter fileWriter = new FileWriter(file); - fileWriter.write(textToWrite); - fileWriter.flush(); - fileWriter.close(); - return file; - } catch (IOException e) { - //e.printStackTrace(); - Log_OC.e(TAG, "Failed to write file : " + e.toString()); - } - return null; - - } - -} diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.java index dc3156614297..66b951647435 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.java +++ b/app/src/main/java/com/owncloud/android/MainApp.java @@ -608,9 +608,9 @@ public static void notificationChannels() { .notification_channel_general_name, R.string.notification_channel_general_description, context, NotificationManager.IMPORTANCE_DEFAULT); - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_SCAN_DOC_SAVE, - R.string.notification_channel_scan_doc_save, - R.string.notification_channel_scan_doc_save_description, context); + createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_IMAGE_SAVE, + R.string.notification_channel_image_save, + R.string.notification_channel_image_save_description, context); } else { Log_OC.e(TAG, "Notification manager is null"); } diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 254e7157b2b4..892bdfd23d96 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -4,8 +4,10 @@ * @author Tobias Kaminsky * @author David A. Velasco * @author Chris Narkiewicz + * @author TSI-mc * Copyright (C) 2015 ownCloud Inc. * Copyright (C) 2019 Chris Narkiewicz + * Copyright (C) 2023 TSI-mc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, diff --git a/app/src/main/java/com/owncloud/android/db/OCUpload.java b/app/src/main/java/com/owncloud/android/db/OCUpload.java index 298aba9d28d1..a4ed6b5b8fdb 100644 --- a/app/src/main/java/com/owncloud/android/db/OCUpload.java +++ b/app/src/main/java/com/owncloud/android/db/OCUpload.java @@ -5,8 +5,10 @@ * @author masensio * @author David A. Velasco * @author Tobias Kaminsky + * @author TSI-mc * Copyright (C) 2016 ownCloud Inc. * Copyright (C) 2018 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 General Public License version 2, diff --git a/app/src/main/java/com/owncloud/android/files/services/FileUploader.java b/app/src/main/java/com/owncloud/android/files/services/FileUploader.java index eb67e70b5fc7..1041d59ba942 100644 --- a/app/src/main/java/com/owncloud/android/files/services/FileUploader.java +++ b/app/src/main/java/com/owncloud/android/files/services/FileUploader.java @@ -6,10 +6,12 @@ * @author LukeOwnCloud * @author David A. Velasco * @author Chris Narkiewicz + * @author TSI-mc * * Copyright (C) 2012 Bartek Przybylski * Copyright (C) 2012-2016 ownCloud Inc. * Copyright (C) 2020 Chris Narkiewicz + * Copyright (C) 2023 TSI-mc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index 6bea6376765c..8084ce28364b 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -3,8 +3,10 @@ * * @author David A. Velasco * @author Chris Narkiewicz + * @author TSI-mc * Copyright (C) 2016 ownCloud GmbH. * Copyright (C) 2020 Chris Narkiewicz + * Copyright (C) 2023 TSI-mc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index eefa0cd95343..ab9b352a912c 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -79,6 +79,7 @@ import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; import com.owncloud.android.files.services.NameCollisionPolicy; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.RemoteOperation; @@ -638,6 +639,12 @@ protected void resetTitleBarAndScrolling() { public void updateListOfFilesFragment(boolean fromSearch) { OCFileListFragment fileListFragment = getListOfFilesFragment(); + // notify the adapter when rotate image upload is success + // by updating this will refresh the gallery and will avoid creating conflict via etagInConflict + if (fileListFragment instanceof GalleryFragment) { + ((GalleryFragment) fileListFragment).showAllGalleryItems(); + } + if (fileListFragment != null) { fileListFragment.listDirectory(MainApp.isOnlyOnDevice(), fromSearch); } @@ -1657,7 +1664,7 @@ public void onServiceConnected(ComponentName component, IBinder service) { } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { Log_OC.d(TAG, "Upload service connected"); - mUploaderBinder = (FileUploader.FileUploaderBinder) service; + mUploaderBinder = (FileUploaderBinder) service; } else { return; } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index c1646ed00596..4c658b837808 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -11,7 +11,7 @@ * Copyright (C) 2016 ownCloud Inc. * Copyright (C) 2018 Andy Scherzinger * Copyright (C) 2019 Chris Narkiewicz - * Copyright (C) 2021 TSI-mc + * Copyright (C) 2023 TSI-mc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index e9d95a4123fe..f4719d98f0df 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -124,9 +124,7 @@ import java.io.File; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -611,8 +609,9 @@ public void onOverflowIconClicked(OCFile file, View view) { public void openActionsMenu(final int filesCount, final Set checkedFiles, final boolean isOverflow) { throttler.run("overflowClick", () -> { - final List additionalFilter = new ArrayList<>( - Collections.singletonList(R.id.action_rotate_image)); + final List additionalFilter = new ArrayList<>(); + //hide the rotate menu for overflow menu + additionalFilter.add(R.id.action_rotate_image); final FragmentManager childFragmentManager = getChildFragmentManager(); FileActionsBottomSheet.newInstance(filesCount, checkedFiles, isOverflow, additionalFilter) .setResultListener(childFragmentManager, this, (id) -> { @@ -1384,10 +1383,9 @@ public void listDirectory(OCFile directory, OCFile file, boolean onlyOnDevice, b mRefreshListLayout.setRefreshing(false); } - //Notify the adapter only for Gallery - //this will be used when user rotated the image and come back - //so we have to update the thumbnail of the rotated image //this method will also be called when uploading of the any file (rotated image) is finished + //even though we are updating gallery adapter from FileDisplayActivity.updateListOfFilesFragment we need to update here too + //to show the updated thumbnail after user comes back to gallery view from preview page if (searchEvent != null && searchEvent.getSearchType() == SearchRemoteOperation.SearchType.PHOTO_SEARCH && getRecyclerView().getAdapter() != null){ getRecyclerView().getAdapter().notifyDataSetChanged(); } diff --git a/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java b/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java index bf48ad2ff1e8..3170b125c3c9 100644 --- a/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java +++ b/app/src/main/java/com/owncloud/android/ui/notifications/NotificationUtils.java @@ -1,7 +1,10 @@ /* * ownCloud Android client application * + * @author TSI-mc + * * Copyright (C) 2015 ownCloud Inc. + * Copyright (C) 2023 TSI-mc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -41,7 +44,7 @@ public final class NotificationUtils { public static final String NOTIFICATION_CHANNEL_FILE_SYNC = "NOTIFICATION_CHANNEL_FILE_SYNC"; public static final String NOTIFICATION_CHANNEL_FILE_OBSERVER = "NOTIFICATION_CHANNEL_FILE_OBSERVER"; public static final String NOTIFICATION_CHANNEL_PUSH = "NOTIFICATION_CHANNEL_PUSH"; - public static final String NOTIFICATION_CHANNEL_SCAN_DOC_SAVE = "NOTIFICATION_CHANNEL_SCAN_DOC_SAVE"; + public static final String NOTIFICATION_CHANNEL_IMAGE_SAVE = "NOTIFICATION_CHANNEL_IMAGE_SAVE"; private NotificationUtils() { // utility class -> private constructor diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java index 90574931947d..068efd34d488 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -3,9 +3,11 @@ * * @author David A. Velasco * @author Chris Narkiewicz + * @author TSI-mc * * Copyright (C) 2016 ownCloud Inc. * Copyright (C) 2019 Chris Narkiewicz + * Copyright (C) 2023 TSI-mc * * 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,7 +32,6 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.graphics.Bitmap; -import android.graphics.Color; import android.os.Bundle; import android.os.IBinder; import android.view.MenuItem; @@ -76,15 +77,15 @@ /** - * Holds a swiping galley where image files contained in an Nextcloud directory are shown + * Holds a swiping galley where image files contained in an Nextcloud directory are shown */ @SuppressWarnings("PMD.AvoidDuplicateLiterals") public class PreviewImageActivity extends FileActivity implements - FileFragment.ContainerActivity, - ViewPager.OnPageChangeListener, - OnRemoteOperationListener, - Injectable, - PreviewImageFragment.OnImageLoadListener { + FileFragment.ContainerActivity, + ViewPager.OnPageChangeListener, + OnRemoteOperationListener, + PreviewImageFragment.OnImageLoadListener, + Injectable { public static final String TAG = PreviewImageActivity.class.getSimpleName(); public static final String EXTRA_VIRTUAL_TYPE = "EXTRA_VIRTUAL_TYPE"; @@ -204,7 +205,6 @@ private void initViewPager(User user) { // adapter does not result in a call to #onPageSelected(0) mRequestWaitingForBinder = true; } - } @Override @@ -279,26 +279,24 @@ public void onImageLoadCompleted() { } } - /** - * Defines callbacks for service binding, passed to bindService() - */ + /*** Defines callbacks for service binding, passed to bindService() */ private class PreviewImageServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName component, IBinder service) { if (component.equals(new ComponentName(PreviewImageActivity.this, - FileDownloader.class))) { + FileDownloader.class))) { mDownloaderBinder = (FileDownloaderBinder) service; if (mRequestWaitingForBinder) { mRequestWaitingForBinder = false; Log_OC.d(TAG, "Simulating reselection of current page after connection " + - "of download binder"); + "of download binder"); onPageSelected(mViewPager.getCurrentItem()); } } else if (component.equals(new ComponentName(PreviewImageActivity.this, - FileUploader.class))) { + FileUploader.class))) { Log_OC.d(TAG, "Upload service connected"); mUploaderBinder = (FileUploaderBinder) service; } @@ -308,11 +306,11 @@ public void onServiceConnected(ComponentName component, IBinder service) { @Override public void onServiceDisconnected(ComponentName component) { if (component.equals(new ComponentName(PreviewImageActivity.this, - FileDownloader.class))) { + FileDownloader.class))) { Log_OC.d(TAG, "Download service suddenly disconnected"); mDownloaderBinder = null; } else if (component.equals(new ComponentName(PreviewImageActivity.this, - FileUploader.class))) { + FileUploader.class))) { Log_OC.d(TAG, "Upload service suddenly disconnected"); mUploaderBinder = null; } @@ -339,18 +337,18 @@ public void onDestroy() { public boolean onOptionsItemSelected(MenuItem item) { boolean returnValue = false; - switch (item.getItemId()) { - case android.R.id.home: - if (isDrawerOpen()) { - closeDrawer(); - } else { - backToDisplayActivity(); - } - returnValue = true; - break; - default: - returnValue = super.onOptionsItemSelected(item); - break; + switch(item.getItemId()){ + case android.R.id.home: + if (isDrawerOpen()) { + closeDrawer(); + } else { + backToDisplayActivity(); + } + returnValue = true; + break; + default: + returnValue = super.onOptionsItemSelected(item); + break; } return returnValue; @@ -375,7 +373,7 @@ protected void onPostResume() { @Override public void onPause() { - if (mDownloadFinishReceiver != null) { + if (mDownloadFinishReceiver != null){ localBroadcastManager.unregisterReceiver(mDownloadFinishReceiver); mDownloadFinishReceiver = null; } @@ -418,9 +416,10 @@ public void requestForDownload(OCFile file) { } /** - * This method will be invoked when a new page becomes selected. Animation is not necessarily complete. + * This method will be invoked when a new page becomes selected. Animation is not necessarily + * complete. * - * @param position Position index of the new selected page + * @param position Position index of the new selected page */ @Override public void onPageSelected(int position) { @@ -438,7 +437,7 @@ public void onPageSelected(int position) { setDrawerIndicatorEnabled(false); if (currentFile.isEncrypted() && !currentFile.isDown() && - !mPreviewImagePagerAdapter.pendingErrorAt(position)) { + !mPreviewImagePagerAdapter.pendingErrorAt(position)) { requestForDownload(currentFile); } @@ -450,10 +449,10 @@ public void onPageSelected(int position) { } /** - * Called when the scroll state changes. Useful for discovering when the user begins dragging, when the pager is - * automatically settling to the current page. when it is fully stopped/idle. + * Called when the scroll state changes. Useful for discovering when the user begins dragging, + * when the pager is automatically settling to the current page. when it is fully stopped/idle. * - * @param state The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING + * @param state The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING */ @Override public void onPageScrollStateChanged(int state) { @@ -461,13 +460,15 @@ public void onPageScrollStateChanged(int state) { } /** - * This method will be invoked when the current page is scrolled, either as part of a programmatically initiated - * smooth scroll or a user initiated touch scroll. + * This method will be invoked when the current page is scrolled, either as part of a + * programmatically initiated smooth scroll or a user initiated touch scroll. * - * @param position Position index of the first page currently being displayed. Page position+1 will be - * visible if positionOffset is nonzero. - * @param positionOffset Value from [0, 1) indicating the offset from the page at position. - * @param positionOffsetPixels Value in pixels indicating the offset from position. + * @param position Position index of the first page currently being displayed. + * Page position+1 will be visible if positionOffset is + * nonzero. + * @param positionOffset Value from [0, 1) indicating the offset from the page + * at position. + * @param positionOffsetPixels Value in pixels indicating the offset from position. */ @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { @@ -476,9 +477,9 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse /** * Class waiting for broadcast events from the {@link FileDownloader} service. - *

- * Updates the UI when a download is started or finished, provided that it is relevant for the folder displayed in - * the gallery. + * + * Updates the UI when a download is started or finished, provided that it is relevant for the + * folder displayed in the gallery. */ private class DownloadFinishReceiver extends BroadcastReceiver { @Override @@ -486,7 +487,7 @@ public void onReceive(Context context, Intent intent) { String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); if (getAccount().name.equals(accountName) && - downloadedRemotePath != null) { + downloadedRemotePath != null) { OCFile file = getStorageManager().getFileByPath(downloadedRemotePath); int position = mPreviewImagePagerAdapter.getFilePosition(file); @@ -515,7 +516,7 @@ public boolean isSystemUIVisible() { public void toggleFullScreen() { boolean visible = (mFullScreenAnchorView.getSystemUiVisibility() - & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; + & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; if (visible) { hideSystemUI(mFullScreenAnchorView); @@ -546,24 +547,24 @@ public void onTransferStateChanged(OCFile file, boolean downloading, boolean upl @SuppressLint("InlinedApi") - private void hideSystemUI(View anchorView) { + private void hideSystemUI(View anchorView) { anchorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hides NAVIGATION BAR; Android >= 4.0 - | View.SYSTEM_UI_FLAG_FULLSCREEN // hides STATUS BAR; Android >= 4.1 - | View.SYSTEM_UI_FLAG_IMMERSIVE // stays interactive; Android >= 4.4 - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE // draw full window; Android >= 4.1 - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // draw full window; Android >= 4.1 - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // draw full window; Android >= 4.1 - ); + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hides NAVIGATION BAR; Android >= 4.0 + | View.SYSTEM_UI_FLAG_FULLSCREEN // hides STATUS BAR; Android >= 4.1 + | View.SYSTEM_UI_FLAG_IMMERSIVE // stays interactive; Android >= 4.4 + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE // draw full window; Android >= 4.1 + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // draw full window; Android >= 4.1 + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // draw full window; Android >= 4.1 + ); } @SuppressLint("InlinedApi") private void showSystemUI(View anchorView) { anchorView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE // draw full window; Android >= 4.1 - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // draw full window; Android >= 4.1 - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // draw full window; Android >= 4. - ); + View.SYSTEM_UI_FLAG_LAYOUT_STABLE // draw full window; Android >= 4.1 + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // draw full window; Android >= 4.1 + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // draw full window; Android >= 4. + ); } //add the rotated bitmap to hash map diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java index 84258a40b5af..d207759621e8 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -3,9 +3,11 @@ * * @author David A. Velasco * @author Chris Narkiewicz + * @author TSI-mc * * Copyright (C) 2015 ownCloud Inc. * Copyright (C) 2019 Chris Narkiewicz + * Copyright (C) 2023 TSI-mc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -100,10 +102,12 @@ /** * This fragment shows a preview of a downloaded image. - *

- * Trying to get an instance with a NULL {@link OCFile} will produce an {@link IllegalStateException}. - *

- * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. + * + * Trying to get an instance with a NULL {@link OCFile} will produce an + * {@link IllegalStateException}. + * + * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on + * instantiation too. */ public class PreviewImageFragment extends FileFragment implements Injectable { @@ -146,15 +150,17 @@ public class PreviewImageFragment extends FileFragment implements Injectable { /** * Public factory method to create a new fragment that previews an image. - *

- * Android strongly recommends keep the empty constructor of fragments as the only public constructor, and use - * {@link #setArguments(Bundle)} to set the needed arguments. - *

+ * + * Android strongly recommends keep the empty constructor of fragments as the only public + * constructor, and + * use {@link #setArguments(Bundle)} to set the needed arguments. + * * This method hides to client objects the need of doing the construction in two steps. * * @param imageFile An {@link OCFile} to preview as an image in the fragment - * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter} ; - * TODO better solution + * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of + * {@link FragmentStatePagerAdapter} + * ; TODO better solution */ public static PreviewImageFragment newInstance(@NonNull OCFile imageFile, boolean ignoreFirstSavedState, @@ -533,10 +539,8 @@ private void rotate() { ThumbnailsCacheManager.addBitmapToCache(ThumbnailsCacheManager.PREFIX_THUMBNAIL + getFile().getRemoteId(), rotatedThumbBitmap); } - //if image resized is enabled then add the current rotated bitmap image to cache - if (showResizedImage) { - ThumbnailsCacheManager.addBitmapToCache(ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId(), rotatedBitmap); - } + //update thumbnail to cache for resized image + ThumbnailsCacheManager.addBitmapToCache(ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + getFile().getRemoteId(), rotatedBitmap); rotationHandler.post(() -> { //set the rotated bitmap to image view @@ -590,9 +594,9 @@ private class LoadBitmapTask extends AsyncTask { /** * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. - *

- * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load - * finishes. + * + * Using a weak reference will avoid memory leaks if the target ImageView is retired from + * memory before the load finishes. */ private final WeakReference imageViewRef; private final WeakReference infoViewRef; @@ -659,7 +663,7 @@ protected LoadImage doInBackground(OCFile... params) { try { bitmapResult = BitmapUtils.decodeSampledBitmapFromFile(storagePath, minWidth, - minHeight); + minHeight); if (isCancelled()) { return new LoadImage(bitmapResult, null, ocFile); @@ -697,7 +701,7 @@ protected LoadImage doInBackground(OCFile... params) { } catch (NoSuchFieldError e) { mErrorMessageId = R.string.common_error_unknown; Log_OC.e(TAG, "Error from access to non-existing field despite protection; file " - + storagePath, e); + + storagePath, e); } catch (Throwable t) { mErrorMessageId = R.string.common_error_unknown; @@ -737,7 +741,7 @@ private void showLoadedImage(LoadImage result) { if (imageView != null) { if (bitmap != null) { Log_OC.d(TAG, "Showing image with resolution " + bitmap.getWidth() + "x" + - bitmap.getHeight()); + bitmap.getHeight()); if (MIME_TYPE_PNG.equalsIgnoreCase(result.ocFile.getMimeType()) || MIME_TYPE_GIF.equalsIgnoreCase(result.ocFile.getMimeType())) { @@ -855,7 +859,7 @@ public void setErrorPreviewMessage() { Snackbar.LENGTH_INDEFINITE).show(); } } - ).show(); + ).show(); } } catch (IllegalArgumentException e) { Log_OC.d(TAG, e.getMessage()); @@ -871,7 +875,8 @@ public void setNoConnectionErrorMessage() { } /** - * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} to be previewed. + * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} + * to be previewed. * * @param file File to test if can be previewed. * @return 'True' if the file can be handled by the fragment. diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java index 7907ad5ea3ce..9f8ba5f61164 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author David A. Velasco + * @author TSI-mc * Copyright (C) 2015 ownCloud Inc. + * Copyright (C) 2023 TSI-mc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java index 25f871362c66..010f02112247 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -4,9 +4,11 @@ * @author David A. Velasco * @author Chris Narkiewicz * @author Andy Scherzinger + * @author TSI-mc * Copyright (C) 2016 ownCloud Inc. * Copyright (C) 2019 Chris Narkiewicz * Copyright (C) 2020 Andy Scherzinger + * Copyright (C) 2023 TSI-mc * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -408,9 +410,9 @@ private void showFileActions(OCFile file) { R.id.action_select_all, R.id.action_move, R.id.action_copy, + R.id.action_rotate_image, R.id.action_favorite, - R.id.action_unset_favorite, - R.id.action_rotate_image + R.id.action_unset_favorite )); if (getFile() != null && getFile().isSharedWithMe() && !getFile().canReshare()) { additionalFilter.add(R.id.action_send_share_file); diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java index 3b08739645ff..d4eb291703b8 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewTextFileFragment.java @@ -3,8 +3,10 @@ * Nextcloud Android client application * * @author Tobias Kaminsky + * @author TSI-mc * Copyright (C) 2019 Tobias Kaminsky * Copyright (C) 2019 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 @@ -303,9 +305,9 @@ private void showFileActions(OCFile file) { R.id.action_select_all, R.id.action_move, R.id.action_copy, + R.id.action_rotate_image, R.id.action_favorite, - R.id.action_unset_favorite, - R.id.action_rotate_image + R.id.action_unset_favorite )); if (getFile() != null && getFile().isSharedWithMe() && !getFile().canReshare()) { additionalFilter.add(R.id.action_send_share_file); diff --git a/app/src/main/java/com/owncloud/android/utils/FileUtil.java b/app/src/main/java/com/owncloud/android/utils/FileUtil.java index e87007152ce3..000525ade02e 100644 --- a/app/src/main/java/com/owncloud/android/utils/FileUtil.java +++ b/app/src/main/java/com/owncloud/android/utils/FileUtil.java @@ -2,7 +2,9 @@ * Nextcloud Android client application * * @author Andy Scherzinger + * @author TSI-mc * Copyright (C) 2020 Andy Scherzinger + * 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 @@ -20,13 +22,19 @@ package com.owncloud.android.utils; +import android.content.Context; +import android.graphics.Bitmap; import android.os.Build; +import android.os.Environment; import android.text.TextUtils; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.UploadFileRemoteOperation; +import com.owncloud.android.ui.helpers.FileOperationsHelper; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.attribute.BasicFileAttributes; @@ -37,6 +45,11 @@ public final class FileUtil { + private static final String TAG = FileUtil.class.getSimpleName(); + + private static final int JPG_FILE_TYPE = 1; + private static final int PNG_FILE_TYPE = 2; + private FileUtil() { // utility class -> private constructor } @@ -77,4 +90,70 @@ Long getCreationTimestamp(File file) { return null; } } + + public static File saveJpgImage(Context context, Bitmap bitmap, String imageName, int quality) { + return createFileAndSaveImage(context, bitmap, imageName, quality, JPG_FILE_TYPE); + } + + public static File savePngImage(Context context, Bitmap bitmap, String imageName, int quality) { + return createFileAndSaveImage(context, bitmap, imageName, quality, PNG_FILE_TYPE); + } + + public static File saveJpgImage(Bitmap bitmap, File file, int quality) { + return saveImage(file, bitmap, quality); + } + + public static File savePngImage(Bitmap bitmap, File file, int quality) { + return saveImage(file, bitmap, quality); + } + + private static File createFileAndSaveImage(Context context, Bitmap bitmap, String imageName, int quality, + int fileType) { + File file = fileType == PNG_FILE_TYPE ? getPngImageName(context, imageName) : getJpgImageName(context, + imageName); + return saveImage(file, bitmap, quality); + } + + private static File saveImage(File file, Bitmap bitmap, int quality) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos); + byte[] bitmapData = bos.toByteArray(); + + FileOutputStream fileOutputStream = new FileOutputStream(file); + fileOutputStream.write(bitmapData); + fileOutputStream.flush(); + fileOutputStream.close(); + return file; + } catch (Exception e) { + Log_OC.e(TAG, " Failed to save image : " + e.getLocalizedMessage()); + return null; + } + } + + private static File getJpgImageName(Context context, String imageName) { + File imageFile = getOutputMediaFile(context); + if (!TextUtils.isEmpty(imageName)) { + return new File(imageFile.getPath() + File.separator + imageName + ".jpg"); + } else { + return new File(imageFile.getPath() + File.separator + "IMG_" + FileOperationsHelper.getCapturedImageName()); + } + } + + private static File getPngImageName(Context context, String imageName) { + File imageFile = getOutputMediaFile(context); + if (!TextUtils.isEmpty(imageName)) { + return new File(imageFile.getPath() + File.separator + imageName + ".png"); + } else { + return new File(imageFile.getPath() + File.separator + "IMG_" + FileOperationsHelper.getCapturedImageName().replace(".jpg", ".png")); + } + } + + public static File getOutputMediaFile(Context context) { + File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), ""); + if (!file.exists()) { + file.mkdir(); + } + return file; + } } diff --git a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java index 61313f8784bf..f6790d5bc3ab 100644 --- a/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java +++ b/app/src/main/java/com/owncloud/android/utils/MimeTypeUtil.java @@ -218,14 +218,6 @@ public static boolean isImageOrVideo(ServerFileInterface file) { return isImage(file) || isVideo(file); } - //check if file is png or jpg image - public static boolean isJpgOrPngFile(String fileName) { - String extension = fileName.substring(fileName.lastIndexOf(".")); - return extension.equalsIgnoreCase(".png") - || extension.equalsIgnoreCase(".jpg") - || extension.equalsIgnoreCase(".jpeg"); - } - /** * @return 'True' if the mime type defines image */ diff --git a/app/src/main/res/drawable/ic_rotate_right.xml b/app/src/main/res/drawable/ic_rotate_right.xml index f16133f9bb82..12dabbbca9c9 100644 --- a/app/src/main/res/drawable/ic_rotate_right.xml +++ b/app/src/main/res/drawable/ic_rotate_right.xml @@ -1,3 +1,4 @@ +