浏览代码

File/folder choosers load contents on a separate thread. Part of #1615.

Aidan Follestad 6 年之前
父节点
当前提交
ca71954cad

+ 5 - 6
core/src/main/java/com/afollestad/materialdialogs/MaterialDialog.kt

@@ -105,7 +105,7 @@ class MaterialDialog(
    * @param res The drawable resource to display as the drawable.
    * @param drawable The drawable to display as the drawable.
    */
-  @CheckResult fun icon(
+  fun icon(
     @DrawableRes res: Int? = null,
     drawable: Drawable? = null
   ): MaterialDialog {
@@ -124,7 +124,7 @@ class MaterialDialog(
    * @param res The string resource to display as the title.
    * @param text The literal string to display as the title.
    */
-  @CheckResult fun title(
+  fun title(
     @StringRes res: Int? = null,
     text: String? = null
   ): MaterialDialog {
@@ -145,7 +145,7 @@ class MaterialDialog(
    * @param res The string resource to display as the message.
    * @param text The literal string to display as the message.
    */
-  @CheckResult fun message(
+  fun message(
     @StringRes res: Int? = null,
     text: CharSequence? = null
   ): MaterialDialog {
@@ -164,7 +164,7 @@ class MaterialDialog(
    * @param text The literal string to display on the button.
    * @param click A listener to invoke when the button is pressed.
    */
-  @CheckResult fun positiveButton(
+  fun positiveButton(
     @StringRes res: Int? = null,
     text: CharSequence? = null,
     click: DialogCallback? = null
@@ -198,7 +198,7 @@ class MaterialDialog(
    * @param text The literal string to display on the button.
    * @param click A listener to invoke when the button is pressed.
    */
-  @CheckResult fun negativeButton(
+  fun negativeButton(
     @StringRes res: Int? = null,
     text: CharSequence? = null,
     click: DialogCallback? = null
@@ -224,7 +224,6 @@ class MaterialDialog(
     return this
   }
 
-  @CheckResult
   @Deprecated(
       "Use of neutral buttons is discouraged, see " +
           "https://material.io/design/components/dialogs.html#actions."

+ 30 - 36
files/src/main/java/com/afollestad/materialdialogs/files/FileChooserAdapter.kt

@@ -17,6 +17,7 @@ import com.afollestad.materialdialogs.MaterialDialog
 import com.afollestad.materialdialogs.WhichButton.POSITIVE
 import com.afollestad.materialdialogs.actions.hasActionButtons
 import com.afollestad.materialdialogs.actions.setActionButtonEnabled
+import com.afollestad.materialdialogs.callbacks.onDismiss
 import com.afollestad.materialdialogs.files.utilext.betterParent
 import com.afollestad.materialdialogs.files.utilext.friendlyName
 import com.afollestad.materialdialogs.files.utilext.getColor
@@ -59,28 +60,23 @@ internal class FileChooserAdapter(
   var selectedFile: File? = null
 
   private var currentFolder = initialFolder
-  private lateinit var contents: List<File>
+  private var listingJob: Job<List<File>>? = null
+  private var contents: List<File>? = null
 
   private val isLightTheme =
     getColor(dialog.windowContext, attr = android.R.attr.textColorPrimary).isColorDark()
 
   init {
+    dialog.onDismiss { listingJob?.abort() }
     loadContents(initialFolder)
   }
 
   fun itemClicked(index: Int) {
-    if (
-        currentFolder.hasParent() &&
-        index == goUpIndex()
-    ) {
+    if (currentFolder.hasParent() && index == goUpIndex()) {
       // go up
       loadContents(currentFolder.betterParent()!!)
       return
-    } else if (
-        currentFolder.canWrite() &&
-        allowFolderCreation &&
-        index == newFolderIndex()
-    ) {
+    } else if (currentFolder.canWrite() && allowFolderCreation && index == newFolderIndex()) {
       // New folder
       dialog.showNewFolderCreator(
           parent = currentFolder,
@@ -93,7 +89,7 @@ internal class FileChooserAdapter(
     }
 
     val actualIndex = actualIndex(index)
-    val selected = contents[actualIndex].jumpOverEmulated()
+    val selected = contents!![actualIndex].jumpOverEmulated()
 
     if (selected.isDirectory) {
       loadContents(selected)
@@ -122,23 +118,28 @@ internal class FileChooserAdapter(
     this.currentFolder = directory
     dialog.title(text = directory.friendlyName())
 
-    val rawContents = directory.listFiles() ?: emptyArray()
-    if (onlyFolders) {
-      this.contents = rawContents
-          .filter { it.isDirectory && filter?.invoke(it) ?: true }
-          .sortedBy { it.name.toLowerCase() }
-    } else {
-      this.contents = rawContents
-          .filter { filter?.invoke(it) ?: true }
-          .sortedWith(compareBy({ !it.isDirectory }, { it.nameWithoutExtension.toLowerCase() }))
-    }
+    listingJob?.abort()
+    listingJob = job<List<File>> { _ ->
+      val rawContents = directory.listFiles() ?: emptyArray()
+      if (onlyFolders) {
+        rawContents
+            .filter { it.isDirectory && filter?.invoke(it) ?: true }
+            .sortedBy { it.name.toLowerCase() }
+      } else {
+        rawContents
+            .filter { filter?.invoke(it) ?: true }
+            .sortedWith(compareBy({ !it.isDirectory }, { it.nameWithoutExtension.toLowerCase() }))
+      }
 
-    this.emptyView.setVisible(this.contents.isEmpty())
-    notifyDataSetChanged()
+    }.after {
+      this.contents = it
+      this.emptyView.setVisible(it.isEmpty())
+      notifyDataSetChanged()
+    }
   }
 
   override fun getItemCount(): Int {
-    var count = contents.size
+    var count = contents?.size ?: 0
     if (currentFolder.hasParent()) {
       count += 1
     }
@@ -165,10 +166,7 @@ internal class FileChooserAdapter(
     holder: FileChooserViewHolder,
     position: Int
   ) {
-    if (
-        currentFolder.hasParent() &&
-        position == goUpIndex()
-    ) {
+    if (currentFolder.hasParent() && position == goUpIndex()) {
       // Go up
       holder.iconView.setImageResource(
           if (isLightTheme) R.drawable.icon_return_dark
@@ -179,11 +177,7 @@ internal class FileChooserAdapter(
       return
     }
 
-    if (
-        allowFolderCreation &&
-        currentFolder.canWrite() &&
-        position == newFolderIndex()
-    ) {
+    if (allowFolderCreation && currentFolder.canWrite() && position == newFolderIndex()) {
       // New folder
       holder.iconView.setImageResource(
           if (isLightTheme) R.drawable.icon_new_folder_dark
@@ -197,7 +191,7 @@ internal class FileChooserAdapter(
     }
 
     val actualIndex = actualIndex(position)
-    val item = contents[actualIndex]
+    val item = contents!![actualIndex]
     holder.iconView.setImageResource(item.iconRes())
     holder.nameView.text = item.name
     holder.itemView.isActivated = selectedFile?.absolutePath == item.absolutePath ?: false
@@ -230,8 +224,8 @@ internal class FileChooserAdapter(
 
   private fun getSelectedIndex(): Int {
     if (selectedFile == null) return -1
-    else if (contents.isEmpty()) return -1
-    val index = contents.indexOfFirst { it.absolutePath == selectedFile!!.absolutePath }
+    else if (contents?.isEmpty() == true) return -1
+    val index = contents?.indexOfFirst { it.absolutePath == selectedFile!!.absolutePath } ?: -1
     return if (index > -1 && currentFolder.hasParent()) index + 1 else index
   }
 }

+ 41 - 0
files/src/main/java/com/afollestad/materialdialogs/files/Job.kt

@@ -0,0 +1,41 @@
+package com.afollestad.materialdialogs.files
+
+import android.os.Handler
+
+internal typealias Execution<T> = (Job<T>) -> T
+internal typealias PostExecution<T> = (T) -> Unit
+
+// Can probably be replaced with coroutines
+internal class Job<T>(private val execution: Execution<T>) {
+
+  private var thread: Thread? = null
+  private var after: ((T) -> Unit)? = null
+  private var handler = Handler()
+
+  var isAborted: Boolean = false
+    private set
+
+  fun after(after: PostExecution<T>): Job<T> {
+    this.after = after
+    return execute()
+  }
+
+  fun abort() {
+    thread?.interrupt()
+    thread = null
+  }
+
+  private fun execute(): Job<T> {
+    thread = Thread(Runnable {
+      val result = execution(this@Job)
+      if (isAborted) return@Runnable
+      handler.post { after?.invoke(result) }
+    })
+    thread!!.start()
+    return this
+  }
+}
+
+internal fun <T> job(execution: Execution<T>): Job<T> {
+  return Job(execution)
+}