瀏覽代碼

Show lock info in bottom sheet

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
Álvaro Brey 2 年之前
父節點
當前提交
5ff16b9ba5

+ 0 - 78
app/src/main/java/com/nextcloud/android/files/FileLockingMenuCustomization.kt

@@ -1,78 +0,0 @@
-/*
- * 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.android.files
-
-import android.content.Context
-import android.graphics.Typeface
-import android.os.Build
-import android.text.style.StyleSpan
-import android.view.Menu
-import android.view.MenuItem
-import com.nextcloud.utils.TimeConstants
-import com.owncloud.android.R
-import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.lib.resources.files.model.FileLockType
-import com.owncloud.android.utils.DisplayUtils
-
-/**
- * Customizes a Menu to show locking information
- */
-class FileLockingMenuCustomization(val context: Context) {
-    fun customizeMenu(menu: Menu, file: OCFile) {
-        if (file.isLocked) {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-                menu.setGroupDividerEnabled(true)
-            }
-            menu.findItem(R.id.action_locked_by).title = getLockedByText(file)
-            showLockedUntil(menu, file)
-        }
-    }
-
-    private fun getLockedByText(file: OCFile): CharSequence {
-        val username = file.lockOwnerDisplayName ?: file.lockOwnerId
-        val resource = when (file.lockType) {
-            FileLockType.COLLABORATIVE -> R.string.locked_by_app
-            else -> R.string.locked_by
-        }
-        return DisplayUtils.createTextWithSpan(
-            context.getString(resource, username),
-            username,
-            StyleSpan(Typeface.BOLD)
-        )
-    }
-
-    private fun showLockedUntil(menu: Menu, file: OCFile) {
-        val lockedUntil: MenuItem = menu.findItem(R.id.action_locked_until)
-        if (file.lockTimestamp == 0L || file.lockTimeout == 0L) {
-            lockedUntil.isVisible = false
-        } else {
-            lockedUntil.title =
-                context.getString(R.string.lock_expiration_info, getExpirationRelativeText(file))
-            lockedUntil.isVisible = true
-        }
-    }
-
-    private fun getExpirationRelativeText(file: OCFile): CharSequence? {
-        val expirationTimestamp = (file.lockTimestamp + file.lockTimeout) * TimeConstants.MILLIS_PER_SECOND
-        return DisplayUtils.getRelativeTimestamp(context, expirationTimestamp, true)
-    }
-}

+ 0 - 42
app/src/main/java/com/nextcloud/android/files/ThemedPopupMenu.kt

@@ -1,42 +0,0 @@
-/*
- * 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.android.files
-
-import android.content.Context
-import android.view.ContextThemeWrapper
-import android.view.View
-import androidx.appcompat.widget.PopupMenu
-import com.owncloud.android.R
-
-/**
- * This is a [PopupMenu] with grayed out disabled elements
- */
-class ThemedPopupMenu(
-    context: Context,
-    anchor: View
-) : PopupMenu(wrapContext(context), anchor) {
-
-    companion object {
-        private fun wrapContext(context: Context): Context =
-            ContextThemeWrapper(context, R.style.Nextcloud_Widget_PopupMenu)
-    }
-}

+ 68 - 1
app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt

@@ -22,25 +22,33 @@
 
 package com.nextcloud.ui.fileactions
 
+import android.content.res.ColorStateList
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
 import android.os.Bundle
+import android.text.style.StyleSpan
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.IdRes
 import androidx.core.os.bundleOf
+import androidx.core.view.isVisible
 import androidx.lifecycle.ViewModelProvider
 import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.nextcloud.client.account.CurrentAccountProvider
 import com.nextcloud.client.di.Injectable
 import com.nextcloud.client.di.ViewModelFactory
 import com.owncloud.android.R
 import com.owncloud.android.databinding.FileActionsBottomSheetBinding
 import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding
 import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.resources.files.model.FileLockType
 import com.owncloud.android.ui.activity.ComponentsGetter
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.DisplayUtils.AvatarGenerationListener
 import com.owncloud.android.utils.theme.ViewThemeUtils
 import javax.inject.Inject
 
