فهرست منبع

FileActionsBottomSheet: use ViewModel

Signed-off-by: Álvaro Brey <alvaro.brey@nextcloud.com>
Álvaro Brey 2 سال پیش
والد
کامیت
e41d474e89

+ 6 - 0
app/src/main/java/com/nextcloud/client/di/ViewModelModule.kt

@@ -23,6 +23,7 @@ import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import com.nextcloud.client.etm.EtmViewModel
 import com.nextcloud.client.logger.ui.LogsViewModel
+import com.nextcloud.ui.fileactions.FileActionsViewModel
 import com.owncloud.android.ui.preview.pdf.PreviewPdfViewModel
 import com.owncloud.android.ui.unifiedsearch.UnifiedSearchViewModel
 import dagger.Binds
@@ -51,6 +52,11 @@ abstract class ViewModelModule {
     @ViewModelKey(PreviewPdfViewModel::class)
     abstract fun previewPDFViewModel(vm: PreviewPdfViewModel): ViewModel
 
+    @Binds
+    @IntoMap
+    @ViewModelKey(FileActionsViewModel::class)
+    abstract fun fileActionsViewModel(vm: FileActionsViewModel): ViewModel
+
     @Binds
     abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
 }

+ 70 - 37
app/src/main/java/com/nextcloud/ui/fileactions/FileActionsBottomSheet.kt

@@ -28,13 +28,13 @@ import android.view.View
 import android.view.ViewGroup
 import androidx.annotation.IdRes
 import androidx.core.os.bundleOf
+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.databinding.FileActionsBottomSheetBinding
 import com.owncloud.android.databinding.FileActionsBottomSheetItemBinding
 import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.files.FileMenuFilter
 import com.owncloud.android.ui.activity.ComponentsGetter
 import com.owncloud.android.utils.theme.ViewThemeUtils
 import javax.inject.Inject
@@ -42,7 +42,6 @@ import javax.inject.Inject
 // TODO add file name
 // TODO add lock info (see FileLockingMenuCustomization)
 // TODO give events back
