Browse Source

Replace material-dialogs usage with Material Components' (#5423)

* Use Material Components' dialogs

For all dialogs that has direct replacement.

* Convert text input dialogs

* Convert quad-state multi choices dialogs

* Convert date picker dialogs

This also changes the flow to remove selected start/finish tracking date and
the track item itself

* Remove material-dialogs dependencies
Ivan Iskandar 3 years ago
parent
commit
ae97bb0445
56 changed files with 702 additions and 697 deletions
  1. 0 6
      app/build.gradle.kts
  2. 3 6
      app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt
  3. 7 6
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionTrustDialog.kt
  4. 22 22
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt
  5. 5 8
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt
  6. 4 7
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt
  7. 9 11
      app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryCreateDialog.kt
  8. 9 11
      app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryRenameDialog.kt
  9. 21 20
      app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCategoriesDialog.kt
  10. 7 6
      app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCoverDialog.kt
  11. 15 12
      app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt
  12. 6 5
      app/src/main/java/eu/kanade/tachiyomi/ui/main/WhatsNewDialogController.kt
  13. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
  14. 6 5
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DeleteChaptersDialog.kt
  15. 7 7
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt
  16. 7 10
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/SetChapterSettingsDialog.kt
  17. 14 21
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt
  18. 0 87
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt
  19. 14 21
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt
  20. 11 14
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt
  21. 6 4
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt
  22. 39 5
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt
  23. 0 5
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt
  24. 73 18
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt
  25. 7 6
      app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
  26. 5 6
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt
  27. 6 5
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt
  28. 7 7
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt
  29. 6 5
      app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/ConfirmDeleteChaptersDialog.kt
  30. 6 5
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
  31. 34 27
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
  32. 31 27
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
  33. 33 34
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt
  34. 6 5
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/TrackLogoutDialog.kt
  35. 26 0
      app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt
  36. 54 0
      app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialAlertDialogBuilderExtensions.kt
  37. 0 26
      app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialDialogMultiChoiceExt.kt
  38. 0 34
      app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt
  39. 19 104
      app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceDialogAdapter.kt
  40. 4 7
      app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceViewHolder.kt
  41. 45 0
      app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateTextView.kt
  42. 7 10
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt
  43. 10 0
      app/src/main/res/drawable/ic_more_vert_24.xml
  44. 4 2
      app/src/main/res/layout/common_dialog_with_checkbox.xml
  45. 18 0
      app/src/main/res/layout/dialog_quadstatemultichoice_item.xml
  46. 9 0
      app/src/main/res/layout/dialog_stub_quadstatemultichoice.xml
  47. 20 0
      app/src/main/res/layout/dialog_stub_textinput.xml
  48. 2 1
      app/src/main/res/layout/download_custom_amount.xml
  49. 0 15
      app/src/main/res/layout/md_listitem_quadstatemultichoice.xml
  50. 27 19
      app/src/main/res/layout/track_item.xml
  51. 15 0
      app/src/main/res/menu/track_item.xml
  52. 15 0
      app/src/main/res/menu/track_item_date.xml
  53. 0 8
      app/src/main/res/menu/track_search.xml
  54. 0 3
      app/src/main/res/values/dimens.xml
  55. 0 15
      app/src/main/res/values/styles.xml
  56. 0 8
      app/src/main/res/values/themes.xml

+ 0 - 6
app/build.gradle.kts

@@ -245,12 +245,6 @@ dependencies {
     implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0")
     implementation("dev.chrisbanes.insetter:insetter:0.6.0")
 
-    // 3.2.0+ introduces weird UI blinking or cut off issues on some devices
-    val materialDialogsVersion = "3.1.1"
-    implementation("com.afollestad.material-dialogs:core:$materialDialogsVersion")
-    implementation("com.afollestad.material-dialogs:input:$materialDialogsVersion")
-    implementation("com.afollestad.material-dialogs:datetime:$materialDialogsVersion")
-
     // Conductor
     val conductorVersion = "3.0.0"
     implementation("com.bluelinelabs:conductor:$conductorVersion")

+ 3 - 6
app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt

@@ -2,9 +2,6 @@ package eu.kanade.tachiyomi.data.track.anilist
 
 import android.net.Uri
 import androidx.core.net.toUri
-import com.afollestad.date.dayOfMonth
-import com.afollestad.date.month
-import com.afollestad.date.year
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
 import eu.kanade.tachiyomi.network.POST
@@ -315,9 +312,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
         val calendar = Calendar.getInstance()
         calendar.timeInMillis = dateValue
         return buildJsonObject {
-            put("year", calendar.year)
-            put("month", calendar.month + 1)
-            put("day", calendar.dayOfMonth)
+            put("year", calendar.get(Calendar.YEAR))
+            put("month", calendar.get(Calendar.MONTH) + 1)
+            put("day", calendar.get(Calendar.DAY_OF_MONTH))
         }
     }
 

+ 7 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionTrustDialog.kt

@@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.browse.extension
 import android.app.Dialog
 import android.os.Bundle
 import androidx.core.os.bundleOf
-import com.afollestad.materialdialogs.MaterialDialog
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 
@@ -21,15 +21,16 @@ class ExtensionTrustDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
     }
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .title(R.string.untrusted_extension)
-            .message(R.string.untrusted_extension_message)
-            .positiveButton(R.string.ext_trust) {
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.untrusted_extension)
+            .setMessage(R.string.untrusted_extension_message)
+            .setPositiveButton(R.string.ext_trust) { _, _ ->
                 (targetController as? Listener)?.trustSignature(args.getString(SIGNATURE_KEY)!!)
             }
