diff --git a/app/build.gradle b/app/build.gradle index f2e5f94..f9877d8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,11 +42,13 @@ android { dependencies { - implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.preference:preference:1.2.0' + implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' + implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/app/src/main/java/com/denizd/textbasic/activity/MainActivity.kt b/app/src/main/java/com/denizd/textbasic/activity/MainActivity.kt index cdbac1e..7f1cb9d 100644 --- a/app/src/main/java/com/denizd/textbasic/activity/MainActivity.kt +++ b/app/src/main/java/com/denizd/textbasic/activity/MainActivity.kt @@ -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().findNavController() + ) } override fun onPause() { diff --git a/app/src/main/java/com/denizd/textbasic/adapter/QuoteAdapter.kt b/app/src/main/java/com/denizd/textbasic/adapter/QuoteAdapter.kt index 9aff9ed..b16778a 100644 --- a/app/src/main/java/com/denizd/textbasic/adapter/QuoteAdapter.kt +++ b/app/src/main/java/com/denizd/textbasic/adapter/QuoteAdapter.kt @@ -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, + private val onDeleteListener: OnDeleteListener, ) : RecyclerView.Adapter(), 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 { 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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/fragment/QuoteFragment.kt b/app/src/main/java/com/denizd/textbasic/fragment/QuoteFragment.kt index f6359fe..acff126 100644 --- a/app/src/main/java/com/denizd/textbasic/fragment/QuoteFragment.kt +++ b/app/src/main/java/com/denizd/textbasic/fragment/QuoteFragment.kt @@ -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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/denizd/textbasic/util/Extensions.kt b/app/src/main/java/com/denizd/textbasic/util/Extensions.kt new file mode 100644 index 0000000..fab2a07 --- /dev/null +++ b/app/src/main/java/com/denizd/textbasic/util/Extensions.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/res/anim/item_animation_fall_down.xml b/app/src/main/res/anim/item_animation_fall_down.xml new file mode 100644 index 0000000..801ea7a --- /dev/null +++ b/app/src/main/res/anim/item_animation_fall_down.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/layout_animation_fall_down.xml b/app/src/main/res/anim/layout_animation_fall_down.xml new file mode 100644 index 0000000..6eb9447 --- /dev/null +++ b/app/src/main/res/anim/layout_animation_fall_down.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/drag_indicator.xml b/app/src/main/res/drawable/drag_indicator.xml new file mode 100644 index 0000000..69ec316 --- /dev/null +++ b/app/src/main/res/drawable/drag_indicator.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1c87821..d0de1ce 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,89 +1,34 @@ - + android:fitsSystemWindows="true" + tools:context=".activity.MainActivity"> - + - + - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_quote.xml b/app/src/main/res/layout/fragment_quote.xml index ad45dea..beb62f3 100644 --- a/app/src/main/res/layout/fragment_quote.xml +++ b/app/src/main/res/layout/fragment_quote.xml @@ -1,34 +1,52 @@ - + tools:context=".fragment.QuoteFragment"> + + + + + + + android:scrollbarStyle="outsideOverlay" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + android:layoutAnimation="@anim/layout_animation_fall_down"/> - + android:text="@string/quote_fragment_fab_add" + app:icon="@drawable/add"/> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/quote_entry.xml b/app/src/main/res/layout/quote_entry.xml index e563a49..edaae18 100644 --- a/app/src/main/res/layout/quote_entry.xml +++ b/app/src/main/res/layout/quote_entry.xml @@ -3,39 +3,64 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="4dp"> + android:layout_marginHorizontal="8dp" + android:layout_marginVertical="4dp"> - - - + android:hint="@string/quote_entry_text_input_hint" + app:boxStrokeColor="@color/selector_text_input" + app:hintTextColor="@color/selector_text_input"> - + + + + - + + diff --git a/app/src/main/res/menu/nav_items.xml b/app/src/main/res/menu/nav_items.xml index 51865a8..35c2f14 100644 --- a/app/src/main/res/menu/nav_items.xml +++ b/app/src/main/res/menu/nav_items.xml @@ -3,7 +3,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 926fd1d..05f452f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -2,7 +2,8 @@ Text Basic Gib etwas ein… - Dies ist ein Beispieltext + Eintrag + Beispieltext Textgröße Textopazität Hintergrundopazität @@ -15,7 +16,13 @@ Daten werden wiederhergestellt… Deine Daten werden innerhalb der nächsten Stunde gesichert + Deine Einträge + Eintrag hinzufügen + Eintrag wurde gelöscht + Rückgängig + Text bearbeiten + Einträge Anleitung Einstellungen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 09cfcf3..83b4fce 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,8 @@ Text Basic Enter something… - This is an example text + Entry + Sample text text size text opacity bg opacity @@ -14,7 +15,13 @@ Restoring your data… Your data will be backed up within the next hour + Your Entries + Add entry + Entry has been deleted + Undo + Edit Text + Entries Guide Settings