-// TODO viewModel
 // TODO drag handle
 // TODO theming
 class FileActionsBottomSheet private constructor() : BottomSheetDialogFragment(), Injectable {
@@ -54,55 +53,89 @@ class FileActionsBottomSheet private constructor() : BottomSheetDialogFragment()
     lateinit var clickListener: ClickListener
 
     @Inject
-    lateinit var currentAccountProvider: CurrentAccountProvider
+    lateinit var viewThemeUtils: ViewThemeUtils
 
     @Inject
-    lateinit var viewThemeUtils: ViewThemeUtils
+    lateinit var vmFactory: ViewModelFactory
+
+    lateinit var viewModel: FileActionsViewModel
 
-    private lateinit var binding: FileActionsBottomSheetBinding
+    private var _binding: FileActionsBottomSheetBinding? = null
+    private val binding
+        get() = _binding!!
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
-        // TODO pass only IDs, fetch from DB to avoid TransactionTooLarge
         val args = requireArguments()
+        // TODO pass only IDs, fetch from DB to avoid TransactionTooLarge
         val files: Array<OCFile>? = args.getParcelableArray(ARG_FILES) as Array<OCFile>?
         require(files != null)
         val numberOfAllFiles = args.getInt(ARG_ALL_FILES_COUNT, 1)
         val isOverflow = args.getBoolean(ARG_IS_OVERFLOW, false)
 
-        binding = FileActionsBottomSheetBinding.inflate(inflater, container, false)
-        val toHide = FileMenuFilter(
-            numberOfAllFiles,
-            files.toList(),
-            componentsGetter,
-            requireContext(),
-            isOverflow,
-            currentAccountProvider.user
-        )
-            .getToHide(false)
-        FileAction.SORTED_VALUES
-            .filter { it.id !in toHide }.forEach { action ->
-                // TODO change icon
-                val itemBinding = FileActionsBottomSheetItemBinding.inflate(inflater, binding.fileActionsList, false)
-                    .apply {
-                        root.setOnClickListener {
-                            clickListener.onClick(action.id)
-                            dismiss()
-                        }
-                        text.setText(action.title)
-                        if (action.icon != null) {
-                            val drawable =
-                                viewThemeUtils.platform.tintDrawable(
-                                    requireContext(),
-                                    resources.getDrawable(action.icon)
-                                )
-                            icon.setImageDrawable(drawable)
-                        }
-                    }
-                binding.fileActionsList.addView(itemBinding.root)
+        viewModel = ViewModelProvider(this, vmFactory)[FileActionsViewModel::class.java]
+        _binding = FileActionsBottomSheetBinding.inflate(inflater, container, false)
+
+        viewModel.uiState.observe(viewLifecycleOwner) { state ->
+            when (state) {
+                is FileActionsViewModel.UiState.Loaded -> {
+                    displayActions(state.actions, inflater)
+                }
+                FileActionsViewModel.UiState.Loading -> {
+                    // TODO show spinner
+                }
             }
+        }
+
+        viewModel.clickActionId.observe(viewLifecycleOwner) { id ->
+            dispatchActionClick(id)
+        }
+
+        viewModel.load(files.toList(), componentsGetter, numberOfAllFiles, isOverflow)
+
         return binding.root
     }
 
+    private fun displayActions(
+        actions: List<FileAction>,
+        inflater: LayoutInflater
+    ) {
+        actions.forEach { action ->
+            val view = inflateActionView(inflater, action)
+            binding.fileActionsList.addView(view)
+        }
+    }
+
+    private fun inflateActionView(inflater: LayoutInflater, action: FileAction): View {
+        val itemBinding = FileActionsBottomSheetItemBinding.inflate(inflater, binding.fileActionsList, false)
+            .apply {
+                root.setOnClickListener {
+                    viewModel.onClick(action)
+                }
+                text.setText(action.title)
+                if (action.icon != null) {
+                    val drawable =
+                        viewThemeUtils.platform.tintDrawable(
+                            requireContext(),
+                            resources.getDrawable(action.icon)
+                        )
+                    icon.setImageDrawable(drawable)
+                }
+            }
+        return itemBinding.root
+    }
+
+    private fun dispatchActionClick(id: Int?) {
+        if (id != null) {
+            clickListener.onClick(id)
+            dismiss()
+        }
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+
     interface ClickListener {
         fun onClick(@IdRes itemId: Int)
     }

+ 83 - 0
app/src/main/java/com/nextcloud/ui/fileactions/FileActionsViewModel.kt

@@ -0,0 +1,83 @@
+/*
+ * Nextcloud Android client application
+ *
+ *  @author Álvaro Brey
+ *  Copyright (C) 2022 Álvaro Brey
+ *  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
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.nextcloud.ui.fileactions
+
+import android.app.Application
+import android.content.Context
+import androidx.annotation.IdRes
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.nextcloud.client.account.CurrentAccountProvider
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.files.FileMenuFilter
+import com.owncloud.android.ui.activity.ComponentsGetter
+import javax.inject.Inject
+
+class FileActionsViewModel @Inject constructor(
+    application: Application,
+    private val currentAccountProvider: CurrentAccountProvider
+) :
+    AndroidViewModel(application) {
+
+    sealed interface UiState {
+        object Loading : UiState
+        class Loaded(val actions: List<FileAction>) : UiState
+    }
+
+    private val context: Context
+        get() = getApplication()
+
+    private val _uiState: MutableLiveData<UiState> = MutableLiveData(UiState.Loading)
+    val uiState: LiveData<UiState>
+        get() = _uiState
+
+    private val _clickActionId: MutableLiveData<Int?> = MutableLiveData(null)
+    val clickActionId: LiveData<Int?>
+        @IdRes
+        get() = _clickActionId
+
+    fun load(
+        files: Collection<OCFile>,
+        componentsGetter: ComponentsGetter,
+        numberOfAllFiles: Int,
+        isOverflow: Boolean
+    ) {
+        val toHide = FileMenuFilter(
+            numberOfAllFiles,
+            files.toList(),
+            componentsGetter,
+            context,
+            isOverflow,
+            currentAccountProvider.user
+        )
+            .getToHide(false)
+        val availableActions = FileAction.SORTED_VALUES
+            .filter { it.id !in toHide }
+        _uiState.value = UiState.Loaded(availableActions)
+    }
+
+    fun onClick(action: FileAction) {
+        _clickActionId.value = action.id
+    }
+}