-            .negativeButton(R.string.ext_uninstall) {
+            .setNegativeButton(R.string.ext_uninstall) { _, _ ->
                 (targetController as? Listener)?.uninstallExtension(args.getString(PKGNAME_KEY)!!)
             }
+            .create()
     }
 
     private companion object {

+ 22 - 22
app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt

@@ -3,10 +3,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
 import android.app.Dialog
 import android.os.Bundle
 import androidx.core.view.isVisible
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.listItemsMultiChoice
 import com.bluelinelabs.conductor.Controller
 import com.bluelinelabs.conductor.RouterTransaction
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -89,26 +88,26 @@ class SearchController(
         @Suppress("DEPRECATION")
         override fun onCreateDialog(savedViewState: Bundle?): Dialog {
             val prefValue = preferences.migrateFlags().get()
-
-            val preselected =
-                MigrationFlags.getEnabledFlagsPositions(
-                    prefValue
-                )
-
-            return MaterialDialog(activity!!)
-                .title(R.string.migration_dialog_what_to_include)
-                .listItemsMultiChoice(
-                    items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence },
-                    initialSelection = preselected.toIntArray()
-                ) { _, positions, _ ->
+            val enabledFlagsPositions = MigrationFlags.getEnabledFlagsPositions(prefValue)
+            val items = MigrationFlags.titles
+                .map { resources?.getString(it) }
+                .toTypedArray()
+            val selected = items
+                .mapIndexed { i, _ -> enabledFlagsPositions.contains(i) }
+                .toBooleanArray()
+
+            return MaterialAlertDialogBuilder(activity!!)
+                .setTitle(R.string.migration_dialog_what_to_include)
+                .setMultiChoiceItems(items, selected) { _, which, checked ->
+                    selected[which] = checked
+                }
+                .setPositiveButton(R.string.migrate) { _, _ ->
                     // Save current settings for the next time
-                    val newValue =
-                        MigrationFlags.getFlagsFromPositions(
-                            positions.toTypedArray()
-                        )
+                    val selectedIndices = mutableListOf<Int>()
+                    selected.forEachIndexed { i, b -> if (b) selectedIndices.add(i) }
+                    val newValue = MigrationFlags.getFlagsFromPositions(selectedIndices.toTypedArray())
                     preferences.migrateFlags().set(newValue)
-                }
-                .positiveButton(R.string.migrate) {
+
                     if (callingController != null) {
                         if (callingController.javaClass == SourceSearchController::class.java) {
                             router.popController(callingController)
@@ -116,7 +115,7 @@ class SearchController(
                     }
                     (targetController as? SearchController)?.migrateManga(manga, newManga)
                 }
-                .negativeButton(R.string.copy) {
+                .setNegativeButton(R.string.copy) { _, _, ->
                     if (callingController != null) {
                         if (callingController.javaClass == SourceSearchController::class.java) {
                             router.popController(callingController)
@@ -124,7 +123,8 @@ class SearchController(
                     }
                     (targetController as? SearchController)?.copyManga(manga, newManga)
                 }
-                .neutralButton(android.R.string.cancel)
+                .setNeutralButton(android.R.string.cancel, null)
+                .create()
         }
     }
 

+ 5 - 8
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt

@@ -9,10 +9,9 @@ import android.view.MenuInflater
 import android.view.MenuItem
 import android.view.View
 import androidx.recyclerview.widget.LinearLayoutManager
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.listItems
 import com.bluelinelabs.conductor.ControllerChangeHandler
 import com.bluelinelabs.conductor.ControllerChangeType
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import dev.chrisbanes.insetter.applyInsetter
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.davidea.flexibleadapter.items.IFlexible
@@ -238,15 +237,13 @@ class SourceController :
         }
 
         override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-            return MaterialDialog(activity!!)
-                .title(text = source)
-                .listItems(
-                    items = items.map { it.first },
-                    waitForPositiveButton = false
-                ) { dialog, which, _ ->
+            return MaterialAlertDialogBuilder(activity!!)
+                .setTitle(source)
+                .setItems(items.map { it.first }.toTypedArray()) { dialog, which ->
                     items[which].second()
                     dialog.dismiss()
                 }
+                .create()
         }
     }
 

+ 4 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt

@@ -13,8 +13,7 @@ import androidx.core.view.updatePadding
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.listItems
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
 import com.google.android.material.snackbar.Snackbar
 import com.tfcporciuncula.flow.Preference
@@ -589,11 +588,9 @@ open class BrowseSourceController(bundle: Bundle) :
         val manga = (adapter?.getItem(position) as? SourceItem?)?.manga ?: return
 
         if (manga.favorite) {
-            MaterialDialog(activity)
-                .listItems(
-                    items = listOf(activity.getString(R.string.remove_from_library)),
-                    waitForPositiveButton = false
-                ) { _, which, _ ->
+            MaterialAlertDialogBuilder(activity)
+                .setTitle(manga.title)
+                .setItems(arrayOf(activity.getString(R.string.remove_from_library))) { _, which ->
                     when (which) {
                         0 -> {
                             presenter.changeMangaFavorite(manga)

+ 9 - 11
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryCreateDialog.kt

@@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.category
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.input.input
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
 
 /**
  * Dialog to create a new category for the library.
@@ -30,18 +30,16 @@ class CategoryCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
      * @return a new dialog instance.
      */
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .title(R.string.action_add_category)
-            .negativeButton(android.R.string.cancel)
-            .input(
-                hint = resources?.getString(R.string.name),
-                prefill = currentName
-            ) { _, input ->
-                currentName = input.toString()
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.action_add_category)
+            .setTextInput(prefill = currentName) {
+                currentName = it
             }
-            .positiveButton(android.R.string.ok) {
+            .setPositiveButton(android.R.string.ok) { _, _ ->
                 (targetController as? Listener)?.createCategory(currentName)
             }
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 9 - 11
app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryRenameDialog.kt

@@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.ui.category
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.input.input
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Category
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.widget.materialdialogs.setTextInput
 
 /**
  * Dialog to rename an existing category of the library.
@@ -35,16 +35,14 @@ class CategoryRenameDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
      * @return a new dialog instance.
      */
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .title(R.string.action_rename_category)
-            .negativeButton(android.R.string.cancel)
-            .input(
-                hint = resources?.getString(R.string.name),
-                prefill = currentName
-            ) { _, input ->
-                currentName = input.toString()
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.action_rename_category)
+            .setTextInput(prefill = currentName) {
+                currentName = it
             }
-            .positiveButton(android.R.string.ok) { onPositive() }
+            .setPositiveButton(android.R.string.ok) { _, _ -> onPositive() }
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     /**

+ 21 - 20
app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCategoriesDialog.kt

@@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.library
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.listItemsMultiChoice
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Category
 import eu.kanade.tachiyomi.data.database.models.Manga
@@ -32,32 +31,34 @@ class ChangeMangaCategoriesDialog<T>(bundle: Bundle? = null) :
     }
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .title(R.string.action_move_category)
-            .negativeButton(android.R.string.cancel)
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.action_move_category)
+            .setNegativeButton(android.R.string.cancel, null)
             .apply {
                 if (categories.isNotEmpty()) {
-                    listItemsMultiChoice(
-                        items = categories.map { it.name },
-                        initialSelection = preselected.toIntArray(),
-                        allowEmptySelection = true
-                    ) { _, selections, _ ->
-                        val newCategories = selections.map { categories[it] }
+                    val selected = categories
+                        .mapIndexed { i, _ -> preselected.contains(i) }
+                        .toBooleanArray()
+                    setMultiChoiceItems(categories.map { it.name }.toTypedArray(), selected) { _, which, checked ->
+                        selected[which] = checked
+                    }
+                    setPositiveButton(android.R.string.ok) { _, _ ->
+                        val newCategories = categories.filterIndexed { i, _ -> selected[i] }
                         (targetController as? Listener)?.updateCategoriesForMangas(mangas, newCategories)
                     }
-                        .positiveButton(android.R.string.ok)
                 } else {
-                    message(R.string.information_empty_category_dialog)
-                        .positiveButton(R.string.action_edit_categories) {
-                            if (targetController is LibraryController) {
-                                val libController = targetController as LibraryController
-                                libController.clearSelection()
-                            }
-                            router.popCurrentController()
-                            router.pushController(CategoryController().withFadeTransaction())
+                    setMessage(R.string.information_empty_category_dialog)
+                    setPositiveButton(R.string.action_edit_categories) { _, _ ->
+                        if (targetController is LibraryController) {
+                            val libController = targetController as LibraryController
+                            libController.clearSelection()
                         }
+                        router.popCurrentController()
+                        router.pushController(CategoryController().withFadeTransaction())
+                    }
                 }
             }
+            .create()
     }
 
     interface Listener {

+ 7 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/library/ChangeMangaCoverDialog.kt

@@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.library
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
@@ -20,15 +20,16 @@ class ChangeMangaCoverDialog<T>(bundle: Bundle? = null) :
 
     @Suppress("DEPRECATION")
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .title(R.string.action_edit_cover)
-            .positiveButton(R.string.action_edit) {
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.action_edit_cover)
+            .setPositiveButton(R.string.action_edit) { _, _ ->
                 (targetController as? Listener)?.openMangaCoverPicker(manga)
             }
-            .negativeButton(android.R.string.cancel)
-            .neutralButton(R.string.action_delete) {
+            .setNegativeButton(android.R.string.cancel, null)
+            .setNeutralButton(R.string.action_delete) { _, _ ->
                 (targetController as? Listener)?.deleteMangaCover(manga)
             }
+            .create()
     }
 
     interface Listener {

+ 15 - 12
app/src/main/java/eu/kanade/tachiyomi/ui/library/DeleteLibraryMangasDialog.kt

@@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.library
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.listItemsMultiChoice
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
@@ -20,18 +19,22 @@ class DeleteLibraryMangasDialog<T>(bundle: Bundle? = null) :
     }
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .title(R.string.action_remove)
-            .listItemsMultiChoice(
-                R.array.delete_selected_mangas,
-                initialSelection = intArrayOf(0)
-            ) { _, selections, _ ->
-                val deleteFromLibrary = 0 in selections
-                val deleteChapters = 1 in selections
+        val items = resources!!.getStringArray(R.array.delete_selected_mangas)
+        val selected = items
+            .mapIndexed { i, _ -> i == 0 }
+            .toBooleanArray()
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.action_remove)
+            .setMultiChoiceItems(items, selected) { _, which, checked ->
+                selected[which] = checked
+            }
+            .setPositiveButton(android.R.string.ok) { _, _ ->
+                val deleteFromLibrary = selected[0]
+                val deleteChapters = selected[1]
                 (targetController as? Listener)?.deleteMangas(mangas, deleteFromLibrary, deleteChapters)
             }
-            .positiveButton(android.R.string.ok)
-            .negativeButton(android.R.string.cancel)
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/main/WhatsNewDialogController.kt

@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.main
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
@@ -12,11 +12,12 @@ class WhatsNewDialogController(bundle: Bundle? = null) : DialogController(bundle
 
     @Suppress("DEPRECATION")
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .title(text = activity!!.getString(R.string.updated_version, BuildConfig.VERSION_NAME))
-            .positiveButton(android.R.string.ok)
-            .neutralButton(R.string.whats_new) {
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(activity!!.getString(R.string.updated_version, BuildConfig.VERSION_NAME))
+            .setPositiveButton(android.R.string.ok, null)
+            .setNeutralButton(R.string.whats_new) { _, _ ->
                 openInBrowser("https://github.com/tachiyomiorg/tachiyomi/releases/tag/v${BuildConfig.VERSION_NAME}")
             }
+            .create()
     }
 }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt

@@ -289,7 +289,7 @@ class MangaController :
             }
         }
 
-        trackSheet = TrackSheet(this, manga!!)
+        trackSheet = TrackSheet(this, manga!!, (activity as MainActivity).supportFragmentManager)
 
         updateFilterIconState()
     }

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DeleteChaptersDialog.kt

@@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.manga.chapter
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 
@@ -15,12 +15,13 @@ class DeleteChaptersDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
     }
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .message(R.string.confirm_delete_chapters)
-            .positiveButton(android.R.string.ok) {
+        return MaterialAlertDialogBuilder(activity!!)
+            .setMessage(R.string.confirm_delete_chapters)
+            .setPositiveButton(android.R.string.ok) { _, _ ->
                 (targetController as? Listener)?.deleteChapters()
             }
-            .negativeButton(android.R.string.cancel)
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 7 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/DownloadCustomChaptersDialog.kt

@@ -3,9 +3,8 @@ package eu.kanade.tachiyomi.ui.manga.chapter
 import android.app.Dialog
 import android.os.Bundle
 import androidx.core.os.bundleOf
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.customview.customView
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 import eu.kanade.tachiyomi.widget.DialogCustomDownloadView
@@ -57,13 +56,14 @@ class DownloadCustomChaptersDialog<T> : DialogController
 
         // Build dialog.
         // when positive dialog is pressed call custom listener.
-        return MaterialDialog(activity)
-            .title(R.string.custom_download)
-            .customView(view = view, scrollable = true)
-            .positiveButton(android.R.string.ok) {
+        return MaterialAlertDialogBuilder(activity)
+            .setTitle(R.string.custom_download)
+            .setView(view)
+            .setPositiveButton(android.R.string.ok) { _, _ ->
                 (targetController as? Listener)?.downloadCustomChapters(view.amount)
             }
-            .negativeButton(android.R.string.cancel)
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 7 - 10
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/SetChapterSettingsDialog.kt

@@ -3,8 +3,7 @@ package eu.kanade.tachiyomi.ui.manga.chapter
 import android.app.Dialog
 import android.os.Bundle
 import androidx.core.os.bundleOf
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.customview.customView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
@@ -24,13 +23,10 @@ class SetChapterSettingsDialog(bundle: Bundle? = null) : DialogController(bundle
             setOptionDescription(R.string.also_set_chapter_settings_for_library)
         }
 
-        return MaterialDialog(activity!!)
-            .title(R.string.chapter_settings)
-            .customView(
-                view = view,
-                horizontalPadding = true
-            )
-            .positiveButton(android.R.string.ok) {
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.chapter_settings)
+            .setView(view)
+            .setPositiveButton(android.R.string.ok) { _, _ ->
                 ChapterSettingsHelper.setGlobalSettings(args.getSerializable(MANGA_KEY)!! as Manga)
                 if (view.isChecked()) {
                     ChapterSettingsHelper.updateAllMangasWithGlobalDefaults()
@@ -38,7 +34,8 @@ class SetChapterSettingsDialog(bundle: Bundle? = null) : DialogController(bundle
 
                 activity?.toast(activity!!.getString(R.string.chapter_settings_updated))
             }
-            .negativeButton(android.R.string.cancel)
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     private companion object {

+ 14 - 21
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackChaptersDialog.kt

@@ -2,15 +2,14 @@ package eu.kanade.tachiyomi.ui.manga.track
 
 import android.app.Dialog
 import android.os.Bundle
-import android.widget.NumberPicker
+import android.view.LayoutInflater
 import androidx.core.os.bundleOf
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.customview.customView
-import com.afollestad.materialdialogs.customview.getCustomView
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.TrackManager
+import eu.kanade.tachiyomi.databinding.TrackChaptersDialogBinding
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -38,23 +37,9 @@ class SetTrackChaptersDialog<T> : DialogController
     }
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        val item = item
+        val pickerView = TrackChaptersDialogBinding.inflate(LayoutInflater.from(activity!!))
+        val np = pickerView.chaptersPicker
 
-        val dialog = MaterialDialog(activity!!)
-            .title(R.string.chapters)
-            .customView(R.layout.track_chapters_dialog, dialogWrapContent = false)
-            .positiveButton(android.R.string.ok) { dialog ->
-                val view = dialog.getCustomView()
-                // Remove focus to update selected number
-                val np: NumberPicker = view.findViewById(R.id.chapters_picker)
-                np.clearFocus()
-
-                listener.setChaptersRead(item, np.value)
-            }
-            .negativeButton(android.R.string.cancel)
-
-        val view = dialog.getCustomView()
-        val np: NumberPicker = view.findViewById(R.id.chapters_picker)
         // Set initial value
         np.value = item.track?.last_chapter_read ?: 0
 
@@ -66,7 +51,15 @@ class SetTrackChaptersDialog<T> : DialogController
         // Don't allow to go from 0 to 9999
         np.wrapSelectorWheel = false
 
-        return dialog
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.chapters)
+            .setView(pickerView.root)
+            .setPositiveButton(android.R.string.ok) { _, _ ->
+                np.clearFocus()
+                listener.setChaptersRead(item, np.value)
+            }
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 0 - 87
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackReadingDatesDialog.kt

@@ -1,87 +0,0 @@
-package eu.kanade.tachiyomi.ui.manga.track
-
-import android.app.Dialog
-import android.os.Bundle
-import androidx.core.os.bundleOf
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.datetime.datePicker
-import com.bluelinelabs.conductor.Controller
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.database.models.Track
-import eu.kanade.tachiyomi.data.track.TrackManager
-import eu.kanade.tachiyomi.ui.base.controller.DialogController
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import java.util.Calendar
-
-class SetTrackReadingDatesDialog<T> : DialogController
-        where T : Controller {
-
-    private val item: TrackItem
-
-    private val dateToUpdate: ReadingDate
-
-    private lateinit var listener: Listener
-
-    constructor(target: T, listener: Listener, dateToUpdate: ReadingDate, item: TrackItem) : super(
-        bundleOf(KEY_ITEM_TRACK to item.track)
-    ) {
-        targetController = target
-        this.listener = listener
-        this.item = item
-        this.dateToUpdate = dateToUpdate
-    }
-
-    @Suppress("unused")
-    constructor(bundle: Bundle) : super(bundle) {
-        val track = bundle.getSerializable(KEY_ITEM_TRACK) as Track
-        val service = Injekt.get<TrackManager>().getService(track.sync_id)!!
-        item = TrackItem(track, service)
-        dateToUpdate = ReadingDate.Start
-    }
-
-    @Suppress("DEPRECATION")
-    override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .title(
-                when (dateToUpdate) {
-                    ReadingDate.Start -> R.string.track_started_reading_date
-                    ReadingDate.Finish -> R.string.track_finished_reading_date
-                }
-            )
-            .datePicker(currentDate = getCurrentDate()) { _, date ->
-                listener.setReadingDate(item, dateToUpdate, date.timeInMillis)
-            }
-            .neutralButton(R.string.action_remove) {
-                listener.setReadingDate(item, dateToUpdate, 0L)
-            }
-    }
-
-    private fun getCurrentDate(): Calendar {
-        // Today if no date is set, otherwise the already set date
-        return Calendar.getInstance().apply {
-            item.track?.let {
-                val date = when (dateToUpdate) {
-                    ReadingDate.Start -> it.started_reading_date
-                    ReadingDate.Finish -> it.finished_reading_date
-                }
-                if (date != 0L) {
-                    timeInMillis = date
-                }
-            }
-        }
-    }
-
-    interface Listener {
-        fun setReadingDate(item: TrackItem, type: ReadingDate, date: Long)
-    }
-
-    enum class ReadingDate {
-        Start,
-        Finish
-    }
-
-    companion object {
-        private const val KEY_ITEM_TRACK = "SetTrackReadingDatesDialog.item.track"
-    }
-}

+ 14 - 21
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackScoreDialog.kt

@@ -2,15 +2,14 @@ package eu.kanade.tachiyomi.ui.manga.track
 
 import android.app.Dialog
 import android.os.Bundle
-import android.widget.NumberPicker
+import android.view.LayoutInflater
 import androidx.core.os.bundleOf
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.customview.customView
-import com.afollestad.materialdialogs.customview.getCustomView
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.TrackManager
+import eu.kanade.tachiyomi.databinding.TrackScoreDialogBinding
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -38,23 +37,9 @@ class SetTrackScoreDialog<T> : DialogController
     }
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        val item = item
+        val pickerView = TrackScoreDialogBinding.inflate(LayoutInflater.from(activity!!))
+        val np = pickerView.scorePicker
 
-        val dialog = MaterialDialog(activity!!)
-            .title(R.string.score)
-            .customView(R.layout.track_score_dialog, dialogWrapContent = false)
-            .positiveButton(android.R.string.ok) { dialog ->
-                val view = dialog.getCustomView()
-                // Remove focus to update selected number
-                val np: NumberPicker = view.findViewById(R.id.score_picker)
-                np.clearFocus()
-
-                listener.setScore(item, np.value)
-            }
-            .negativeButton(android.R.string.cancel)
-
-        val view = dialog.getCustomView()
-        val np: NumberPicker = view.findViewById(R.id.score_picker)
         val scores = item.service.getScoreList().toTypedArray()
         np.maxValue = scores.size - 1
         np.displayedValues = scores
@@ -66,7 +51,15 @@ class SetTrackScoreDialog<T> : DialogController
             np.value = if (index != -1) index else 0
         }
 
-        return dialog
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.score)
+            .setView(pickerView.root)
+            .setPositiveButton(android.R.string.ok) { _, _ ->
+                np.clearFocus()
+                listener.setScore(item, np.value)
+            }
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 11 - 14
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/SetTrackStatusDialog.kt

@@ -3,9 +3,8 @@ package eu.kanade.tachiyomi.ui.manga.track
 import android.app.Dialog
 import android.os.Bundle
 import androidx.core.os.bundleOf
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.listItemsSingleChoice
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Track
 import eu.kanade.tachiyomi.data.track.TrackManager
@@ -36,22 +35,20 @@ class SetTrackStatusDialog<T> : DialogController
     }
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        val item = item
         val statusList = item.service.getStatusList()
         val statusString = statusList.map { item.service.getStatus(it) }
-        val selectedIndex = statusList.indexOf(item.track?.status)
+        var selectedIndex = statusList.indexOf(item.track?.status)
 
-        return MaterialDialog(activity!!)
-            .title(R.string.status)
-            .negativeButton(android.R.string.cancel)
-            .listItemsSingleChoice(
-                items = statusString,
-                initialSelection = selectedIndex,
-                waitForPositiveButton = false
-            ) { dialog, position, _ ->
-                listener.setStatus(item, position)
-                dialog.dismiss()
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(R.string.status)
+            .setSingleChoiceItems(statusString.toTypedArray(), selectedIndex) { _, which ->
+                selectedIndex = which
             }
+            .setPositiveButton(android.R.string.ok) { _, _ ->
+                listener.setStatus(item, selectedIndex)
+            }
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 6 - 4
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackAdapter.kt

@@ -5,7 +5,6 @@ import android.view.ViewGroup
 import androidx.recyclerview.widget.RecyclerView
 import eu.kanade.tachiyomi.databinding.TrackItemBinding
 import eu.kanade.tachiyomi.util.view.applyElevationOverlay
-import uy.kohesive.injekt.api.get
 
 class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter<TrackHolder>() {
 
@@ -40,13 +39,16 @@ class TrackAdapter(listener: OnClickListener) : RecyclerView.Adapter<TrackHolder
     }
 
     interface OnClickListener {
-        fun onLogoClick(position: Int)
+        fun onOpenInBrowserClick(position: Int)
         fun onSetClick(position: Int)
         fun onTitleLongClick(position: Int)
         fun onStatusClick(position: Int)
         fun onChaptersClick(position: Int)
         fun onScoreClick(position: Int)
-        fun onStartDateClick(position: Int)
-        fun onFinishDateClick(position: Int)
+        fun onStartDateEditClick(position: Int)
+        fun onStartDateRemoveClick(position: Int)
+        fun onFinishDateEditClick(position: Int)
+        fun onFinishDateRemoveClick(position: Int)
+        fun onRemoveItemClick(position: Int)
     }
 }

+ 39 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackHolder.kt

@@ -6,6 +6,7 @@ import androidx.recyclerview.widget.RecyclerView
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.databinding.TrackItemBinding
+import eu.kanade.tachiyomi.util.view.popupMenu
 import uy.kohesive.injekt.injectLazy
 import java.text.DateFormat
 
@@ -17,10 +18,9 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
         preferences.dateFormat()
     }
 
-    init {
-        val listener = adapter.rowClickListener
+    private val listener = adapter.rowClickListener
 
-        binding.logoContainer.setOnClickListener { listener.onLogoClick(bindingAdapterPosition) }
+    init {
         binding.trackSet.setOnClickListener { listener.onSetClick(bindingAdapterPosition) }
         binding.trackTitle.setOnClickListener { listener.onSetClick(bindingAdapterPosition) }
         binding.trackTitle.setOnLongClickListener {
@@ -30,8 +30,6 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
         binding.trackStatus.setOnClickListener { listener.onStatusClick(bindingAdapterPosition) }
         binding.trackChapters.setOnClickListener { listener.onChaptersClick(bindingAdapterPosition) }
         binding.trackScore.setOnClickListener { listener.onScoreClick(bindingAdapterPosition) }
-        binding.trackStartDate.setOnClickListener { listener.onStartDateClick(bindingAdapterPosition) }
-        binding.trackFinishDate.setOnClickListener { listener.onFinishDateClick(bindingAdapterPosition) }
     }
 
     @SuppressLint("SetTextI18n")
@@ -42,6 +40,7 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
 
         binding.trackSet.isVisible = track == null
         binding.trackTitle.isVisible = track != null
+        binding.more.isVisible = track != null
 
         binding.middleRow.isVisible = track != null
         binding.bottomDivider.isVisible = track != null
@@ -77,20 +76,55 @@ class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter)
                 if (track.started_reading_date != 0L) {
                     binding.trackStartDate.text = dateFormat.format(track.started_reading_date)
                     binding.trackStartDate.alpha = SET_STATUS_TEXT_ALPHA
+                    binding.trackStartDate.setOnClickListener {
+                        it.popupMenu(R.menu.track_item_date) {
+                            when (itemId) {
+                                R.id.action_edit -> listener.onStartDateEditClick(bindingAdapterPosition)
+                                R.id.action_remove -> listener.onStartDateRemoveClick(bindingAdapterPosition)
+                            }
+                        }
+                    }
                 } else {
                     binding.trackStartDate.text = ctx.getString(R.string.track_started_reading_date)
                     binding.trackStartDate.alpha = UNSET_STATUS_TEXT_ALPHA
+                    binding.trackStartDate.setOnClickListener {
+                        listener.onStartDateEditClick(bindingAdapterPosition)
+                    }
                 }
                 if (track.finished_reading_date != 0L) {
                     binding.trackFinishDate.text = dateFormat.format(track.finished_reading_date)
                     binding.trackFinishDate.alpha = SET_STATUS_TEXT_ALPHA
+                    binding.trackFinishDate.setOnClickListener {
+                        it.popupMenu(R.menu.track_item_date) {
+                            when (itemId) {
+                                R.id.action_edit -> listener.onFinishDateEditClick(bindingAdapterPosition)
+                                R.id.action_remove -> listener.onFinishDateRemoveClick(bindingAdapterPosition)
+                            }
+                        }
+                    }
                 } else {
                     binding.trackFinishDate.text = ctx.getString(R.string.track_finished_reading_date)
                     binding.trackFinishDate.alpha = UNSET_STATUS_TEXT_ALPHA
+                    binding.trackFinishDate.setOnClickListener {
+                        listener.onFinishDateEditClick(bindingAdapterPosition)
+                    }
                 }
             }
             binding.bottomDivider.isVisible = supportsReadingDates
             binding.bottomRow.isVisible = supportsReadingDates
+
+            binding.more.setOnClickListener {
+                it.popupMenu(R.menu.track_item) {
+                    when (itemId) {
+                        R.id.action_open_in_browser -> {
+                            listener.onOpenInBrowserClick(bindingAdapterPosition)
+                        }
+                        R.id.action_remove -> {
+                            listener.onRemoveItemClick(bindingAdapterPosition)
+                        }
+                    }
+                }
+            }
         }
     }
 

+ 0 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchDialog.kt

@@ -74,14 +74,9 @@ class TrackSearchDialog : DialogController {
                         dialog?.dismiss()
                     }
                 }
-                R.id.remove -> {
-                    trackController.presenter.unregisterTracking(service)
-                    dialog?.dismiss()
-                }
             }
             true
         }
-        binding!!.toolbar.menu.findItem(R.id.remove).isVisible = currentTrackUrl != null
 
         // Create adapter
         adapter = TrackSearchAdapter(currentTrackUrl) { which ->

+ 73 - 18
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt

@@ -3,9 +3,14 @@ package eu.kanade.tachiyomi.ui.manga.track
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
+import androidx.fragment.app.FragmentManager
 import androidx.recyclerview.widget.LinearLayoutManager
 import com.google.android.material.bottomsheet.BottomSheetBehavior
-import eu.kanade.tachiyomi.R.string
+import com.google.android.material.datepicker.CalendarConstraints
+import com.google.android.material.datepicker.DateValidatorPointBackward
+import com.google.android.material.datepicker.DateValidatorPointForward
+import com.google.android.material.datepicker.MaterialDatePicker
+import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.track.UnattendedTrackService
 import eu.kanade.tachiyomi.databinding.TrackControllerBinding
@@ -13,6 +18,7 @@ import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.util.lang.launchIO
+import eu.kanade.tachiyomi.util.lang.toUtcCalendar
 import eu.kanade.tachiyomi.util.lang.withUIContext
 import eu.kanade.tachiyomi.util.system.copyToClipboard
 import eu.kanade.tachiyomi.util.system.toast
@@ -23,13 +29,13 @@ import uy.kohesive.injekt.api.get
 class TrackSheet(
     val controller: MangaController,
     val manga: Manga,
+    val fragmentManager: FragmentManager,
     private val sourceManager: SourceManager = Injekt.get()
 ) : BaseBottomSheetDialog(controller.activity!!),
     TrackAdapter.OnClickListener,
     SetTrackStatusDialog.Listener,
     SetTrackChaptersDialog.Listener,
-    SetTrackScoreDialog.Listener,
-    SetTrackReadingDatesDialog.Listener {
+    SetTrackScoreDialog.Listener {
 
     private lateinit var binding: TrackControllerBinding
 
@@ -63,7 +69,7 @@ class TrackSheet(
         }
     }
 
-    override fun onLogoClick(position: Int) {
+    override fun onOpenInBrowserClick(position: Int) {
         val track = adapter.getItem(position)?.track ?: return
 
         if (track.tracking_url.isNotBlank()) {
@@ -81,7 +87,7 @@ class TrackSheet(
             }
 
             if (!item.service.accept(sourceManager.getOrStub(manga.source))) {
-                controller.presenter.view?.applicationContext?.toast(string.source_unsupported)
+                controller.presenter.view?.applicationContext?.toast(R.string.source_unsupported)
                 return
             }
 
@@ -90,9 +96,9 @@ class TrackSheet(
                     item.service.match(manga)?.let { track ->
                         controller.presenter.registerTracking(track, item.service)
                     }
-                        ?: withUIContext { controller.presenter.view?.applicationContext?.toast(string.error_no_match) }
+                        ?: withUIContext { controller.presenter.view?.applicationContext?.toast(R.string.error_no_match) }
                 } catch (e: Exception) {
-                    withUIContext { controller.presenter.view?.applicationContext?.toast(string.error_no_match) }
+                    withUIContext { controller.presenter.view?.applicationContext?.toast(R.string.error_no_match) }
                 }
             }
         } else {
@@ -128,18 +134,74 @@ class TrackSheet(
         SetTrackScoreDialog(controller, this, item).showDialog(controller.router)
     }
 
-    override fun onStartDateClick(position: Int) {
+    override fun onStartDateEditClick(position: Int) {
         val item = adapter.getItem(position) ?: return
         if (item.track == null) return
 
-        SetTrackReadingDatesDialog(controller, this, SetTrackReadingDatesDialog.ReadingDate.Start, item).showDialog(controller.router)
+        val selection = item.track.started_reading_date.toUtcCalendar()?.timeInMillis
+            ?: MaterialDatePicker.todayInUtcMilliseconds()
+
+        // No time travellers allowed
+        val constraints = CalendarConstraints.Builder().apply {
+            val finishedMillis = item.track.finished_reading_date.toUtcCalendar()?.timeInMillis
+            if (finishedMillis != null) {
+                setValidator(DateValidatorPointBackward.before(finishedMillis))
+            }
+        }.build()
+
+        val picker = MaterialDatePicker.Builder.datePicker()
+            .setTitleText(R.string.track_started_reading_date)
+            .setSelection(selection)
+            .setCalendarConstraints(constraints)
+            .build()
+        picker.addOnPositiveButtonClickListener {
+            controller.presenter.setTrackerStartDate(item, it)
+        }
+        picker.show(fragmentManager, null)
     }
 
-    override fun onFinishDateClick(position: Int) {
+    override fun onFinishDateEditClick(position: Int) {
         val item = adapter.getItem(position) ?: return
         if (item.track == null) return
 
-        SetTrackReadingDatesDialog(controller, this, SetTrackReadingDatesDialog.ReadingDate.Finish, item).showDialog(controller.router)
+        val selection = item.track.finished_reading_date.toUtcCalendar()?.timeInMillis
+            ?: MaterialDatePicker.todayInUtcMilliseconds()
+
+        // No time travellers allowed
+        val constraints = CalendarConstraints.Builder().apply {
+            val startMillis = item.track.started_reading_date.toUtcCalendar()?.timeInMillis
+            if (startMillis != null) {
+                setValidator(DateValidatorPointForward.from(item.track.started_reading_date))
+            }
+        }.build()
+
+        val picker = MaterialDatePicker.Builder.datePicker()
+            .setTitleText(R.string.track_finished_reading_date)
+            .setSelection(selection)
+            .setCalendarConstraints(constraints)
+            .build()
+        picker.addOnPositiveButtonClickListener {
+            controller.presenter.setTrackerFinishDate(item, it)
+        }
+        picker.show(fragmentManager, null)
+    }
+
+    override fun onStartDateRemoveClick(position: Int) {
+        val item = adapter.getItem(position) ?: return
+        if (item.track == null) return
+        controller.presenter.setTrackerStartDate(item, 0)
+    }
+
+    override fun onFinishDateRemoveClick(position: Int) {
+        val item = adapter.getItem(position) ?: return
+        if (item.track == null) return
+        controller.presenter.setTrackerFinishDate(item, 0)
+    }
+
+    override fun onRemoveItemClick(position: Int) {
+        val item = adapter.getItem(position) ?: return
+        if (item.track == null) return
+        controller.presenter.unregisterTracking(item.service)
     }
 
     override fun setStatus(item: TrackItem, selection: Int) {
@@ -154,13 +216,6 @@ class TrackSheet(
         controller.presenter.setTrackerScore(item, score)
     }
 
-    override fun setReadingDate(item: TrackItem, type: SetTrackReadingDatesDialog.ReadingDate, date: Long) {
-        when (type) {
-            SetTrackReadingDatesDialog.ReadingDate.Start -> controller.presenter.setTrackerStartDate(item, date)
-            SetTrackReadingDatesDialog.ReadingDate.Finish -> controller.presenter.setTrackerFinishDate(item, date)
-        }
-    }
-
     fun getSearchDialog(): TrackSearchDialog? {
         return controller.router.getControllerWithTag(TAG_SEARCH_CONTROLLER) as? TrackSearchDialog
     }

+ 7 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt

@@ -4,7 +4,7 @@ import android.app.Dialog
 import android.os.Bundle
 import androidx.core.os.bundleOf
 import androidx.preference.PreferenceScreen
-import com.afollestad.materialdialogs.MaterialDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.mikepenz.aboutlibraries.LibsBuilder
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.R
@@ -133,10 +133,10 @@ class AboutController : SettingsController(), NoToolbarElevationController {
         )
 
         override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-            return MaterialDialog(activity!!)
-                .title(res = R.string.update_check_notification_update_available)
-                .message(text = args.getString(BODY_KEY) ?: "")
-                .positiveButton(R.string.update_check_confirm) {
+            return MaterialAlertDialogBuilder(activity!!)
+                .setTitle(R.string.update_check_notification_update_available)
+                .setMessage(args.getString(BODY_KEY) ?: "")
+                .setPositiveButton(R.string.update_check_confirm) { _, _ ->
                     val appContext = applicationContext
                     if (appContext != null) {
                         // Start download
@@ -144,7 +144,8 @@ class AboutController : SettingsController(), NoToolbarElevationController {
                         UpdaterService.start(appContext, url)
                     }
                 }
-                .negativeButton(R.string.update_check_ignore)
+                .setNegativeButton(R.string.update_check_ignore, null)
+                .create()
         }
 
         private companion object {

+ 5 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt

@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.reader
 
 import android.view.LayoutInflater
 import android.view.View
-import com.afollestad.materialdialogs.MaterialDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.databinding.ReaderPageSheetBinding
 import eu.kanade.tachiyomi.source.model.Page
@@ -35,13 +35,12 @@ class ReaderPageSheet(
     private fun setAsCover() {
         if (page.status != Page.READY) return
 
-        MaterialDialog(activity)
-            .message(R.string.confirm_set_image_as_cover)
-            .positiveButton(android.R.string.ok) {
+        MaterialAlertDialogBuilder(activity)
+            .setMessage(R.string.confirm_set_image_as_cover)
+            .setPositiveButton(android.R.string.ok) { _, _ ->
                 activity.setAsCover(page)
-                dismiss()
             }
-            .negativeButton(android.R.string.cancel)
+            .setNegativeButton(android.R.string.cancel, null)
             .show()
     }
 

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt

@@ -9,7 +9,7 @@ import android.view.MenuItem
 import android.view.View
 import androidx.appcompat.widget.SearchView
 import androidx.recyclerview.widget.LinearLayoutManager
-import com.afollestad.materialdialogs.MaterialDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import dev.chrisbanes.insetter.applyInsetter
 import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.kanade.tachiyomi.R
@@ -221,12 +221,13 @@ class HistoryController :
 
     class ClearHistoryDialogController : DialogController() {
         override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-            return MaterialDialog(activity!!)
-                .message(R.string.clear_history_confirmation)
-                .positiveButton(android.R.string.ok) {
+            return MaterialAlertDialogBuilder(activity!!)
+                .setMessage(R.string.clear_history_confirmation)
+                .setPositiveButton(android.R.string.ok) { _, _ ->
                     (targetController as? HistoryController)?.clearHistory()
                 }
-                .negativeButton(android.R.string.cancel)
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
         }
     }
 

+ 7 - 7
app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/RemoveHistoryDialog.kt

@@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.recent.history
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.customview.customView
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.History
 import eu.kanade.tachiyomi.data.database.models.Manga
@@ -33,11 +32,12 @@ class RemoveHistoryDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
             setOptionDescription(R.string.dialog_with_checkbox_reset)
         }
 
-        return MaterialDialog(activity)
-            .title(R.string.action_remove)
-            .customView(view = dialogCheckboxView, horizontalPadding = true)
-            .positiveButton(R.string.action_remove) { onPositive(dialogCheckboxView.isChecked()) }
-            .negativeButton(android.R.string.cancel)
+        return MaterialAlertDialogBuilder(activity)
+            .setTitle(R.string.action_remove)
+            .setView(dialogCheckboxView)
+            .setPositiveButton(R.string.action_remove) { _, _ -> onPositive(dialogCheckboxView.isChecked()) }
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     private fun onPositive(checked: Boolean) {

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/ConfirmDeleteChaptersDialog.kt

@@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.recent.updates
 
 import android.app.Dialog
 import android.os.Bundle
-import com.afollestad.materialdialogs.MaterialDialog
 import com.bluelinelabs.conductor.Controller
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 
@@ -18,12 +18,13 @@ class ConfirmDeleteChaptersDialog<T>(bundle: Bundle? = null) : DialogController(
     }
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-        return MaterialDialog(activity!!)
-            .message(R.string.confirm_delete_chapters)
-            .positiveButton(android.R.string.ok) {
+        return MaterialAlertDialogBuilder(activity!!)
+            .setMessage(R.string.confirm_delete_chapters)
+            .setPositiveButton(android.R.string.ok) { _, _ ->
                 (targetController as? Listener)?.deleteChapters(chaptersToDelete)
             }
-            .negativeButton(android.R.string.cancel)
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt

@@ -8,7 +8,7 @@ import android.os.Bundle
 import android.provider.Settings
 import androidx.core.net.toUri
 import androidx.preference.PreferenceScreen
-import com.afollestad.materialdialogs.MaterialDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.BuildConfig
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.cache.ChapterCache
@@ -184,12 +184,13 @@ class SettingsAdvancedController : SettingsController() {
 
     class ClearDatabaseDialogController : DialogController() {
         override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-            return MaterialDialog(activity!!)
-                .message(R.string.clear_database_confirmation)
-                .positiveButton(android.R.string.ok) {
+            return MaterialAlertDialogBuilder(activity!!)
+                .setMessage(R.string.clear_database_confirmation)
+                .setPositiveButton(android.R.string.ok) { _, _ ->
                     (targetController as? SettingsAdvancedController)?.clearDatabase()
                 }
-                .negativeButton(android.R.string.cancel)
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
         }
     }
 

+ 34 - 27
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt

@@ -9,12 +9,12 @@ import android.net.Uri
 import android.os.Bundle
 import android.view.View
 import android.widget.Toast
+import androidx.appcompat.app.AlertDialog
 import androidx.core.net.toUri
 import androidx.core.os.bundleOf
 import androidx.documentfile.provider.DocumentFile
 import androidx.preference.PreferenceScreen
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.listItemsMultiChoice
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.hippo.unifile.UniFile
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.backup.BackupConst
@@ -246,29 +246,34 @@ class SettingsBackupController : SettingsController() {
                 R.string.history
             )
                 .map { activity.getString(it) }
-
-            return MaterialDialog(activity)
-                .title(R.string.pref_create_backup)
-                .message(R.string.backup_choice)
-                .listItemsMultiChoice(
-                    items = options,
-                    disabledIndices = intArrayOf(0),
-                    initialSelection = intArrayOf(0, 1, 2, 3, 4)
-                ) { _, positions, _ ->
+            val selected = options.map { true }.toBooleanArray()
+
+            return MaterialAlertDialogBuilder(activity)
+                .setTitle(R.string.backup_choice)
+                .setMultiChoiceItems(options.toTypedArray(), selected) { dialog, which, checked ->
+                    if (which == 0) {
+                        (dialog as AlertDialog).listView.setItemChecked(which, true)
+                    } else {
+                        selected[which] = checked
+                    }
+                }
+                .setPositiveButton(R.string.action_create) { _, _ ->
                     var flags = 0
-                    for (i in 1 until positions.size) {
-                        when (positions[i]) {
-                            1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
-                            2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
-                            3 -> flags = flags or BackupCreateService.BACKUP_TRACK
-                            4 -> flags = flags or BackupCreateService.BACKUP_HISTORY
+                    selected.forEachIndexed { i, checked ->
+                        if (checked) {
+                            when (i) {
+                                1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
+                                2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
+                                3 -> flags = flags or BackupCreateService.BACKUP_TRACK
+                                4 -> flags = flags or BackupCreateService.BACKUP_HISTORY
+                            }
                         }
                     }
 
                     (targetController as? SettingsBackupController)?.createBackup(flags)
                 }
-                .positiveButton(R.string.action_create)
-                .negativeButton(android.R.string.cancel)
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
         }
     }
 
@@ -306,17 +311,19 @@ class SettingsBackupController : SettingsController() {
                     message += "\n\n${activity.getString(R.string.backup_restore_missing_trackers)}\n${results.missingTrackers.joinToString("\n") { "- $it" }}"
                 }
 
-                MaterialDialog(activity)
-                    .title(R.string.pref_restore_backup)
-                    .message(text = message)
-                    .positiveButton(R.string.action_restore) {
+                MaterialAlertDialogBuilder(activity)
+                    .setTitle(R.string.pref_restore_backup)
+                    .setMessage(message)
+                    .setPositiveButton(R.string.action_restore) { _, _ ->
                         BackupRestoreService.start(activity, uri, type)
                     }
+                    .create()
             } catch (e: Exception) {
-                MaterialDialog(activity)
-                    .title(R.string.invalid_backup_file)
-                    .message(text = e.message)
-                    .positiveButton(android.R.string.cancel)
+                MaterialAlertDialogBuilder(activity)
+                    .setTitle(R.string.invalid_backup_file)
+                    .setMessage(e.message)
+                    .setPositiveButton(android.R.string.cancel, null)
+                    .create()
             }
         }
 

+ 31 - 27
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt

@@ -10,8 +10,7 @@ import androidx.core.content.ContextCompat
 import androidx.core.net.toUri
 import androidx.core.text.buildSpannedString
 import androidx.preference.PreferenceScreen
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.listItemsSingleChoice
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.hippo.unifile.UniFile
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -28,8 +27,8 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
 import eu.kanade.tachiyomi.util.preference.switchPreference
 import eu.kanade.tachiyomi.util.preference.titleRes
 import eu.kanade.tachiyomi.util.system.toast
-import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateCheckBox
-import eu.kanade.tachiyomi.widget.materialdialogs.listItemsQuadStateMultiChoice
+import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
+import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import uy.kohesive.injekt.Injekt
@@ -194,20 +193,22 @@ class SettingsDownloadController : SettingsController() {
             val activity = activity!!
             val currentDir = preferences.downloadsDirectory().get()
             val externalDirs = (getExternalDirs() + File(activity.getString(R.string.custom_dir))).map(File::toString)
-            val selectedIndex = externalDirs.indexOfFirst { it in currentDir }
+            var selectedIndex = externalDirs.indexOfFirst { it in currentDir }
 
-            return MaterialDialog(activity)
-                .listItemsSingleChoice(
-                    items = externalDirs,
-                    initialSelection = selectedIndex
-                ) { _, position, text ->
+            return MaterialAlertDialogBuilder(activity)
+                .setTitle(R.string.pref_download_directory)
+                .setSingleChoiceItems(externalDirs.toTypedArray(), selectedIndex) { _, which ->
+                    selectedIndex = which
+                }
+                .setPositiveButton(android.R.string.ok) { _, _ ->
                     val target = targetController as? SettingsDownloadController
-                    if (position == externalDirs.lastIndex) {
+                    if (selectedIndex == externalDirs.lastIndex) {
                         target?.customDirectorySelected()
                     } else {
-                        target?.predefinedDirectorySelected(text.toString())
+                        target?.predefinedDirectorySelected(externalDirs[selectedIndex])
                     }
                 }
+                .create()
         }
 
         private fun getExternalDirs(): List<File> {
@@ -230,30 +231,33 @@ class SettingsDownloadController : SettingsController() {
             val categories = listOf(Category.createDefault()) + dbCategories
 
             val items = categories.map { it.name }
-            val preselected = categories
+            var selected = categories
                 .map {
                     when (it.id.toString()) {
-                        in preferences.downloadNewCategories().get() -> QuadStateCheckBox.State.CHECKED.ordinal
-                        in preferences.downloadNewCategoriesExclude().get() -> QuadStateCheckBox.State.INVERSED.ordinal
-                        else -> QuadStateCheckBox.State.UNCHECKED.ordinal
+                        in preferences.downloadNewCategories().get() -> QuadStateTextView.State.CHECKED.ordinal
+                        in preferences.downloadNewCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal
+                        else -> QuadStateTextView.State.UNCHECKED.ordinal
                     }
                 }
                 .toIntArray()
 
-            return MaterialDialog(activity!!)
-                .title(R.string.categories)
-                .message(R.string.pref_download_new_categories_details)
-                .listItemsQuadStateMultiChoice(
+            return MaterialAlertDialogBuilder(activity!!)
+                .setTitle(R.string.categories)
+                .setMessage(R.string.pref_download_new_categories_details)
+                .setQuadStateMultiChoiceItems(
                     items = items,
-                    initialSelected = preselected
+                    initialSelected = selected
                 ) { selections ->
-                    val included = selections
-                        .mapIndexed { index, value -> if (value == QuadStateCheckBox.State.CHECKED.ordinal) index else null }
+                    selected = selections
+                }
+                .setPositiveButton(android.R.string.ok) { _, _ ->
+                    val included = selected
+                        .mapIndexed { index, value -> if (value == QuadStateTextView.State.CHECKED.ordinal) index else null }
                         .filterNotNull()
                         .map { categories[it].id.toString() }
                         .toSet()
-                    val excluded = selections
-                        .mapIndexed { index, value -> if (value == QuadStateCheckBox.State.INVERSED.ordinal) index else null }
+                    val excluded = selected
+                        .mapIndexed { index, value -> if (value == QuadStateTextView.State.INVERSED.ordinal) index else null }
                         .filterNotNull()
                         .map { categories[it].id.toString() }
                         .toSet()
@@ -261,8 +265,8 @@ class SettingsDownloadController : SettingsController() {
                     preferences.downloadNewCategories().set(included)
                     preferences.downloadNewCategoriesExclude().set(excluded)
                 }
-                .positiveButton(android.R.string.ok)
-                .negativeButton(android.R.string.cancel)
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
         }
     }
 

+ 33 - 34
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt

@@ -2,12 +2,11 @@ package eu.kanade.tachiyomi.ui.setting
 
 import android.app.Dialog
 import android.os.Bundle
-import android.view.View
+import android.view.LayoutInflater
 import androidx.core.content.ContextCompat
 import androidx.core.text.buildSpannedString
 import androidx.preference.PreferenceScreen
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.customview.customView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 import eu.kanade.tachiyomi.data.database.models.Category
@@ -17,6 +16,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.preference.UNMETERED_NETWORK
 import eu.kanade.tachiyomi.data.preference.asImmediateFlow
 import eu.kanade.tachiyomi.data.track.TrackManager
+import eu.kanade.tachiyomi.databinding.PrefLibraryColumnsBinding
 import eu.kanade.tachiyomi.ui.base.controller.DialogController
 import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
 import eu.kanade.tachiyomi.ui.category.CategoryController
@@ -32,9 +32,8 @@ import eu.kanade.tachiyomi.util.preference.summaryRes
 import eu.kanade.tachiyomi.util.preference.switchPreference
 import eu.kanade.tachiyomi.util.preference.titleRes
 import eu.kanade.tachiyomi.util.system.isTablet
-import eu.kanade.tachiyomi.widget.MinMaxNumberPicker
-import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateCheckBox
-import eu.kanade.tachiyomi.widget.materialdialogs.listItemsQuadStateMultiChoice
+import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
+import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -299,21 +298,21 @@ class SettingsLibraryController : SettingsController() {
         private var landscape = preferences.landscapeColumns().get()
 
         override fun onCreateDialog(savedViewState: Bundle?): Dialog {
-            val dialog = MaterialDialog(activity!!)
-                .title(R.string.pref_library_columns)
-                .customView(R.layout.pref_library_columns, horizontalPadding = true)
-                .positiveButton(android.R.string.ok) {
+            val binding = PrefLibraryColumnsBinding.inflate(LayoutInflater.from(activity!!))
+            onViewCreated(binding)
+            return MaterialAlertDialogBuilder(activity!!)
+                .setTitle(R.string.pref_library_columns)
+                .setView(binding.root)
+                .setPositiveButton(android.R.string.ok) { _, _ ->
                     preferences.portraitColumns().set(portrait)
                     preferences.landscapeColumns().set(landscape)
                 }
-                .negativeButton(android.R.string.cancel)
-
-            onViewCreated(dialog.view)
-            return dialog
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
         }
 
-        fun onViewCreated(view: View) {
-            with(view.findViewById(R.id.portrait_columns) as MinMaxNumberPicker) {
+        fun onViewCreated(binding: PrefLibraryColumnsBinding) {
+            with(binding.portraitColumns) {
                 displayedValues = arrayOf(context.getString(R.string.default_columns)) +
                     IntRange(1, 10).map(Int::toString)
                 value = portrait
@@ -322,7 +321,7 @@ class SettingsLibraryController : SettingsController() {
                     portrait = newValue
                 }
             }
-            with(view.findViewById(R.id.landscape_columns) as MinMaxNumberPicker) {
+            with(binding.landscapeColumns) {
                 displayedValues = arrayOf(context.getString(R.string.default_columns)) +
                     IntRange(1, 10).map(Int::toString)
                 value = landscape
@@ -344,30 +343,30 @@ class SettingsLibraryController : SettingsController() {
             val categories = listOf(Category.createDefault()) + dbCategories
 
             val items = categories.map { it.name }
-            val preselected = categories
+            var selected = categories
                 .map {
                     when (it.id.toString()) {
-                        in preferences.libraryUpdateCategories().get() -> QuadStateCheckBox.State.CHECKED.ordinal
-                        in preferences.libraryUpdateCategoriesExclude().get() -> QuadStateCheckBox.State.INVERSED.ordinal
-                        else -> QuadStateCheckBox.State.UNCHECKED.ordinal
+                        in preferences.libraryUpdateCategories().get() -> QuadStateTextView.State.CHECKED.ordinal
+                        in preferences.libraryUpdateCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal
+                        else -> QuadStateTextView.State.UNCHECKED.ordinal
                     }
                 }
                 .toIntArray()
 
-            return MaterialDialog(activity!!)
-                .title(R.string.categories)
-                .message(R.string.pref_library_update_categories_details)
-                .listItemsQuadStateMultiChoice(
-                    items = items,
-                    initialSelected = preselected
-                ) { selections ->
-                    val included = selections
-                        .mapIndexed { index, value -> if (value == QuadStateCheckBox.State.CHECKED.ordinal) index else null }
+            return MaterialAlertDialogBuilder(activity!!)
+                .setTitle(R.string.categories)
+                .setMessage(R.string.pref_library_update_categories_details)
+                .setQuadStateMultiChoiceItems(items = items, initialSelected = selected) { selections ->
+                    selected = selections
+                }
+                .setPositiveButton(android.R.string.ok) { _, _ ->
+                    val included = selected
+                        .mapIndexed { index, value -> if (value == QuadStateTextView.State.CHECKED.ordinal) index else null }
                         .filterNotNull()
                         .map { categories[it].id.toString() }
                         .toSet()
-                    val excluded = selections
-                        .mapIndexed { index, value -> if (value == QuadStateCheckBox.State.INVERSED.ordinal) index else null }
+                    val excluded = selected
+                        .mapIndexed { index, value -> if (value == QuadStateTextView.State.INVERSED.ordinal) index else null }
                         .filterNotNull()
                         .map { categories[it].id.toString() }
                         .toSet()
@@ -375,8 +374,8 @@ class SettingsLibraryController : SettingsController() {
                     preferences.libraryUpdateCategories().set(included)
                     preferences.libraryUpdateCategoriesExclude().set(excluded)
                 }
-                .positiveButton(android.R.string.ok)
-                .negativeButton(android.R.string.cancel)
+                .setNegativeButton(android.R.string.cancel, null)
+                .create()
         }
     }
 }

+ 6 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/TrackLogoutDialog.kt

@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.setting.track
 import android.app.Dialog
 import android.os.Bundle
 import androidx.core.os.bundleOf
-import com.afollestad.materialdialogs.MaterialDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.TrackService
@@ -20,14 +20,15 @@ class TrackLogoutDialog(bundle: Bundle? = null) : DialogController(bundle) {
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
         val serviceName = activity!!.getString(service.nameRes())
-        return MaterialDialog(activity!!)
-            .title(text = activity!!.getString(R.string.logout_title, serviceName))
-            .positiveButton(R.string.logout) {
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(activity!!.getString(R.string.logout_title, serviceName))
+            .setPositiveButton(R.string.logout) { _, _ ->
                 service.logout()
                 (targetController as? Listener)?.trackLogoutDialogClosed(service)
                 activity?.toast(R.string.logout_success)
             }
-            .negativeButton(android.R.string.cancel)
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     interface Listener {

+ 26 - 0
app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.util.lang
 import java.text.DateFormat
 import java.util.Calendar
 import java.util.Date
+import java.util.TimeZone
 
 fun Date.toDateTimestampString(dateFormatter: DateFormat): String {
     val date = dateFormatter.format(this)
@@ -43,3 +44,28 @@ fun Long.toCalendar(): Calendar? {
     cal.timeInMillis = this
     return cal
 }
+
+/**
+ * Convert epoch long to Calendar instance in UTC
+ *
+ * @return UTC Calendar instance at supplied epoch time. Null if epoch was 0.
+ */
+fun Long.toUtcCalendar(): Calendar? {
+    if (this == 0L) {
+        return null
+    }
+    val rawCalendar = Calendar.getInstance().apply {
+        timeInMillis = this@toUtcCalendar
+    }
+    return Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
+        clear()
+        set(
+            rawCalendar.get(Calendar.YEAR),
+            rawCalendar.get(Calendar.MONTH),
+            rawCalendar.get(Calendar.DAY_OF_MONTH),
+            rawCalendar.get(Calendar.HOUR_OF_DAY),
+            rawCalendar.get(Calendar.MINUTE),
+            rawCalendar.get(Calendar.SECOND)
+        )
+    }
+}

+ 54 - 0
app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialAlertDialogBuilderExtensions.kt

@@ -0,0 +1,54 @@
+package eu.kanade.tachiyomi.widget.materialdialogs
+
+import android.view.LayoutInflater
+import android.view.inputmethod.InputMethodManager
+import android.widget.TextView
+import androidx.core.content.getSystemService
+import androidx.core.widget.doAfterTextChanged
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import eu.kanade.tachiyomi.databinding.DialogStubQuadstatemultichoiceBinding
+import eu.kanade.tachiyomi.databinding.DialogStubTextinputBinding
+
+fun MaterialAlertDialogBuilder.setTextInput(
+    hint: String? = null,
+    prefill: String? = null,
+    onTextChanged: (String) -> Unit
+): MaterialAlertDialogBuilder {
+    val binding = DialogStubTextinputBinding.inflate(LayoutInflater.from(context))
+    binding.textField.hint = hint
+    binding.textField.editText?.apply {
+        setText(prefill, TextView.BufferType.EDITABLE)
+        doAfterTextChanged {
+            onTextChanged(it?.toString() ?: "")
+        }
+        post {
+            requestFocusFromTouch()
+            context.getSystemService<InputMethodManager>()?.showSoftInput(this, 0)
+        }
+    }
+    return setView(binding.root)
+}
+
+/**
+ * Sets a list of items with checkboxes that supports 4 states.
+ *
+ * @see eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
+ */
+fun MaterialAlertDialogBuilder.setQuadStateMultiChoiceItems(
+    items: List<CharSequence>,
+    initialSelected: IntArray,
+    disabledIndices: IntArray? = null,
+    selection: QuadStateMultiChoiceListener
+): MaterialAlertDialogBuilder {
+    val binding = DialogStubQuadstatemultichoiceBinding.inflate(LayoutInflater.from(context))
+    binding.list.layoutManager = LinearLayoutManager(context)
+    binding.list.adapter = QuadStateMultiChoiceDialogAdapter(
+        items = items,
+        disabledItems = disabledIndices,
+        initialSelected = initialSelected,
+        listener = selection
+    )
+    setView(binding.root)
+    return this
+}

+ 0 - 26
app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialDialogMultiChoiceExt.kt

@@ -1,26 +0,0 @@
-package eu.kanade.tachiyomi.widget.materialdialogs
-
-import androidx.annotation.CheckResult
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.list.customListAdapter
-
-/**
- * A variant of listItemsMultiChoice that allows for checkboxes that supports 4 states instead.
- */
-@CheckResult
-fun MaterialDialog.listItemsQuadStateMultiChoice(
-    items: List<CharSequence>,
-    disabledIndices: IntArray? = null,
-    initialSelected: IntArray = IntArray(items.size),
-    selection: QuadStateMultiChoiceListener
-): MaterialDialog {
-    return customListAdapter(
-        QuadStateMultiChoiceDialogAdapter(
-            dialog = this,
-            items = items,
-            disabledItems = disabledIndices,
-            initialSelected = initialSelected,
-            selection = selection
-        )
-    )
-}

+ 0 - 34
app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt

@@ -1,34 +0,0 @@
-package eu.kanade.tachiyomi.widget.materialdialogs
-
-import android.content.Context
-import android.util.AttributeSet
-import androidx.appcompat.widget.AppCompatImageView
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.util.view.setVectorCompat
-
-class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
-    AppCompatImageView(context, attrs) {
-
-    var state: State = State.UNCHECKED
-        set(value) {
-            field = value
-            updateDrawable()
-        }
-
-    private fun updateDrawable() {
-        when (state) {
-            State.UNCHECKED -> setVectorCompat(R.drawable.ic_check_box_outline_blank_24dp, R.attr.colorControlNormal)
-            State.INDETERMINATE -> setVectorCompat(R.drawable.ic_indeterminate_check_box_24dp, R.attr.colorAccent)
-            State.CHECKED -> setVectorCompat(R.drawable.ic_check_box_24dp, R.attr.colorAccent)
-            State.INVERSED -> setVectorCompat(R.drawable.ic_check_box_x_24dp, R.attr.colorAccent)
-        }
-    }
-
-    enum class State {
-        UNCHECKED,
-        INDETERMINATE,
-        CHECKED,
-        INVERSED,
-        ;
-    }
-}

+ 19 - 104
app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceDialogAdapter.kt

@@ -1,14 +1,9 @@
 package eu.kanade.tachiyomi.widget.materialdialogs
 
-import android.view.View
+import android.view.LayoutInflater
 import android.view.ViewGroup
 import androidx.recyclerview.widget.RecyclerView
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.internal.list.DialogAdapter
-import com.afollestad.materialdialogs.list.getItemSelector
-import com.afollestad.materialdialogs.utils.MDUtil.inflate
-import com.afollestad.materialdialogs.utils.MDUtil.maybeSetTextColor
-import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.databinding.DialogQuadstatemultichoiceItemBinding
 
 private object CheckPayload
 private object InverseCheckPayload
@@ -17,15 +12,13 @@ private object UncheckPayload
 typealias QuadStateMultiChoiceListener = (indices: IntArray) -> Unit
 
 internal class QuadStateMultiChoiceDialogAdapter(
-    private var dialog: MaterialDialog,
     internal var items: List<CharSequence>,
     disabledItems: IntArray?,
     initialSelected: IntArray,
-    internal var selection: QuadStateMultiChoiceListener
-) : RecyclerView.Adapter<QuadStateMultiChoiceViewHolder>(),
-    DialogAdapter<CharSequence, QuadStateMultiChoiceListener> {
+    internal var listener: QuadStateMultiChoiceListener
+) : RecyclerView.Adapter<QuadStateMultiChoiceViewHolder>() {
 
-    private val states = QuadStateCheckBox.State.values()
+    private val states = QuadStateTextView.State.values()
 
     private var currentSelection: IntArray = initialSelected
         set(value) {
@@ -34,15 +27,15 @@ internal class QuadStateMultiChoiceDialogAdapter(
             previousSelection.forEachIndexed { index, previous ->
                 val current = value[index]
                 when {
-                    current == QuadStateCheckBox.State.CHECKED.ordinal && previous != QuadStateCheckBox.State.CHECKED.ordinal -> {
+                    current == QuadStateTextView.State.CHECKED.ordinal && previous != QuadStateTextView.State.CHECKED.ordinal -> {
                         // This value was selected
                         notifyItemChanged(index, CheckPayload)
                     }
-                    current == QuadStateCheckBox.State.INVERSED.ordinal && previous != QuadStateCheckBox.State.INVERSED.ordinal -> {
+                    current == QuadStateTextView.State.INVERSED.ordinal && previous != QuadStateTextView.State.INVERSED.ordinal -> {
                         // This value was inverse selected
                         notifyItemChanged(index, InverseCheckPayload)
                     }
-                    current == QuadStateCheckBox.State.UNCHECKED.ordinal && previous != QuadStateCheckBox.State.UNCHECKED.ordinal -> {
+                    current == QuadStateTextView.State.UNCHECKED.ordinal && previous != QuadStateTextView.State.UNCHECKED.ordinal -> {
                         // This value was unselected
                         notifyItemChanged(index, UncheckPayload)
                     }
@@ -54,26 +47,24 @@ internal class QuadStateMultiChoiceDialogAdapter(
     internal fun itemClicked(index: Int) {
         val newSelection = this.currentSelection.toMutableList()
         newSelection[index] = when (currentSelection[index]) {
-            QuadStateCheckBox.State.CHECKED.ordinal -> QuadStateCheckBox.State.INVERSED.ordinal
-            QuadStateCheckBox.State.INVERSED.ordinal -> QuadStateCheckBox.State.UNCHECKED.ordinal
+            QuadStateTextView.State.CHECKED.ordinal -> QuadStateTextView.State.INVERSED.ordinal
+            QuadStateTextView.State.INVERSED.ordinal -> QuadStateTextView.State.UNCHECKED.ordinal
             // INDETERMINATE or UNCHECKED
-            else -> QuadStateCheckBox.State.CHECKED.ordinal
+            else -> QuadStateTextView.State.CHECKED.ordinal
         }
         this.currentSelection = newSelection.toIntArray()
+        listener(currentSelection)
     }
 
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
     ): QuadStateMultiChoiceViewHolder {
-        val listItemView: View = parent.inflate(dialog.windowContext, R.layout.md_listitem_quadstatemultichoice)
-        val viewHolder = QuadStateMultiChoiceViewHolder(
-            itemView = listItemView,
+        return QuadStateMultiChoiceViewHolder(
+            itemBinding = DialogQuadstatemultichoiceItemBinding
+                .inflate(LayoutInflater.from(parent.context), parent, false),
             adapter = this
         )
-        viewHolder.titleView.maybeSetTextColor(dialog.windowContext, R.attr.md_color_content)
-
-        return viewHolder
     }
 
     override fun getItemCount() = items.size
@@ -83,14 +74,8 @@ internal class QuadStateMultiChoiceDialogAdapter(
         position: Int
     ) {
         holder.isEnabled = !disabledIndices.contains(position)
-
         holder.controlView.state = states[currentSelection[position]]
-        holder.titleView.text = items[position]
-        holder.itemView.background = dialog.getItemSelector()
-
-        if (dialog.bodyFont != null) {
-            holder.titleView.typeface = dialog.bodyFont
-        }
+        holder.controlView.text = items[position]
     }
 
     override fun onBindViewHolder(
@@ -100,88 +85,18 @@ internal class QuadStateMultiChoiceDialogAdapter(
     ) {
         when (payloads.firstOrNull()) {
             CheckPayload -> {
-                holder.controlView.state = QuadStateCheckBox.State.CHECKED
+                holder.controlView.state = QuadStateTextView.State.CHECKED
                 return
             }
             InverseCheckPayload -> {
-                holder.controlView.state = QuadStateCheckBox.State.INVERSED
+                holder.controlView.state = QuadStateTextView.State.INVERSED
                 return
             }
             UncheckPayload -> {
-                holder.controlView.state = QuadStateCheckBox.State.UNCHECKED
+                holder.controlView.state = QuadStateTextView.State.UNCHECKED
                 return
             }
         }
         super.onBindViewHolder(holder, position, payloads)
     }
-
-    override fun positiveButtonClicked() {
-        selection.invoke(currentSelection)
-    }
-
-    override fun replaceItems(
-        items: List<CharSequence>,
-        listener: QuadStateMultiChoiceListener?
-    ) {
-        this.items = items
-        if (listener != null) {
-            this.selection = listener
-        }
-        this.notifyDataSetChanged()
-    }
-
-    override fun disableItems(indices: IntArray) {
-        this.disabledIndices = indices
-        notifyDataSetChanged()
-    }
-
-    override fun checkItems(indices: IntArray) {
-        val newSelection = this.currentSelection.toMutableList()
-        for (index in indices) {
-            newSelection[index] = QuadStateCheckBox.State.CHECKED.ordinal
-        }
-        this.currentSelection = newSelection.toIntArray()
-    }
-
-    override fun uncheckItems(indices: IntArray) {
-        val newSelection = this.currentSelection.toMutableList()
-        for (index in indices) {
-            newSelection[index] = QuadStateCheckBox.State.UNCHECKED.ordinal
-        }
-        this.currentSelection = newSelection.toIntArray()
-    }
-
-    override fun toggleItems(indices: IntArray) {
-        val newSelection = this.currentSelection.toMutableList()
-        for (index in indices) {
-            if (this.disabledIndices.contains(index)) {
-                continue
-            }
-
-            if (this.currentSelection[index] != QuadStateCheckBox.State.CHECKED.ordinal) {
-                newSelection[index] = QuadStateCheckBox.State.CHECKED.ordinal
-            } else {
-                newSelection[index] = QuadStateCheckBox.State.UNCHECKED.ordinal
-            }
-        }
-        this.currentSelection = newSelection.toIntArray()
-    }
-
-    override fun checkAllItems() {
-        this.currentSelection = IntArray(itemCount) { QuadStateCheckBox.State.CHECKED.ordinal }
-    }
-
-    override fun uncheckAllItems() {
-        this.currentSelection = IntArray(itemCount) { QuadStateCheckBox.State.UNCHECKED.ordinal }
-    }
-
-    override fun toggleAllChecked() {
-        if (this.currentSelection.any { it != QuadStateCheckBox.State.CHECKED.ordinal }) {
-            checkAllItems()
-        } else {
-            uncheckAllItems()
-        }
-    }
-
-    override fun isItemChecked(index: Int) = this.currentSelection[index] == QuadStateCheckBox.State.CHECKED.ordinal
 }

+ 4 - 7
app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateMultiChoiceViewHolder.kt

@@ -1,27 +1,24 @@
 package eu.kanade.tachiyomi.widget.materialdialogs
 
 import android.view.View
-import android.widget.TextView
 import androidx.recyclerview.widget.RecyclerView
-import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.databinding.DialogQuadstatemultichoiceItemBinding
 
 internal class QuadStateMultiChoiceViewHolder(
-    itemView: View,
+    itemBinding: DialogQuadstatemultichoiceItemBinding,
     private val adapter: QuadStateMultiChoiceDialogAdapter
-) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
+) : RecyclerView.ViewHolder(itemBinding.root), View.OnClickListener {
     init {
         itemView.setOnClickListener(this)
     }
 
-    val controlView: QuadStateCheckBox = itemView.findViewById(R.id.md_quad_state_control)
-    val titleView: TextView = itemView.findViewById(R.id.md_quad_state_title)
+    val controlView = itemBinding.quadStateControl
 
     var isEnabled: Boolean
         get() = itemView.isEnabled
         set(value) {
             itemView.isEnabled = value
             controlView.isEnabled = value
-            titleView.isEnabled = value
         }
 
     override fun onClick(view: View) = adapter.itemClicked(bindingAdapterPosition)

+ 45 - 0
app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateTextView.kt

@@ -0,0 +1,45 @@
+package eu.kanade.tachiyomi.widget.materialdialogs
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatTextView
+import com.mikepenz.aboutlibraries.util.getThemeColor
+import eu.kanade.tachiyomi.R
+
+class QuadStateTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    AppCompatTextView(context, attrs) {
+
+    var state: State = State.UNCHECKED
+        set(value) {
+            field = value
+            updateDrawable()
+        }
+
+    private fun updateDrawable() {
+        val drawableStartId = when (state) {
+            State.UNCHECKED -> R.drawable.ic_check_box_outline_blank_24dp
+            State.INDETERMINATE -> R.drawable.ic_indeterminate_check_box_24dp
+            State.CHECKED -> R.drawable.ic_check_box_24dp
+            State.INVERSED -> R.drawable.ic_check_box_x_24dp
+        }
+        setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStartId, 0, 0, 0)
+
+        val tint = if (state == State.UNCHECKED) {
+            context.getThemeColor(R.attr.colorControlNormal)
+        } else {
+            context.getThemeColor(R.attr.colorAccent)
+        }
+        if (tint != 0) {
+            compoundDrawableTintList = ColorStateList.valueOf(tint)
+        }
+    }
+
+    enum class State {
+        UNCHECKED,
+        INDETERMINATE,
+        CHECKED,
+        INVERSED,
+        ;
+    }
+}

+ 7 - 10
app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt

@@ -5,11 +5,10 @@ import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import androidx.annotation.StringRes
-import com.afollestad.materialdialogs.MaterialDialog
-import com.afollestad.materialdialogs.customview.customView
 import com.bluelinelabs.conductor.ControllerChangeHandler
 import com.bluelinelabs.conductor.ControllerChangeType
 import com.dd.processbutton.iml.ActionProcessButton
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.databinding.PrefAccountLoginBinding
@@ -28,15 +27,13 @@ abstract class LoginDialogPreference(
 
     override fun onCreateDialog(savedViewState: Bundle?): Dialog {
         binding = PrefAccountLoginBinding.inflate(LayoutInflater.from(activity!!))
+        onViewCreated(binding!!.root)
         val titleName = activity!!.getString(getTitleName())
-        val dialog = MaterialDialog(activity!!)
-            .title(text = activity!!.getString(R.string.login_title, titleName))
-            .customView(view = binding!!.root)
-            .negativeButton(android.R.string.cancel)
-
-        onViewCreated(dialog.view)
-
-        return dialog
+        return MaterialAlertDialogBuilder(activity!!)
+            .setTitle(activity!!.getString(R.string.login_title, titleName))
+            .setView(binding!!.root)
+            .setNegativeButton(android.R.string.cancel, null)
+            .create()
     }
 
     fun onViewCreated(view: View) {

+ 10 - 0
app/src/main/res/drawable/ic_more_vert_24.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>

+ 4 - 2
app/src/main/res/layout/common_dialog_with_checkbox.xml

@@ -2,7 +2,9 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:paddingHorizontal="24dp"
+    android:paddingTop="16dp">
 
     <TextView
         android:id="@+id/description"
@@ -15,7 +17,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginStart="-5dp"
-        android:layout_marginTop="24dp"
+        android:layout_marginTop="16dp"
         android:layout_marginEnd="0dp" />
 
 </LinearLayout>

+ 18 - 0
app/src/main/res/layout/dialog_quadstatemultichoice_item.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    style="?attr/materialAlertDialogBodyTextStyle"
+    android:id="@+id/quad_state_control"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/selectableItemBackground"
+    android:minHeight="?attr/listPreferredItemHeightSmall"
+    android:gravity="start|center_vertical"
+    android:textAlignment="viewStart"
+    android:paddingStart="@dimen/abc_select_dialog_padding_start_material"
+    android:paddingEnd="?attr/dialogPreferredPadding"
+    android:drawablePadding="20dp"
+    android:ellipsize="marquee"
+    app:drawableStartCompat="@drawable/ic_check_box_outline_blank_24dp"
+    tools:text="Quad-state item" />

+ 9 - 0
app/src/main/res/layout/dialog_stub_quadstatemultichoice.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/list"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="8dp"
+    android:scrollIndicators="none"
+    tools:listitem="@layout/dialog_quadstatemultichoice_item" />

+ 20 - 0
app/src/main/res/layout/dialog_stub_textinput.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingHorizontal="24dp"
+    android:paddingVertical="16dp">
+
+    <com.google.android.material.textfield.TextInputLayout
+        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+        android:id="@+id/text_field"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </com.google.android.material.textfield.TextInputLayout>
+
+</FrameLayout>

+ 2 - 1
app/src/main/res/layout/download_custom_amount.xml

@@ -4,7 +4,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:gravity="center"
-    android:orientation="horizontal">
+    android:orientation="horizontal"
+    android:paddingVertical="8dp">
 
     <androidx.appcompat.widget.AppCompatImageButton
         android:id="@+id/btn_decrease_10"

+ 0 - 15
app/src/main/res/layout/md_listitem_quadstatemultichoice.xml

@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    style="@style/MD_ListItem.Choice">
-
-    <eu.kanade.tachiyomi.widget.materialdialogs.QuadStateCheckBox
-        android:id="@+id/md_quad_state_control"
-        style="@style/MD_ListItem_Control" />
-
-    <com.afollestad.materialdialogs.internal.rtl.RtlTextView
-        android:id="@+id/md_quad_state_title"
-        style="@style/MD_ListItemText.Choice"
-        tools:text="Item" />
-
-</LinearLayout>

+ 27 - 19
app/src/main/res/layout/track_item.xml

@@ -20,9 +20,6 @@
             android:id="@+id/logo_container"
             android:layout_width="48dp"
             android:layout_height="48dp"
-            android:clickable="true"
-            android:focusable="true"
-            android:foreground="?attr/selectableItemBackground"
             app:cardBackgroundColor="#2E51A2"
             app:cardElevation="0dp"
             app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.MaterialCardView.Tracker">
@@ -54,8 +51,9 @@
 
             <TextView
                 android:id="@+id/track_title"
-                android:layout_width="match_parent"
+                android:layout_width="0dp"
                 android:layout_height="48dp"
+                android:layout_weight="1"
                 android:ellipsize="end"
                 android:foreground="?attr/selectableItemBackgroundBorderless"
                 android:gravity="center_vertical"
@@ -64,6 +62,16 @@
                 android:textAppearance="?attr/textAppearanceSubtitle1"
                 tools:text="Title" />
 
+            <ImageButton
+                android:id="@+id/more"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:background="?selectableItemBackgroundBorderless"
+                android:contentDescription="@string/label_more"
+                android:padding="8dp"
+                android:src="@drawable/ic_more_vert_24" />
+
         </LinearLayout>
 
     </LinearLayout>
@@ -100,11 +108,11 @@
                     android:textAppearance="?attr/textAppearanceBody2"
                     tools:text="Reading" />
 
-            <View
-                android:id="@+id/vert_divider_1"
-                android:layout_width="1dp"
-                android:layout_height="match_parent"
-                android:background="?android:divider" />
+                <View
+                    android:id="@+id/vert_divider_1"
+                    android:layout_width="1dp"
+                    android:layout_height="match_parent"
+                    android:background="?android:divider" />
 
                 <TextView
                     android:id="@+id/track_chapters"
@@ -119,11 +127,11 @@
                     android:textAppearance="?attr/textAppearanceBody2"
                     tools:text="12/24" />
 
-            <View
-                android:id="@+id/vert_divider_2"
-                android:layout_width="1dp"
-                android:layout_height="match_parent"
-                android:background="?android:divider"/>
+                <View
+                    android:id="@+id/vert_divider_2"
+                    android:layout_width="1dp"
+                    android:layout_height="match_parent"
+                    android:background="?android:divider" />
 
                 <TextView
                     android:id="@+id/track_score"
@@ -165,11 +173,11 @@
                     android:textAppearance="?attr/textAppearanceBody2"
                     tools:text="4/16/2020" />
 
-            <View
-                android:id="@+id/vert_divider_3"
-                android:layout_width="1dp"
-                android:layout_height="match_parent"
-                android:background="?android:divider" />
+                <View
+                    android:id="@+id/vert_divider_3"
+                    android:layout_width="1dp"
+                    android:layout_height="match_parent"
+                    android:background="?android:divider" />
 
                 <TextView
                     android:id="@+id/track_finish_date"

+ 15 - 0
app/src/main/res/menu/track_item.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/action_open_in_browser"
+        android:title="@string/action_open_in_browser"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/action_remove"
+        android:title="@string/action_remove"
+        app:showAsAction="never" />
+
+</menu>

+ 15 - 0
app/src/main/res/menu/track_item_date.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/action_edit"
+        android:title="@string/action_edit"
+        app:showAsAction="never" />
+
+    <item
+        android:id="@+id/action_remove"
+        android:title="@string/action_remove"
+        app:showAsAction="never" />
+
+</menu>

+ 0 - 8
app/src/main/res/menu/track_search.xml

@@ -2,14 +2,6 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto">
 
-    <item
-        android:id="@+id/remove"
-        android:icon="@drawable/ic_delete_24dp"
-        android:title="@string/action_remove"
-        android:visible="false"
-        app:iconTint="?attr/colorOnToolbar"
-        app:showAsAction="ifRoom" />
-
     <item
         android:id="@+id/done"
         android:enabled="false"

+ 0 - 3
app/src/main/res/values/dimens.xml

@@ -17,7 +17,4 @@
     <dimen name="screen_edge_margin">16dp</dimen>
 
     <dimen name="tablet_horizontal_cover_margin">128dp</dimen>
-
-    <!-- material-dialogs button radius -->
-    <dimen name="md_action_button_corner_radius">4dp</dimen>
 </resources>

+ 0 - 15
app/src/main/res/values/styles.xml

@@ -300,21 +300,6 @@
         <item name="android:textSize">15sp</item>
     </style>
 
-    <!--================================-->
-    <!--material-dialogs style overrides-->
-    <!--================================-->
-    <style name="MD_Light" parent="ThemeOverlay.MaterialComponents.Dialog.Alert">
-        <item name="md_divider_color">@color/md_divider_light_theme</item>
-        <item name="md_item_selector">@drawable/md_item_selector</item>
-        <item name="md_button_selector">@drawable/md_btn_selector</item>
-    </style>
-
-    <style name="MD_Dark" parent="ThemeOverlay.MaterialComponents.Dialog.Alert">
-        <item name="md_divider_color">@color/md_divider_dark_theme</item>
-        <item name="md_item_selector">@drawable/md_item_selector_dark</item>
-        <item name="md_button_selector">@drawable/md_btn_selector_dark</item>
-    </style>
-
     <!--================-->
     <!--Shape Appearance-->
     <!--================-->

+ 0 - 8
app/src/main/res/values/themes.xml

@@ -67,14 +67,6 @@
         <item name="elevationOverlayEnabled">false</item>
         <item name="lightSystemBarsOnPrimary">false</item>
 
-        <!-- Material Dialogs -->
-        <item name="md_background_color">?attr/colorSurface</item>
-        <item name="md_color_title">?attr/colorOnSurface</item>
-        <item name="md_color_content">?attr/colorOnSurface</item>
-        <item name="md_color_button_text">?attr/colorPrimary</item>
-        <item name="md_button_casing">literal</item>
-        <item name="md_corner_radius">@dimen/dialog_radius</item>
-
         <!-- Custom Attributes-->
         <item name="colorFilterActive">@color/filter_light</item>
     </style>