-// TODO add lock info (see FileLockingMenuCustomization)
 // TODO give events back
 // TODO drag handle (needs material 1.7.0)
 // TODO theming
@@ -57,6 +65,9 @@ class FileActionsBottomSheet private constructor() : BottomSheetDialogFragment()
     @Inject
     lateinit var vmFactory: ViewModelFactory
 
+    @Inject
+    lateinit var currentUserProvider: CurrentAccountProvider
+
     lateinit var viewModel: FileActionsViewModel
 
     private var _binding: FileActionsBottomSheetBinding? = null
@@ -78,6 +89,9 @@ class FileActionsBottomSheet private constructor() : BottomSheetDialogFragment()
         viewModel.uiState.observe(viewLifecycleOwner) { state ->
             when (state) {
                 is FileActionsViewModel.UiState.LoadedForSingleFile -> {
+                    if (state.lockInfo != null) {
+                        displayLockInfo(state.lockInfo, inflater)
+                    }
                     displayActions(state.actions, inflater)
                     displayTitle(state.titleFile)
                 }
@@ -116,6 +130,59 @@ class FileActionsBottomSheet private constructor() : BottomSheetDialogFragment()
         }
     }
 
+    private fun displayLockInfo(lockInfo: FileActionsViewModel.LockInfo, inflater: LayoutInflater) {
+        val view = FileActionsBottomSheetItemBinding.inflate(inflater, binding.fileActionsList, false)
+            .apply {
+                // TODO theme
+                val textColor = ColorStateList.valueOf(resources.getColor(R.color.secondary_text_color, null))
+                root.isClickable = false
+                text.setTextColor(textColor)
+                text.text = getLockedByText(lockInfo)
+                if (lockInfo.lockedUntil != null) {
+                    textLine2.text = getLockedUntilText(lockInfo)
+                    textLine2.isVisible = true
+                }
+                if (lockInfo.lockType != FileLockType.COLLABORATIVE) {
+                    val drawable = object : AvatarGenerationListener {
+                        override fun avatarGenerated(avatarDrawable: Drawable?, callContext: Any?) {
+                            icon.setImageDrawable(avatarDrawable)
+                        }
+
+                        override fun shouldCallGeneratedCallback(tag: String?, callContext: Any?): Boolean {
+                            return false
+                        }
+                    }
+                    DisplayUtils.setAvatar(
+                        currentUserProvider.user,
+                        lockInfo.lockedBy,
+                        drawable,
+                        resources.getDimension(R.dimen.list_item_avatar_icon_radius),
+                        resources,
+                        this,
+                        requireContext()
+                    )
+                }
+            }
+        binding.fileActionsList.addView(view.root)
+    }
+
+    private fun getLockedByText(lockInfo: FileActionsViewModel.LockInfo): CharSequence {
+        val resource = when (lockInfo.lockType) {
+            FileLockType.COLLABORATIVE -> R.string.locked_by_app
+            else -> R.string.locked_by
+        }
+        return DisplayUtils.createTextWithSpan(
+            getString(resource, lockInfo.lockedBy),
+            lockInfo.lockedBy,
+            StyleSpan(Typeface.BOLD)
+        )
+    }
+
+    private fun getLockedUntilText(lockInfo: FileActionsViewModel.LockInfo): CharSequence {
+        val relativeTimestamp = DisplayUtils.getRelativeTimestamp(context, lockInfo.lockedUntil!!, true)
+        return getString(R.string.lock_expiration_info, relativeTimestamp)
+    }
+
     private fun displayTitle(fileCount: Int) {
         binding.title.text = resources.getQuantityString(R.plurals.file_list__footer__file, fileCount, fileCount)
     }

+ 33 - 3
app/src/main/java/com/nextcloud/ui/fileactions/FileActionsViewModel.kt

@@ -27,8 +27,10 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
 import com.nextcloud.client.account.CurrentAccountProvider
+import com.nextcloud.utils.TimeConstants
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.files.FileMenuFilter
+import com.owncloud.android.lib.resources.files.model.FileLockType
 import com.owncloud.android.ui.activity.ComponentsGetter
 import javax.inject.Inject
 
