Explorar o código

Merge pull request #12059 from nextcloud/nmc/emailShareViaContactBook

Share screen email selection from contact book
Andy Scherzinger hai 1 ano
pai
achega
6756b4dfdc

BIN=BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailSharingFragmentIT_listSharesFileAllShareTypes.png


BIN=BIN
app/screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailSharingFragmentIT_listSharesFileNone.png


+ 92 - 1
app/src/main/java/com/owncloud/android/ui/fragment/FileDetailSharingFragment.java

@@ -7,7 +7,7 @@
  *
  * Copyright (C) 2018 Andy Scherzinger
  * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
- * Copyright (C) 2020 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 AFFERO GENERAL PUBLIC LICENSE
@@ -25,11 +25,17 @@
 
 package com.owncloud.android.ui.fragment;
 
+import android.Manifest;
 import android.accounts.AccountManager;
+import android.app.Activity;
 import android.app.SearchManager;
 import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
+import android.provider.ContactsContract;
 import android.text.InputType;
 import android.text.TextUtils;
 import android.view.LayoutInflater;
@@ -46,6 +52,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.lib.resources.status.NextcloudVersion;
@@ -61,6 +68,7 @@ import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper;
 import com.owncloud.android.ui.helpers.FileOperationsHelper;
 import com.owncloud.android.utils.ClipboardUtil;
 import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.theme.ViewThemeUtils;
 
 import java.util.ArrayList;
@@ -68,6 +76,8 @@ import java.util.List;
 
 import javax.inject.Inject;
 
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -167,6 +177,8 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
                                                             file.isEncrypted()));
         binding.sharesList.setLayoutManager(new LinearLayoutManager(getContext()));
 
+        binding.pickContactEmailBtn.setOnClickListener(v -> checkContactPermission());
+
         setupView();
 
         return view;
@@ -208,6 +220,7 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
             } else {
                 binding.searchView.setQueryHint(getResources().getString(R.string.reshare_not_allowed));
                 binding.searchView.setInputType(InputType.TYPE_NULL);
+                binding.pickContactEmailBtn.setVisibility(View.GONE);
                 disableSearchView(binding.searchView);
             }
         }
@@ -460,6 +473,52 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
         adapter.addShares(publicShares);
     }
 
