Răsfoiți Sursa

Add scan document feature

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
Signed-off-by: thelittlefireman <thelittlefireman@users.noreply.github.com>
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
thelittlefireman 4 ani în urmă
părinte
comite
9e7a9b410f

+ 2 - 0
build.gradle

@@ -274,6 +274,8 @@ dependencies {
     implementation "com.github.cotechde.hwsecurity:hwsecurity-fido:$fidoVersion"
     implementation "com.github.cotechde.hwsecurity:hwsecurity-fido2:$fidoVersion"
 
+    implementation 'com.github.zynkware:Document-Scanning-Android-SDK:1.0.1'
+
     spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.11.0'
     spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.4.7'
 

+ 9 - 0
drawable_resources/ic_scan_document.svg

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" height="100%" version="1.1"
+    viewBox="0 0 24 24" width="100%" xmlns="http://www.w3.org/2000/svg" xml:space="preserve">
+    <g transform="matrix(1.1,0,0,1.1,-1.2,-1.2)">
+        <path style="fill-rule:nonzero;"
+            d="M22,20.333C22,21.248 21.248,22 20.333,22L17,22L17,20.333L20.333,20.333L20.333,17L22,17L22,20.333ZM3.667,22C2.752,22 2,21.248 2,20.333L2,17L3.667,17L3.667,20.333L7,20.333L7,22L3.667,22ZM6.953,5.5C6.695,5.5 6.493,5.704 6.493,5.964L6.493,18.036C6.493,18.296 6.695,18.5 6.953,18.5L17.056,18.5C17.313,18.5 17.515,18.296 17.515,18.036L17.515,8.286L14.76,5.5L6.953,5.5ZM8.33,15.714L12.004,15.714L12.004,16.643L8.33,16.643L8.33,15.714ZM8.33,12.929L15.678,12.929L15.678,13.857L8.33,13.857L8.33,12.929ZM8.33,10.143L12.923,10.143L12.923,11.071L8.33,11.071L8.33,10.143ZM8.33,7.357L13.841,7.357L13.841,8.286L8.33,8.286L8.33,7.357ZM2,3.667C2,2.752 2.752,2 3.667,2L7,2L7,3.667L3.667,3.667L3.667,7L2,7L2,3.667ZM20.333,2C21.248,2 22,2.752 22,3.667L22,7L20.333,7L20.333,3.667L17,3.667L17,2L20.333,2Z" />
+    </g>
+</svg>

BIN
screenshots/gplay/debug/com.owncloud.android.ui.dialog.DialogFragmentIT_testBottomSheet.png


+ 5 - 0
src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.java

@@ -334,6 +334,11 @@ public class DialogFragmentIT extends AbstractIT {
 
             }
 
+            @Override
+            public void scanDocUpload() {
+                
+            }
+
             @Override
             public void showTemplate(Creator creator, String headline) {
 

+ 5 - 0
src/main/AndroidManifest.xml

@@ -117,6 +117,11 @@
                 android:name="android.app.searchable"
                 android:resource="@xml/users_and_groups_searchable" />
         </activity>
+        <activity
+            android:name=".ui.activity.AppScanActivity"
+            android:screenOrientation="portrait"
+            android:theme="@style/Theme.AppCompat.NoActionBar"
+            tools:ignore="LockedOrientationActivity" />
         <activity
             android:name=".ui.activity.ManageAccountsActivity"
             android:exported="false" />

+ 59 - 0
src/main/java/com/owncloud/android/ui/activity/AppScanActivity.kt

@@ -0,0 +1,59 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2022 Tobias Kaminsky
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.activity
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import com.owncloud.android.R
+import com.owncloud.android.utils.DisplayUtils
+import com.zynksoftware.documentscanner.ScanActivity
+import com.zynksoftware.documentscanner.model.DocumentScannerErrorModel
+import com.zynksoftware.documentscanner.model.ScannerResults
+
+class AppScanActivity : ScanActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        addFragmentContentLayout()
+    }
+
+    override fun onError(error: DocumentScannerErrorModel) {
+        DisplayUtils.showSnackMessage(this, R.string.error_starting_scan_doc)
+    }
+
+    override fun onSuccess(scannerResults: ScannerResults) {
+        val intent = Intent()
+
+        intent.putExtra(
+            "file",
+            scannerResults.transformedImageFile?.absolutePath ?: scannerResults.croppedImageFile?.absolutePath
+        )
+
+        setResult(Activity.RESULT_OK, intent)
+        finish()
+    }
+
+    override fun onClose() {
+        finish()
+    }
+}