@@ -38,10 +40,17 @@ class FileActionsViewModel @Inject constructor(
 ) :
     ViewModel() {
 
+    data class LockInfo(val lockType: FileLockType, val lockedBy: String, val lockedUntil: Long?)
+
     sealed interface UiState {
         object Loading : UiState
-        class LoadedForSingleFile(val actions: List<FileAction>, val titleFile: OCFile?) : UiState
-        class LoadedForMultipleFiles(val actions: List<FileAction>, val fileCount: Int) : UiState
+        data class LoadedForSingleFile(
+            val actions: List<FileAction>,
+            val titleFile: OCFile?,
+            val lockInfo: LockInfo? = null
+        ) : UiState
+
+        data class LoadedForMultipleFiles(val actions: List<FileAction>, val fileCount: Int) : UiState
     }
 
     private val _uiState: MutableLiveData<UiState> = MutableLiveData(UiState.Loading)
@@ -72,11 +81,32 @@ class FileActionsViewModel @Inject constructor(
             .filter { additionalFilter == null || it.id !in additionalFilter }
             .filter { it.id !in toHide }
         _uiState.value = when (files.size) {
-            1 -> UiState.LoadedForSingleFile(availableActions, files.first())
+            1 -> {
+                val file = files.first()
+                UiState.LoadedForSingleFile(availableActions, file, getLockInfo(file))
+            }
             else -> UiState.LoadedForMultipleFiles(availableActions, files.size)
         }
     }
 
+    private fun getLockInfo(file: OCFile): LockInfo? {
+        val lockType = file.lockType
+        val username = file.lockOwnerDisplayName ?: file.lockOwnerId
+        return if (file.isLocked && lockType != null && username != null) {
+            LockInfo(lockType, username, getLockedUntil(file))
+        } else {
+            null
+        }
+    }
+
+    private fun getLockedUntil(file: OCFile): Long? {
+        return if (file.lockTimestamp == 0L || file.lockTimeout == 0L) {
+            null
+        } else {
+            (file.lockTimestamp + file.lockTimeout) * TimeConstants.MILLIS_PER_SECOND
+        }
+    }
+
     fun onClick(action: FileAction) {
         _clickActionId.value = action.id
     }

+ 3 - 2
app/src/main/res/layout/file_actions_bottom_sheet.xml

@@ -43,15 +43,16 @@
             android:padding="@dimen/standard_padding"
             tools:text="Test file name which is very very very very very long.pdf" />
 
+
         <androidx.core.widget.NestedScrollView
             android:layout_width="match_parent"
             android:layout_height="wrap_content">
-
             <LinearLayout
                 android:id="@+id/file_actions_list"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:orientation="vertical" />
+                android:orientation="vertical">
+            </LinearLayout>
         </androidx.core.widget.NestedScrollView>
     </LinearLayout>
 

+ 29 - 8
app/src/main/res/layout/file_actions_bottom_sheet_item.xml

@@ -38,6 +38,7 @@
     android:paddingBottom="@dimen/standard_half_padding"
     tools:ignore="UseCompoundDrawables">
 
+
     <ImageView
         android:id="@+id/icon"
         android:layout_width="@dimen/iconized_single_line_item_icon_size"
@@ -46,14 +47,34 @@
         app:tint="@color/primary"
         tools:src="@drawable/ic_delete" />
 
-    <TextView
-        android:id="@+id/text"
+    <LinearLayout
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_vertical"
-        android:layout_marginStart="@dimen/bottom_sheet_text_start_margin"
-        android:textColor="@color/text_color"
-        android:textSize="@dimen/bottom_sheet_text_size"
-        tools:text="Delete file" />
+        android:layout_height="match_parent"
+        android:gravity="center_vertical"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/bottom_sheet_text_start_margin"
+            android:textColor="@color/text_color"
+            android:textSize="@dimen/bottom_sheet_text_size"
+            tools:text="Delete file" />
+
+        <TextView
+            android:id="@+id/text_line2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_marginStart="@dimen/bottom_sheet_text_start_margin"
+            android:textColor="@color/secondary_text_color"
+            android:textSize="@dimen/bottom_sheet_text_size"
+            android:visibility="gone"
+            tools:text="Some additional action info"
+            tools:visibility="visible" />
+    </LinearLayout>
+
 
 </LinearLayout>