Browse Source

Show lock details when tapping on lock indicator

Signed-off-by: Álvaro Brey Vilas <alvaro.brey@nextcloud.com>
Álvaro Brey Vilas 3 years ago
parent
commit
a88b03a0c8

+ 28 - 0
app/src/main/java/com/nextcloud/utils/TimeConstants.kt

@@ -0,0 +1,28 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Álvaro Brey Vilas
+ * Copyright (C) 2022 Álvaro Brey Vilas
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.utils
+
+@Suppress("MagicNumber")
+object TimeConstants {
+    @JvmStatic
+    val MILLIS_PER_SECOND = 1000
+}

+ 1 - 0
app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -469,6 +469,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
         if (file.isLocked()) {
             holder.getLockIndicator().setVisibility(View.VISIBLE);
+            holder.getLockIndicator().setOnClickListener(view -> ocFileListFragmentInterface.onLockIndicatorClicked(file));
         } else {
             holder.getLockIndicator().setVisibility(View.GONE);
         }

+ 138 - 0
app/src/main/java/com/owncloud/android/ui/dialog/LockInfoDialogFragment.kt

@@ -0,0 +1,138 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Álvaro Brey Vilas
+ * Copyright (C) 2022 Álvaro Brey Vilas
+ * Copyright (C) 2022 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.dialog
+
+import android.app.Dialog
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.text.style.StyleSpan
+import android.view.View
+import android.widget.ImageView
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.nextcloud.client.account.User
+import com.nextcloud.utils.TimeConstants
+import com.owncloud.android.R
+import com.owncloud.android.databinding.LockInfoDialogBinding
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.theme.ThemeButtonUtils
+
+/**
+ * Dialog that shows lock information for a locked file
+ */
+class LockInfoDialogFragment(private val user: User, private val file: OCFile) :
+    DialogFragment(),
+    DisplayUtils.AvatarGenerationListener {
+    private lateinit var binding: LockInfoDialogBinding
+
+    override fun onStart() {
+        super.onStart()
+        dialog?.let {
+            val alertDialog = it as AlertDialog
+            ThemeButtonUtils.themeBorderlessButton(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE))
+        }
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Inflate the layout for the dialog
+        val inflater = requireActivity().layoutInflater
+        binding = LockInfoDialogBinding.inflate(inflater, null, false)
+
+        val view = binding.root
+
+        // Setup layout
+        if (file.isLocked) {
+            binding.lockedByUsername.text = getUserAndTimestampText()
+            loadAvatar()
+            setRemainingTime()
+        } else {
+            setNotLocked()
+        }
+
+        // Build the dialog
+        val dialog = MaterialAlertDialogBuilder(requireActivity(), R.style.Theme_ownCloud_Dialog)
+            .setTitle(R.string.file_lock_dialog_title)
+            .setView(view)
+            .setPositiveButton(R.string.dismiss) { _, _ ->
+                dismiss()
+            }
+            .create()
+
+        return dialog
+    }
+
+    private fun getUserAndTimestampText(): CharSequence {
+        val username = file.lockOwnerDisplayName ?: file.lockOwnerId
+        val lockTimestampMillis = file.lockTimestamp * TimeConstants.MILLIS_PER_SECOND
+        return DisplayUtils.createTextWithSpan(
+            getString(
+                R.string.locked_by_user_date, username, DisplayUtils.unixTimeToHumanReadable(lockTimestampMillis)
+            ),
+            username,
+            StyleSpan(Typeface.BOLD)
+        )
+    }
+
+    private fun loadAvatar() {
+        DisplayUtils.setAvatar(
+            user,
+            file.lockOwnerId!!,
+            file.lockOwnerDisplayName,
+            this,
+            requireContext().resources.getDimension(R.dimen.list_item_avatar_icon_radius),
+            resources,
+            binding.lockedByAvatar,
+            context
+        )
+    }
+
+    private fun setRemainingTime() {
+        if (file.lockTimestamp == 0L || file.lockTimeout == 0L) {
+            binding.lockExpiration.visibility = View.GONE
+        } else {
+            val expirationText = getExpirationRelativeText()
+            binding.lockExpiration.text = getString(R.string.lock_expiration_info, expirationText)
+            binding.lockExpiration.visibility = View.VISIBLE
+        }
+    }
+
+    private fun getExpirationRelativeText(): CharSequence? {
+        val expirationTimestamp = (file.lockTimestamp + file.lockTimeout) * TimeConstants.MILLIS_PER_SECOND
+        return DisplayUtils.getRelativeTimestamp(context, expirationTimestamp, true)
+    }
+
+    private fun setNotLocked() {
+        binding.lockExpiration.visibility = View.GONE
+        binding.lockedByAvatar.visibility = View.GONE
+        binding.lockedByUsername.text = getString(R.string.file_not_locked)
+    }
+
+    override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any) {
+        (callContext as ImageView).setImageDrawable(avatarDrawable)
+    }
+
+    override fun shouldCallGeneratedCallback(tag: String, callContext: Any): Boolean {
+        return (callContext as ImageView).tag == tag
+    }
+}