+    private void checkContactPermission() {
+        if (PermissionUtil.checkSelfPermission(requireActivity(), Manifest.permission.READ_CONTACTS)) {
+            pickContactEmail();
+        } else {
+            requestContactPermissionLauncher.launch(Manifest.permission.READ_CONTACTS);
+        }
+    }
+
+    private void pickContactEmail() {
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        intent.setDataAndType(ContactsContract.Contacts.CONTENT_URI, ContactsContract.CommonDataKinds.Email.CONTENT_TYPE);
+        onContactSelectionResultLauncher.launch(intent);
+    }
+
+    private void handleContactResult(@NonNull Uri contactUri) {
+        // Define the projection to get all email addresses.
+        String[] projection = {ContactsContract.CommonDataKinds.Email.ADDRESS};
+
+        Cursor cursor = fileActivity.getContentResolver().query(contactUri, projection, null, null, null);
+
+        if (cursor != null) {
+            if (cursor.moveToFirst()) {
+                // The contact has only one email address, use it.
+                int columnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS);
+                if (columnIndex != -1) {
+                    // Use the email address as needed.
+                    // email variable contains the selected contact's email address.
+                    String email = cursor.getString(columnIndex);
+                    binding.searchView.post(() -> {
+                        binding.searchView.setQuery(email, false);
+                        binding.searchView.requestFocus();
+                    });
+                } else {
+                    DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed);
+                    Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address.");
+                }
+            } else {
+                DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed);
+                Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address as no Email found.");
+            }
+            cursor.close();
+        } else {
+            DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed);
+            Log_OC.e(FileDetailSharingFragment.class.getSimpleName(), "Failed to pick email address as Cursor is null.");
+        }
+    }
 
     private boolean containsNoNewPublicShare(List<OCShare> shares) {
         for (OCShare share : shares) {
@@ -546,6 +605,38 @@ public class FileDetailSharingFragment extends Fragment implements ShareeListAda
         fileOperationsHelper.setPermissionsToShare(share, permission);
     }
 
+    //launcher for contact permission
+    private final ActivityResultLauncher<String> requestContactPermissionLauncher =
+        registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
+            if (isGranted) {
+                pickContactEmail();
+            } else {
+                DisplayUtils.showSnackMessage(binding.getRoot(), R.string.contact_no_permission);
+            }
+        });
+
+    //launcher to handle contact selection
+    private final ActivityResultLauncher<Intent> onContactSelectionResultLauncher =
+        registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
+                                  result -> {
+                                      if (result.getResultCode() == Activity.RESULT_OK) {
+                                          Intent intent = result.getData();
+                                          if (intent == null) {
+                                              DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed);
+                                              return;
+                                          }
+
+                                          Uri contactUri = intent.getData();
+                                          if (contactUri == null) {
+                                              DisplayUtils.showSnackMessage(binding.getRoot(), R.string.email_pick_failed);
+                                              return;
+                                          }
+
+                                          handleContactResult(contactUri);
+
+                                      }
+                                  });
+
     public interface OnEditShareListener {
         void editExistingShare(OCShare share, int screenTypePermission, boolean isReshareShown,
                                boolean isExpiryDateShown);

+ 26 - 0
app/src/main/res/drawable/ic_contact_book.xml

@@ -0,0 +1,26 @@
+<!--
+    @author Google LLC
+    Copyright (C) 2023 Google LLC
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#666666"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M20,0L4,0v2h16L20,0zM4,24h16v-2L4,22v2zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM12,6.75c1.24,0 2.25,1.01 2.25,2.25s-1.01,2.25 -2.25,2.25S9.75,10.24 9.75,9 10.76,6.75 12,6.75zM17,17L7,17v-1.5c0,-1.67 3.33,-2.5 5,-2.5s5,0.83 5,2.5L17,17z" />
+</vector>

+ 36 - 21
app/src/main/res/layout/file_details_sharing_fragment.xml

@@ -1,7 +1,10 @@
 <?xml version="1.0" encoding="utf-8"?><!--
   Nextcloud Android client application
 
+  @author TSI-mc
+
   Copyright (C) 2018 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
@@ -29,83 +32,94 @@
         android:id="@+id/search_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:orientation="horizontal"
         android:paddingStart="@dimen/standard_padding"
         android:paddingEnd="@dimen/zero">
 
         <ImageView
             android:id="@+id/searchViewIcon"
-            android:layout_height="@dimen/user_icon_size"
             android:layout_width="@dimen/user_icon_size"
-            android:padding="@dimen/standard_half_padding"
-            android:contentDescription="@string/avatar"
+            android:layout_height="@dimen/user_icon_size"
             android:layout_gravity="center_vertical"
+            android:contentDescription="@string/avatar"
+            android:padding="@dimen/standard_half_padding"
             android:src="@drawable/ic_search_grey" />
 
         <androidx.appcompat.widget.SearchView
             android:id="@+id/searchView"
             style="@style/ownCloud.SearchView"
-            android:layout_width="match_parent"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginStart="@dimen/zero"
             android:layout_marginEnd="@dimen/standard_quarter_margin"
+            android:layout_weight="1"
             android:hint="@string/share_search"
             app:searchIcon="@null" />
 
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/pick_contact_email_btn"
+            android:layout_width="@dimen/minimum_size_for_touchable_area"
+            android:layout_height="@dimen/minimum_size_for_touchable_area"
+            android:layout_gravity="center_vertical"
+            android:padding="12dp"
+            android:layout_marginEnd="@dimen/standard_quarter_margin"
+            android:src="@drawable/ic_contact_book" />
+
     </LinearLayout>
 
     <LinearLayout
         android:id="@+id/shared_with_you_container"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginBottom="@dimen/standard_half_margin"
-        android:layout_width="match_parent"
         android:orientation="horizontal"
         android:paddingLeft="@dimen/standard_padding"
-        android:paddingRight="@dimen/standard_padding"
-        android:paddingTop="@dimen/standard_padding">
+        android:paddingTop="@dimen/standard_padding"
+        android:paddingRight="@dimen/standard_padding">
 
         <ImageView
-            android:contentDescription="@string/avatar"
             android:id="@+id/shared_with_you_avatar"
-            android:layout_height="@dimen/user_icon_size"
             android:layout_width="@dimen/user_icon_size"
+            android:layout_height="@dimen/user_icon_size"
+            android:contentDescription="@string/avatar"
             android:src="@drawable/ic_user" />
 
         <LinearLayout
-            android:layout_height="wrap_content"
             android:layout_width="match_parent"
+            android:layout_height="wrap_content"
             android:orientation="vertical"
             android:paddingLeft="@dimen/standard_padding"
-            android:paddingRight="@dimen/standard_padding"
-            android:paddingTop="@dimen/standard_half_padding">
+            android:paddingTop="@dimen/standard_half_padding"
+            android:paddingRight="@dimen/standard_padding">
 
             <TextView
                 android:id="@+id/shared_with_you_username"
-                android:layout_height="wrap_content"
                 android:layout_width="match_parent"
+                android:layout_height="wrap_content"
                 android:text="@string/shared_with_you_by"
                 android:textSize="@dimen/two_line_primary_text_size" />
 
             <LinearLayout
                 android:id="@+id/shared_with_you_note_container"
-                android:layout_height="match_parent"
                 android:layout_width="match_parent"
+                android:layout_height="match_parent"
                 android:orientation="horizontal"
                 android:paddingTop="@dimen/standard_half_padding"
                 tools:ignore="UseCompoundDrawables">
 
                 <ImageView
-                    android:contentDescription="@string/note_icon_hint"
-                    android:layout_height="16dp"
                     android:layout_width="16dp"
+                    android:layout_height="16dp"
+                    android:contentDescription="@string/note_icon_hint"
                     android:src="@drawable/file_text" />
 
                 <TextView
                     android:id="@+id/shared_with_you_note"
+                    android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_weight="1"
-                    android:layout_width="0dp"
-                    android:paddingEnd="@dimen/standard_half_padding"
                     android:paddingStart="@dimen/standard_half_padding"
+                    android:paddingEnd="@dimen/standard_half_padding"
                     android:textSize="16sp" />
             </LinearLayout>
 
@@ -113,10 +127,11 @@
     </LinearLayout>
 
     <androidx.recyclerview.widget.RecyclerView
-        android:divider="@drawable/divider"
-        android:dividerHeight="1dp"
         android:id="@+id/sharesList"
+        android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_width="match_parent" />
+        android:divider="@drawable/divider"
+        android:dividerHeight="1dp"
+        tools:listitem="@layout/file_details_share_link_share_item" />
 
 </LinearLayout>

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

@@ -602,6 +602,7 @@
     <string name="actionbar_calendar_contacts_restore">Restore contacts &amp; calendar</string>
     <string name="contacts_backup_button">Back up now</string>
     <string name="contactlist_no_permission">No permission given, nothing imported.</string>
+    <string name="contact_no_permission">Contact permission is required.</string>
     <string name="restore_backup">Restore backup</string>
     <string name="contacts_preferences_no_file_found">No file found</string>
     <string name="contacts_preferences_something_strange_happened">Could not find your last backup!</string>
@@ -939,6 +940,7 @@
     <string name="link_share_file_drop">File drop (upload only)</string>
     <string name="could_not_retrieve_shares">Could not retrieve shares</string>
     <string name="failed_update_ui">Failed to update UI</string>
+    <string name="email_pick_failed">Failed to pick email address.</string>
     <string name="remote">(remote)</string>
     <string name="set_status">Set status</string>
     <string name="online_status">Online status</string>