+ 30 - 0
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -192,6 +192,7 @@ public class FileDisplayActivity extends FileActivity
     public static final int REQUEST_CODE__MOVE_FILES = REQUEST_CODE__LAST_SHARED + 3;
     public static final int REQUEST_CODE__COPY_FILES = REQUEST_CODE__LAST_SHARED + 4;
     public static final int REQUEST_CODE__UPLOAD_FROM_CAMERA = REQUEST_CODE__LAST_SHARED + 5;
+    public static final int REQUEST_CODE__UPLOAD_SCAN_DOC_FROM_CAMERA = REQUEST_CODE__LAST_SHARED + 6;
 
     protected static final long DELAY_TO_REQUEST_REFRESH_OPERATION_LATER = DELAY_TO_REQUEST_OPERATIONS_LATER + 350;
 
@@ -916,6 +917,35 @@ public class FileDisplayActivity extends FileActivity
                     }
                 }
             }, new String[]{FileOperationsHelper.createImageFile(getActivity()).getAbsolutePath()}).execute();
+        } else if (requestCode == REQUEST_CODE__UPLOAD_SCAN_DOC_FROM_CAMERA &&
+            (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_DELETE)) {
+            Uri fileUri = Uri.parse(data.getStringExtra("file"));
+
+            new CheckAvailableSpaceTask(new CheckAvailableSpaceTask.CheckAvailableSpaceListener() {
+                @Override
+                public void onCheckAvailableSpaceStart() {
+                    Log_OC.d(this, "onCheckAvailableSpaceStart");
+                }
+
+                @Override
+                public void onCheckAvailableSpaceFinish(boolean hasEnoughSpaceAvailable, String... filesToUpload) {
+                    Log_OC.d(this, "onCheckAvailableSpaceFinish");
+
+                    if (hasEnoughSpaceAvailable) {
+                        File file = new File(filesToUpload[0]);
+                        File renamedFile = new File(file.getParent() + PATH_SEPARATOR + FileOperationsHelper.getCapturedImageName());
+
+                        if (!file.renameTo(renamedFile)) {
+                            DisplayUtils.showSnackMessage(getActivity(), "Fail to upload taken image!");
+                            return;
+                        }
+
+                        requestUploadOfFilesFromFileSystem(renamedFile.getParentFile().getAbsolutePath(),
+                                                           new String[]{renamedFile.getAbsolutePath()},
+                                                           FileUploader.LOCAL_BEHAVIOUR_DELETE);
+                    }
+                }
+            }, new String[]{fileUri.getPath()}).execute();
         } else if (requestCode == REQUEST_CODE__MOVE_FILES && resultCode == RESULT_OK) {
             exitSelectionMode();
             final Intent fData = data;

+ 2 - 1
src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -499,7 +499,8 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
             // return the list of files (success)
             Intent data = new Intent();
 
-            if (requestCode == FileDisplayActivity.REQUEST_CODE__UPLOAD_FROM_CAMERA) {
+            if (requestCode == FileDisplayActivity.REQUEST_CODE__UPLOAD_FROM_CAMERA ||
+                requestCode == FileDisplayActivity.REQUEST_CODE__UPLOAD_SCAN_DOC_FROM_CAMERA) {
                 data.putExtra(EXTRA_CHOSEN_FILES, new String[]{filesToUpload[0]});
                 setResult(RESULT_OK_AND_DELETE, data);
 

+ 5 - 0
src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetActions.java

@@ -62,6 +62,11 @@ public interface OCFileListBottomSheetActions {
      */
     void directCameraUpload();
 
+    /**
+     * offers scanning document upload to the current folder.
+     */
+    void scanDocUpload();
+
     /**
      * open template selection for creator @link Creator
      */

+ 5 - 0
src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.java

@@ -183,6 +183,11 @@ public class OCFileListBottomSheetDialog extends BottomSheetDialog {
             dismiss();
         });
 
+        binding.menuScanDocUpload.setOnClickListener(v -> {
+            actions.scanDocUpload();
+            dismiss();
+        });
+
         binding.menuUploadFiles.setOnClickListener(v -> {
             actions.uploadFiles();
             dismiss();

+ 16 - 0
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -42,6 +42,7 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AbsListView;
 import android.widget.PopupMenu;
+import android.widget.Toast;
 
 import com.google.android.material.appbar.AppBarLayout;
 import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
@@ -478,6 +479,21 @@ public class OCFileListFragment extends ExtendedListFragment implements
         }
     }
 
+    @Override
+    public void scanDocUpload() {
+        FileDisplayActivity fileDisplayActivity = (FileDisplayActivity) getActivity();
+
+        if (fileDisplayActivity != null) {
+            fileDisplayActivity.getFileOperationsHelper()
+                .scanFromCamera(fileDisplayActivity, FileDisplayActivity.REQUEST_CODE__UPLOAD_SCAN_DOC_FROM_CAMERA);
+        } else {
+            Toast.makeText(getContext(), 
+                           getString(R.string.error_starting_direct_camera_upload), 
+                           Toast.LENGTH_SHORT)
+                .show();
+        }
+    }
+
     @Override
     public void uploadFiles() {
         UploadFilesActivity.startUploadActivityForResult(

+ 21 - 5
src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -37,6 +37,7 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Environment;
@@ -68,6 +69,7 @@ import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.services.OperationsService;
+import com.owncloud.android.ui.activity.AppScanActivity;
 import com.owncloud.android.ui.activity.ConflictsResolveActivity;
 import com.owncloud.android.ui.activity.ExternalSiteWebView;
 import com.owncloud.android.ui.activity.FileActivity;
@@ -84,6 +86,7 @@ import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.UriUtils;
+import com.zynksoftware.documentscanner.ui.DocumentScanner;
 
 import org.greenrobot.eventbus.EventBus;
 
@@ -92,7 +95,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -123,9 +126,9 @@ public class FileOperationsHelper {
     private static final String FILE_EXTENSION_WEBLOC = "webloc";
     public static final int SINGLE_LINK_SIZE = 1;
 
-    private FileActivity fileActivity;
-    private CurrentAccountProvider currentAccount;
-    private ConnectivityService connectivityService;
+    private final FileActivity fileActivity;
+    private final CurrentAccountProvider currentAccount;
+    private final ConnectivityService connectivityService;
 
     /// Identifier of operation in progress which result shouldn't be lost
     private long mWaitingForOpId = Long.MAX_VALUE;
@@ -145,7 +148,7 @@ public class FileOperationsHelper {
         InputStreamReader fr = null;
         BufferedReader br = null;
         try {
-            fr = new InputStreamReader(new FileInputStream(storagePath), Charset.forName("UTF-8"));
+            fr = new InputStreamReader(new FileInputStream(storagePath), StandardCharsets.UTF_8);
             br = new BufferedReader(fr);
 
             String line;
@@ -1072,6 +1075,19 @@ public class FileOperationsHelper {
         }
     }
 
+    public void scanFromCamera(Activity activity, int requestCode) {
+        DocumentScanner.Configuration configuration = new DocumentScanner.Configuration();
+        configuration.setImageType(Bitmap.CompressFormat.PNG);
+        DocumentScanner.INSTANCE.init(activity, configuration);
+
+        Intent scanIntent = new Intent(activity, AppScanActivity.class);
+        if (PermissionUtil.checkSelfPermission(activity, Manifest.permission.CAMERA)) {
+            activity.startActivityForResult(scanIntent, requestCode);
+        } else {
+            PermissionUtil.requestCameraPermission(activity);
+        }
+    }
+
     public static File createImageFile(Activity activity) {
         File storageDir = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
 

+ 13 - 0
src/main/res/drawable/ic_scan_document.xml

@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:width="24dp"
+    android:height="24dp"
+    android:autoMirrored="true"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    tools:ignore="VectorRaster">
+    <path
+        android:fillColor="#FF000000"
+        android:fillType="nonZero"
+        android:pathData="M23,21.1663C23,22.1728 22.1728,23 21.1663,23L17.5,23L17.5,21.1663L21.1663,21.1663L21.1663,17.5L23,17.5L23,21.1663ZM2.8337,23C1.8272,23 1,22.1728 1,21.1663L1,17.5L2.8337,17.5L2.8337,21.1663L6.5,21.1663L6.5,23L2.8337,23ZM6.4483,4.85C6.1645,4.85 5.9423,5.0744 5.9423,5.3604L5.9423,18.6396C5.9423,18.9256 6.1645,19.15 6.4483,19.15L17.5616,19.15C17.8443,19.15 18.0665,18.9256 18.0665,18.6396L18.0665,7.9146L15.036,4.85L6.4483,4.85ZM7.963,16.0854L12.0044,16.0854L12.0044,17.1073L7.963,17.1073L7.963,16.0854ZM7.963,13.0219L16.0458,13.0219L16.0458,14.0427L7.963,14.0427L7.963,13.0219ZM7.963,9.9573L13.0153,9.9573L13.0153,10.9781L7.963,10.9781L7.963,9.9573ZM7.963,6.8927L14.0251,6.8927L14.0251,7.9146L7.963,7.9146L7.963,6.8927ZM1,2.8337C1,1.8272 1.8272,1 2.8337,1L6.5,1L6.5,2.8337L2.8337,2.8337L2.8337,6.5L1,6.5L1,2.8337ZM21.1663,1C22.1728,1 23,1.8272 23,2.8337L23,6.5L21.1663,6.5L21.1663,2.8337L17.5,2.8337L17.5,1L21.1663,1Z" />
+</vector>

+ 31 - 1
src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml

@@ -125,6 +125,36 @@
 
     </LinearLayout>
 
+    <LinearLayout
+        android:id="@+id/menu_scan_doc_upload"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/selectableItemBackground"
+        android:orientation="horizontal"
+        android:paddingLeft="@dimen/standard_padding"
+        android:paddingTop="@dimen/standard_half_padding"
+        android:paddingRight="@dimen/standard_padding"
+        android:paddingBottom="@dimen/standard_half_padding"
+        tools:ignore="UseCompoundDrawables">
+
+        <ImageView
+            android:id="@+id/menu_icon_scan_doc_upload"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@null"
+            android:src="@drawable/ic_scan_document"
+            app:tint="@color/primary" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/standard_margin"
+            android:text="@string/upload_scan_doc_upload"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/bottom_sheet_text_size" />
+    </LinearLayout>
+
     <View
         android:id="@+id/divider"
         android:layout_width="match_parent"
@@ -133,7 +163,7 @@
         android:layout_marginTop="@dimen/standard_half_margin"
         android:layout_marginEnd="@dimen/standard_margin"
         android:layout_marginBottom="@dimen/standard_half_margin"
-        android:background="@color/list_divider_background"/>
+        android:background="@color/list_divider_background" />
 
     <LinearLayout
         android:id="@+id/menu_mkdir"

+ 2 - 0
src/main/res/values/strings.xml

@@ -724,6 +724,7 @@
     <string name="add_to_cloud">Add to %1$s</string>
     <string name="upload_files">Upload files</string>
     <string name="upload_direct_camera_upload">Upload from camera</string>
+    <string name="upload_scan_doc_upload">Scan document from camera</string>
     <string name="upload_content_from_other_apps">Upload content from other apps</string>
     <string name="create_new_folder">Create new folder</string>
     <string name="uploads_view_upload_status_virus_detected">Virus detected. Upload cannot be completed!</string>
@@ -803,6 +804,7 @@
     <string name="battery_optimization_close">Close</string>
     <string name="file_details_no_content">Failed to load details</string>
     <string name="error_starting_direct_camera_upload">Error starting camera</string>
+    <string name="error_starting_scan_doc">Error using document scanning</string>
     <string name="uploader_upload_files_behaviour_not_writable">source folder is read-only; file will only be uploaded</string>
     <string name="auto_upload_file_behaviour_kept_in_folder">kept in original folder, as it is readonly</string>
     <string name="scanQR_description">Login via QR code</string>