+ 9 - 2
app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -86,6 +86,7 @@ import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment;
 import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
+import com.owncloud.android.ui.dialog.LockInfoDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
 import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
 import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment;
@@ -184,6 +185,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
     private static final String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER";
     private static final String DIALOG_CREATE_DOCUMENT = "DIALOG_CREATE_DOCUMENT";
     private static final String DIALOG_BOTTOM_SHEET = "DIALOG_BOTTOM_SHEET";
+    private static final String DIALOG_LOCK_DETAILS = "DIALOG_LOCK_DETAILS";
 
     private static final int SINGLE_SELECTION = 1;
     private static final int NOT_ENOUGH_SPACE_FRAG_REQUEST_CODE = 2;
@@ -606,7 +608,7 @@ public class OCFileListFragment extends ExtendedListFragment implements
     public void newPresentation() {
         ChooseRichDocumentsTemplateDialogFragment.newInstance(mFile,
                                                               ChooseRichDocumentsTemplateDialogFragment.Type.PRESENTATION)
-                .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT);
+            .show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_DOCUMENT);
     }
 
     @Override
@@ -616,10 +618,15 @@ public class OCFileListFragment extends ExtendedListFragment implements
         }
     }
 
+    @Override
+    public void onLockIndicatorClicked(OCFile file) {
+        new LockInfoDialogFragment(accountManager.getUser(), file).show(requireActivity().getSupportFragmentManager(), DIALOG_LOCK_DETAILS);
+    }
+
     @Override
     public void showTemplate(Creator creator, String headline) {
         ChooseTemplateDialogFragment.newInstance(mFile, creator, headline).show(requireActivity().getSupportFragmentManager(),
-                                                                      DIALOG_CREATE_DOCUMENT);
+                                                                                DIALOG_CREATE_DOCUMENT);
     }
 
     /**

+ 2 - 0
app/src/main/java/com/owncloud/android/ui/interfaces/OCFileListFragmentInterface.java

@@ -48,4 +48,6 @@ public interface OCFileListFragmentInterface {
     boolean isLoading();
 
     void onHeaderClicked();
+
+    void onLockIndicatorClicked(OCFile file);
 }

+ 61 - 0
app/src/main/res/layout/lock_info_dialog.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Nextcloud Android client application
+  ~
+  ~ @author Álvaro Brey Vilas
+  ~ Copyright (C) 2022 Álvaro Brey Vilas
+  ~ Copyright (C) 2022 Nextcloud GmbH
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU 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 General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingHorizontal="?dialogPreferredPadding">
+
+    <ImageView
+        android:id="@+id/locked_by_avatar"
+        android:layout_width="@dimen/file_icon_size"
+        android:layout_height="@dimen/file_icon_size"
+        android:importantForAccessibility="no"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:src="@drawable/all_files" />
+
+    <TextView
+        android:id="@+id/locked_by_username"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/list_item_share_right_margin"
+        android:layout_marginTop="@dimen/list_item_share_right_margin"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/locked_by_avatar"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Locked by Username Goes Here at 2022/01/02 13:00" />
+
+    <TextView
+        android:id="@+id/lock_expiration"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/list_item_share_right_margin"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/locked_by_avatar"
+        app:layout_constraintTop_toBottomOf="@id/locked_by_username"
+        tools:text="Expires: In 20 minutes" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -1012,4 +1012,8 @@
     <string name="lock_file">Lock</string>
     <string name="unlock_file">Unlock</string>
     <string name="error_file_lock">Error changing file lock status</string>
+    <string name="file_lock_dialog_title">Lock details</string>
+    <string name="locked_by_user_date">Locked by %1$s at %2$s</string>
+    <string name="lock_expiration_info">Expires: %1$s</string>
+    <string name="file_not_locked">File is not locked</string>
 </resources>