Implemented navigation component; deleting an entry now shows a snack bar that allows for undo; edited several layouts

This commit is contained in:
denizk0461
2023-05-15 15:18:47 +02:00
parent e0e7a4cbe0
commit a7356bc97d
15 changed files with 288 additions and 198 deletions
@@ -1,19 +1,15 @@
package com.denizd.textbasic.activity
import android.app.backup.BackupManager
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Lifecycle
import com.denizd.textbasic.R
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
import com.denizd.textbasic.databinding.ActivityMainBinding
import com.denizd.textbasic.fragment.GuideFragment
import com.denizd.textbasic.fragment.QuoteFragment
import com.denizd.textbasic.fragment.SettingsFragment
import com.denizd.textbasic.widget.TextCanvasWidget
import com.google.android.material.snackbar.Snackbar
class MainActivity : AppCompatActivity() {
@@ -24,57 +20,9 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(this.layoutInflater)
setContentView(binding.root)
binding.buttonBackupUpload.setOnClickListener {
BackupManager(this).dataChanged()
Snackbar.make(binding.rootLayout, R.string.snack_backup_upload, Snackbar.LENGTH_LONG).show()
}
binding.bottomNav.setOnItemSelectedListener { item ->
loadFragment(item.itemId)
}
}
override fun onResume() {
super.onResume()
if (supportFragmentManager.fragments.size == 0) {
binding.bottomNav.selectedItemId = R.id.quotes
loadFragment(R.id.quotes)
}
}
private fun loadFragment(type: Int): Boolean {
val fragment = when (type) {
R.id.quotes -> QuoteFragment()
R.id.guide -> GuideFragment()
R.id.settings -> SettingsFragment()
else -> QuoteFragment()
}//supportFragmentManager.findFragmentByTag(type) ?: viewModel.getFragment(type)
if (supportFragmentManager.fragments.isNotEmpty() &&
supportFragmentManager.fragments[supportFragmentManager.fragments.size-1]
.tag?.equals(type.toString()) == true
) {
return false
}
if (fragment.lifecycle.currentState != Lifecycle.State.INITIALIZED) {
return false
}
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment, type.toString())
// .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit()
// with (supportFragmentManager) {
// beginTransaction()
// .hide(fragments[fragments.size - 1]) // should this be 0?
// .show(findFragmentByTag(type) ?: TODO())
// .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
// .commit()
// }
return true
binding.navView.setupWithNavController(
binding.navHostFragmentContentMain.getFragment<NavHostFragment>().findNavController()
)
}
override fun onPause() {
@@ -5,14 +5,15 @@ import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.denizd.textbasic.R
import com.google.android.material.button.MaterialButton
import java.util.Collections
class QuoteAdapter(
quotes: List<String>,
private val onDeleteListener: OnDeleteListener,
) : RecyclerView.Adapter<QuoteAdapter.QuoteViewHolder>(),
RecyclerRowMoveCallback.RecyclerViewTouchHelperContract {
@@ -20,8 +21,8 @@ class QuoteAdapter(
class QuoteViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val quote: TextView = view.findViewById(R.id.quote)
val delete: ImageButton = view.findViewById(R.id.delete_button)
val quote: TextView = view.findViewById(R.id.edit_text_quote)
val delete: MaterialButton = view.findViewById(R.id.delete_button)
}
// override fun getItemViewType(position: Int): Int {
@@ -47,6 +48,7 @@ class QuoteAdapter(
})
holder.delete.setOnClickListener {
onDeleteListener.onDelete(mutableQuotes[holder.layoutPosition], holder.layoutPosition)
mutableQuotes.removeAt(holder.layoutPosition)
notifyItemRemoved(holder.layoutPosition)
}
@@ -59,6 +61,11 @@ class QuoteAdapter(
notifyItemInserted(mutableQuotes.size + 1)
}
fun addEntryAt(index: Int, entry: String) {
mutableQuotes.add(index, entry)
notifyItemInserted(index)
}
fun getAllQuotes(): Array<String> {
return mutableQuotes.filter { s -> s.isNotBlank() }.toTypedArray()
}
@@ -80,4 +87,8 @@ class QuoteAdapter(
override fun onRowClear(viewHolder: QuoteViewHolder) {
// TODO
}
interface OnDeleteListener {
fun onDelete(entry: String, index: Int)
}
}
@@ -1,6 +1,5 @@
package com.denizd.textbasic.fragment
import android.animation.LayoutTransition
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.ItemTouchHelper
@@ -11,40 +10,40 @@ import com.denizd.textbasic.util.viewBinding
import com.denizd.textbasic.R
import com.denizd.textbasic.adapter.RecyclerRowMoveCallback
import com.denizd.textbasic.databinding.FragmentQuoteBinding
import com.google.android.material.transition.MaterialSharedAxis
import com.denizd.textbasic.util.showSnackBar
class QuoteFragment : BaseFragment(R.layout.fragment_quote) {
class QuoteFragment : BaseFragment(R.layout.fragment_quote), QuoteAdapter.OnDeleteListener {
private lateinit var storage: QuoteStorage
private lateinit var quoteAdapter: QuoteAdapter
private val binding: FragmentQuoteBinding by viewBinding(FragmentQuoteBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.quoteConstraintLayout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
storage = QuoteStorage(context)
quoteAdapter = QuoteAdapter(storage.getAllQuotes())
quoteAdapter = QuoteAdapter(storage.getAllQuotes(), this)
binding.quoteScroller.let { s ->
s.layoutManager = LinearLayoutManager(context)
ItemTouchHelper(RecyclerRowMoveCallback(quoteAdapter)).attachToRecyclerView(s)
s.adapter = quoteAdapter
binding.recyclerView.layoutManager = LinearLayoutManager(context)
ItemTouchHelper(RecyclerRowMoveCallback(quoteAdapter))
.attachToRecyclerView(binding.recyclerView)
binding.recyclerView.adapter = quoteAdapter
// Set up scroll change listener to shrink and extend FAB accordingly
binding.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
// Calculate the vertical scroll difference
val dy = scrollY - oldScrollY
if (dy > 0) {
// If scrolling down, shrink the FAB
binding.fabAddQuote.shrink()
} else if (dy < 0) {
// If scrolling up, extend the FAB
binding.fabAddQuote.extend()
}
}
binding.addFab.setOnClickListener {
binding.fabAddQuote.setOnClickListener {
quoteAdapter.addNewQuote()
}
}
@@ -57,4 +56,15 @@ class QuoteFragment : BaseFragment(R.layout.fragment_quote) {
save()
super.onPause()
}
override fun onDelete(entry: String, index: Int) {
context.theme.showSnackBar(
binding.coordinatorLayout,
getString(R.string.quote_fragment_snack_text),
null,
getString(R.string.quote_fragment_snack_undo),
) {
quoteAdapter.addEntryAt(index, entry)
}
}
}
@@ -0,0 +1,56 @@
package com.denizd.textbasic.util
import android.content.res.Resources
import android.util.TypedValue
import android.view.View
import androidx.annotation.AttrRes
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.snackbar.Snackbar
/**
* Retrieves a specified colour customised to the currently applied theme.
*
* @param id attribute ID of the colour reference
* @return resolved colour
*/
fun Resources.Theme.getThemedColor(@AttrRes id: Int): Int = TypedValue().also { value ->
resolveAttribute(id, value, true)
}.data
/**
* Show a snack bar. Colours will be applied according to the theme given.
*
* @receiver theme to apply colours of
* @param view view where the snack bar will be shown in
* @param text text to present in the snack bar
* @param anchor view to anchor the snack bar to
* @param actionText text to show on the action button; button will only be shown if a value is
* passed here
* @param action action to execute if the action button is pressed
*/
fun Resources.Theme.showSnackBar(
view: CoordinatorLayout,
text: String,
anchor: View? = null,
actionText: String = "",
action: () -> Unit = {},
) {
val s = Snackbar.make(view, text, Snackbar.LENGTH_SHORT)
// Set colours
.setTextColor(getThemedColor(com.google.android.material.R.attr.colorOnSurfaceInverse))
.setBackgroundTint(getThemedColor(com.google.android.material.R.attr.colorSurfaceInverse))
if (actionText.isNotBlank()) {
// Set action, if a value for the text has been passed
s.setAction(actionText) {
action()
}
// Show this snack bar for a longer time for the user to have a chance to react
s.duration = Snackbar.LENGTH_LONG
}
// Set anchor view, if one has been passed
anchor?.let { a -> s.setAnchorView(a) }
s.show()
}