Skip to content

Commit

Permalink
refactor(app): replace dialogs with bottom sheets
Browse files Browse the repository at this point in the history
  • Loading branch information
ashutoshgngwr committed Apr 30, 2020
1 parent 850ca1e commit 26775f0
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 273 deletions.

This file was deleted.

21 changes: 9 additions & 12 deletions app/src/main/java/com/github/ashutoshgngwr/noice/MainActivity.kt
Expand Up @@ -13,11 +13,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import com.github.ashutoshgngwr.noice.fragment.AboutFragment
import com.github.ashutoshgngwr.noice.fragment.PresetFragment
import com.github.ashutoshgngwr.noice.fragment.SleepTimerFragment
import com.github.ashutoshgngwr.noice.fragment.SoundLibraryFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.github.ashutoshgngwr.noice.fragment.*
import com.google.android.material.navigation.NavigationView
import kotlinx.android.synthetic.main.activity_main.*

Expand Down Expand Up @@ -106,13 +102,14 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
setFragment(SleepTimerFragment::class.java)
}
R.id.app_theme -> {
MaterialAlertDialogBuilder(this)
.setSingleChoiceItems(R.array.app_themes, getAppTheme()) { dialog, which ->
dialog.dismiss()
setAppTheme(which)
}
.setTitle(R.string.app_theme)
.show()
DialogFragment().show(supportFragmentManager) {
title(R.string.app_theme)
singleChoiceItems(
itemsRes = R.array.app_themes,
currentChoice = getAppTheme(),
onItemSelected = { setAppTheme(it) }
)
}
}
R.id.about -> {
setFragment(AboutFragment::class.java)
Expand Down
@@ -0,0 +1,162 @@
package com.github.ashutoshgngwr.noice.fragment

import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ListView
import androidx.annotation.ArrayRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.FragmentManager
import com.github.ashutoshgngwr.noice.R
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.textview.MaterialTextView
import kotlinx.android.synthetic.main.fragment_dialog__base.view.*
import kotlinx.android.synthetic.main.fragment_dialog__text_input.view.*


/**
* A generic implementation with extensions for use-case specific designs.
* API inspired by https://github.com/afollestad/material-dialogs but not using it
* due to its reliance on old AppCompat API. I tried to make material-dialogs
* work but it was bringing appearance inconsistencies and was generally rigid in
* terms of styling.
*/
class DialogFragment : BottomSheetDialogFragment() {

private var displayOptions: DialogFragment.() -> Unit = { }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true // so this instance is retained when screen orientation changes.
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_dialog__base, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
displayOptions()
}

private fun addContentView(view: View) {
requireView().content.addView(view)
}

private fun setButton(@IdRes which: Int, @StringRes resId: Int, onClick: () -> Unit) {
val button = requireView().findViewById<Button>(which)
button.visibility = View.VISIBLE
button.text = getString(resId)
button.setOnClickListener {
onClick()
dismiss()
}
}

private fun @receiver:androidx.annotation.AttrRes Int.resolveAttributeValue(): Int {
val value = TypedValue()
requireNotNull(dialog).context.theme.resolveAttribute(this, value, true)
return value.data
}

fun title(@StringRes resId: Int) {
requireView().title.text = getString(resId)
}

fun positiveButton(@StringRes resId: Int, onClick: () -> Unit = { }) {
setButton(R.id.positive, resId, onClick)
}

fun negativeButton(@StringRes resId: Int, onClick: () -> Unit = { }) {
setButton(R.id.negative, resId, onClick)
}

fun show(fragmentManager: FragmentManager, options: DialogFragment.() -> Unit = { }) {
displayOptions = options
show(fragmentManager, javaClass.simpleName)
}

fun message(@StringRes resId: Int, vararg formatArgs: Any) {
addContentView(
MaterialTextView(requireContext()).apply {
val textAppearance = android.R.attr.textAppearance.resolveAttributeValue()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setTextAppearance(textAppearance)
} else {
@Suppress("DEPRECATION")
setTextAppearance(textAppearance)
}

text = getString(resId, *formatArgs)
}
)
}

fun input(
@StringRes hintRes: Int = 0,
preFillValue: CharSequence = "",
type: Int = InputType.TYPE_CLASS_TEXT,
singleLine: Boolean = true,
validator: (String) -> Boolean = { it.isNotBlank() },
@StringRes errorRes: Int = 0
) {
layoutInflater.inflate(R.layout.fragment_dialog__text_input, requireView().content, false)
.apply {
textInputLayout.hint = getString(hintRes)
editText.inputType = type
editText.isSingleLine = singleLine
editText.setText(preFillValue)
editText.addTextChangedListener {
val valid = validator(it.toString())
requireView().positive.isEnabled = valid
textInputLayout.error = if (valid) {
null
} else {
getString(errorRes)
}
}

addContentView(this)
}
}

fun getInputText(): String {
return requireView().editText.text.toString()
}

fun singleChoiceItems(
@ArrayRes itemsRes: Int,
currentChoice: Int = -1,
onItemSelected: (Int) -> Unit = { }
) {
val items = requireContext().resources.getStringArray(itemsRes)
require(currentChoice >= -1 && currentChoice < items.size)
addContentView(
ListView(requireContext(), null, android.R.attr.listViewStyle).apply {
id = android.R.id.list
dividerHeight = 0
choiceMode = ListView.CHOICE_MODE_SINGLE
adapter = ArrayAdapter(context, android.R.layout.simple_list_item_single_choice, items)
if (currentChoice > -1) {
setItemChecked(currentChoice, true)
}

setOnItemClickListener { _, _, position, _ ->
onItemSelected(position)
dismiss()
}
}
)
}
}
@@ -1,7 +1,6 @@
package com.github.ashutoshgngwr.noice.fragment

import android.content.Context
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
Expand All @@ -14,7 +13,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.github.ashutoshgngwr.noice.R
import com.github.ashutoshgngwr.noice.sound.Playback
import com.github.ashutoshgngwr.noice.sound.PlaybackControlEvents
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.gson.GsonBuilder
import com.google.gson.annotations.Expose
Expand Down Expand Up @@ -146,15 +144,11 @@ class PresetFragment : Fragment() {
}

private fun showDeletePresetConfirmation() {
MaterialAlertDialogBuilder(requireContext()).run {
setMessage(
getString(
R.string.preset_delete_confirmation,
dataSet[adapterPosition].name
)
)
setNegativeButton(R.string.cancel, null)
setPositiveButton(R.string.delete) { _: DialogInterface, _: Int ->
DialogFragment().show(requireActivity().supportFragmentManager) {
title(R.string.delete)
message(R.string.preset_delete_confirmation, dataSet[adapterPosition].name)
negativeButton(R.string.cancel)
positiveButton(R.string.delete) {
val preset = dataSet.removeAt(adapterPosition)
Preset.writeAllToUserPreferences(requireContext(), dataSet)
notifyItemRemoved(adapterPosition)
Expand All @@ -171,7 +165,6 @@ class PresetFragment : Fragment() {
.setAction(R.string.dismiss) { }
.show()
}
show()
}
}
}
Expand Down

0 comments on commit 26775f0

Please sign in to comment.