feat: draft scroll

This commit is contained in:
danilkinkin 2022-07-04 01:21:36 +04:00
parent 83cf4abffe
commit 94a741ee66
12 changed files with 480 additions and 168 deletions

View file

@ -47,6 +47,9 @@ dependencies {
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.fragment:fragment-ktx:$activity_version"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'

View file

@ -0,0 +1,146 @@
package com.danilkinkin.buckwheat
import android.animation.ObjectAnimator
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.activityViewModels
import com.danilkinkin.buckwheat.utils.toSP
import com.danilkinkin.buckwheat.viewmodels.DrawsViewModel
class CalculatorFragment : Fragment() {
private lateinit var model: DrawsViewModel
private var budgetFragment: TextWithLabelFragment? = null
private var drawFragment: TextWithLabelFragment? = null
private var restBudgetFragment: TextWithLabelFragment? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_calculator, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val model: DrawsViewModel by activityViewModels()
this.model = model
build()
observe()
build()
}
fun animate (view: View?, property: String, from: Float?, to: Float, duration: Long = 0): ObjectAnimator {
val animator = (if (from === null) {
ObjectAnimator
.ofFloat(view, property, to)
} else {
ObjectAnimator
.ofFloat(view, property, from, to)
})
animator.apply {
this.duration = duration
start()
}
return animator!!
}
fun build() {
val ft: FragmentTransaction = parentFragmentManager.beginTransaction()
budgetFragment = TextWithLabelFragment().also {
it.setValue("${model.budgetValue}")
it.setLabel("Budget")
it.onCreated { view ->
it.getLabelView().textSize = 10.toSP().toFloat()
it.getValueView().textSize = 40.toSP().toFloat()
}
}
ft.add(R.id.calculator, budgetFragment!!)
}
fun observe() {
model.stage.observeForever { stage ->
val ft: FragmentTransaction = parentFragmentManager.beginTransaction()
when (stage) {
DrawsViewModel.Stage.IDLE, null -> {
}
DrawsViewModel.Stage.CREATING_DRAW -> {
drawFragment = TextWithLabelFragment()
drawFragment!!.onCreated {
drawFragment!!.setValue("${model.drawValue}")
drawFragment!!.setLabel("Draw")
drawFragment!!.getLabelView().textSize = 10.toSP().toFloat()
drawFragment!!.getValueView().textSize = 46.toSP().toFloat()
}
restBudgetFragment = TextWithLabelFragment()
restBudgetFragment!!.onCreated {
restBudgetFragment!!.setValue("${model.budgetValue - model.drawValue}")
restBudgetFragment!!.setLabel("Rest budget")
restBudgetFragment!!.getLabelView().textSize = 8.toSP().toFloat()
restBudgetFragment!!.getValueView().textSize = 20.toSP().toFloat()
}
ft.add(R.id.calculator, drawFragment!!)
ft.add(R.id.calculator, restBudgetFragment!!)
ft.commit()
ft.runOnCommit {
budgetFragment?.also {
animate(it.getLabelView(), "textSize", 10.toSP().toFloat(), 6.toSP().toFloat())
animate(it.getValueView(), "textSize", 40.toSP().toFloat(), 12.toSP().toFloat())
}
}
}
DrawsViewModel.Stage.EDIT_DRAW -> {
drawFragment?.also {
it.setValue("${model.drawValue}")
it.setLabel("Draw")
}
restBudgetFragment?.also {
it.setValue("${model.budgetValue - model.drawValue}")
it.setLabel("Rest budget")
}
}
DrawsViewModel.Stage.COMMITTING_DRAW -> {
ft.remove(budgetFragment!!)
ft.remove(drawFragment!!)
ft.commit()
ft.runOnCommit {
budgetFragment = restBudgetFragment
drawFragment = null
restBudgetFragment = null
budgetFragment?.also {
it.setValue("${model.budgetValue}")
it.setLabel("Budget")
animate(it.getLabelView(), "textSize", 8.toSP().toFloat(), 10.toSP().toFloat())
animate(it.getValueView(), "textSize", 20.toSP().toFloat(), 40.toSP().toFloat())
}
}
}
}
}
}
}

View file

@ -1,36 +1,29 @@
package com.danilkinkin.buckwheat
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.*
import android.view.Menu
import android.view.MenuItem
import android.view.ViewGroup.MarginLayoutParams
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.LinearLayout
import android.view.WindowInsetsController
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.*
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentTransaction
import com.danilkinkin.buckwheat.utils.toSP
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.danilkinkin.buckwheat.adapters.DrawsAdapter
import com.danilkinkin.buckwheat.adapters.KeyboardAdapter
import com.danilkinkin.buckwheat.decorators.KeyboardDecorator
import com.danilkinkin.buckwheat.viewmodels.DrawsViewModel
import java.util.*
import kotlin.math.floor
class MainActivity : AppCompatActivity() {
private lateinit var model: DrawsViewModel
private val keyboardContainer: FragmentContainerView by lazy {
findViewById(R.id.keyboard_container)
}
private var budgetFragment: TextWithLabelFragment? = null
private var drawFragment: TextWithLabelFragment? = null
private var restBudgetFragment: TextWithLabelFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -65,6 +58,7 @@ class MainActivity : AppCompatActivity() {
val model: DrawsViewModel by viewModels()
this.model = model
build()
observe()
}
@ -85,95 +79,40 @@ class MainActivity : AppCompatActivity() {
}
}
fun animate (view: View?, property: String, from: Float?, to: Float, duration: Long = 0): ObjectAnimator {
val animator = (if (from === null) {
ObjectAnimator
.ofFloat(view, property, to)
} else {
ObjectAnimator
.ofFloat(view, property, from, to)
})
animator.apply {
this.duration = duration
start()
}
return animator!!
}
private fun observe() {
model.stage.observeForever { stage ->
val ft: FragmentTransaction = supportFragmentManager.beginTransaction()
when (stage) {
DrawsViewModel.Stage.IDLE, null -> {
}
DrawsViewModel.Stage.CREATING_DRAW -> {
drawFragment = TextWithLabelFragment()
drawFragment!!.onCreated {
drawFragment!!.setValue("${model.drawValue}")
drawFragment!!.setLabel("Draw")
drawFragment!!.getLabelView().textSize = 10.toSP().toFloat()
drawFragment!!.getValueView().textSize = 46.toSP().toFloat()
}
restBudgetFragment = TextWithLabelFragment()
restBudgetFragment!!.onCreated {
restBudgetFragment!!.setValue("${model.budgetValue - model.drawValue}")
restBudgetFragment!!.setLabel("Rest budget")
restBudgetFragment!!.getLabelView().textSize = 8.toSP().toFloat()
restBudgetFragment!!.getValueView().textSize = 20.toSP().toFloat()
}
ft.add(R.id.calculator, drawFragment!!)
ft.add(R.id.calculator, restBudgetFragment!!)
ft.commit()
ft.runOnCommit {
budgetFragment?.also {
animate(it.getLabelView(), "textSize", 10.toSP().toFloat(), 6.toSP().toFloat())
animate(it.getValueView(), "textSize", 40.toSP().toFloat(), 12.toSP().toFloat())
}
}
}
DrawsViewModel.Stage.EDIT_DRAW -> {
drawFragment?.also {
it.setValue("${model.drawValue}")
it.setLabel("Draw")
}
restBudgetFragment?.also {
it.setValue("${model.budgetValue - model.drawValue}")
it.setLabel("Rest budget")
}
}
DrawsViewModel.Stage.COMMITTING_DRAW -> {
ft.remove(budgetFragment!!)
ft.remove(drawFragment!!)
ft.commit()
ft.runOnCommit {
budgetFragment = restBudgetFragment
drawFragment = null
restBudgetFragment = null
budgetFragment?.also {
it.setValue("${model.budgetValue}")
it.setLabel("Budget")
animate(it.getLabelView(), "textSize", 8.toSP().toFloat(), 10.toSP().toFloat())
animate(it.getValueView(), "textSize", 20.toSP().toFloat(), 40.toSP().toFloat())
}
}
}
}
}
}
private fun build() {
keyboardContainer.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
val myLinearLayoutManager = object : LinearLayoutManager(this) {
private var isScrollEnabled = true
fun setScrollEnabled(flag: Boolean) {
this.isScrollEnabled = flag
}
override fun canScrollVertically(): Boolean {
// Log.d("Main", "isScrollEnabled = $isScrollEnabled")
return isScrollEnabled && super.canScrollVertically();
}
}
val drawsAdapter = DrawsAdapter()
val keyboardAdapter = KeyboardAdapter(supportFragmentManager) { lockScroll ->
myLinearLayoutManager.setScrollEnabled(!lockScroll)
}
val contactAdapter = ConcatAdapter(drawsAdapter, keyboardAdapter)
val recyclerView: RecyclerView = findViewById(R.id.recycle_view)
recyclerView.layoutManager = myLinearLayoutManager
recyclerView.adapter = contactAdapter
recyclerView.addItemDecoration(KeyboardDecorator())
model.getDraws().observeForever { draws ->
drawsAdapter.submitList(draws)
}
/* keyboardContainer.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
keyboardContainer.viewTreeObserver.removeOnGlobalLayoutListener(this)
@ -215,21 +154,9 @@ class MainActivity : AppCompatActivity() {
Log.d("Main", "$type: $value")
}
budgetFragment = TextWithLabelFragment().also {
it.setValue("${model.budgetValue}")
it.setLabel("Budget")
it.onCreated { view ->
it.getLabelView().textSize = 10.toSP().toFloat()
it.getValueView().textSize = 40.toSP().toFloat()
}
}
ft.add(R.id.calculator, budgetFragment!!)
ft.replace(R.id.keyboard_container, keyboard)
ft.commit()
ft.commit() */
}
override fun onSupportNavigateUp(): Boolean {

View file

@ -0,0 +1,61 @@
package com.danilkinkin.buckwheat.adapters
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.danilkinkin.buckwheat.R
import com.danilkinkin.buckwheat.entities.Draw
import com.google.android.material.textview.MaterialTextView
class DrawsAdapter() : ListAdapter<Draw, DrawsAdapter.DrawViewHolder>(DrawDiffCallback) {
/**
* Provide a reference to the type of views that you are using
* (custom ViewHolder).
*/
class DrawViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val valueTextView: MaterialTextView = itemView.findViewById(R.id.value)
private val dateTextView: MaterialTextView = itemView.findViewById(R.id.date)
private var currentDraw: Draw? = null
fun bind(draw: Draw) {
currentDraw = draw
valueTextView.text = "${draw.value}"
dateTextView.text = "${draw.date.time}"
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): DrawViewHolder {
// Create a new view, which defines the UI of the list item
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.item_draw, viewGroup, false)
return DrawViewHolder(view)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(viewHolder: DrawViewHolder, position: Int) {
// Get element from your dataset at this position and replace the
// contents of the view with that element
val draw = getItem(position)
viewHolder.bind(draw)
}
}
object DrawDiffCallback : DiffUtil.ItemCallback<Draw>() {
override fun areItemsTheSame(oldItem: Draw, newItem: Draw): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Draw, newItem: Draw): Boolean {
return oldItem.uid == newItem.uid
}
}

View file

@ -0,0 +1,104 @@
package com.danilkinkin.buckwheat.adapters
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.danilkinkin.buckwheat.KeyboardFragment
import com.danilkinkin.buckwheat.R
class KeyboardAdapter(
private val fragmentManager: FragmentManager,
val lockScroll: (lock: Boolean) -> Unit,
) : RecyclerView.Adapter<KeyboardAdapter.KeyboardHolder>() {
private var isStartInKeyboard = false
private var isStartInOutside = false
/**
* Provide a reference to the type of views that you are using
* (custom ViewHolder).
*/
class KeyboardHolder(view: View) : RecyclerView.ViewHolder(view) {
val flContainer: ConstraintLayout
init {
// Define click listener for the ViewHolder's View.
flContainer = view.findViewById(R.id.container)
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): KeyboardHolder {
// Create a new view, which defines the UI of the list item
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.item_keyboard, viewGroup, false)
viewGroup.setOnTouchListener { viewClick, motionEvent ->
if (motionEvent.action == MotionEvent.ACTION_UP) {
isStartInKeyboard = false
isStartInOutside = false
}
if (motionEvent.y < view.y || isStartInOutside) {
if (motionEvent.action == MotionEvent.ACTION_DOWN || motionEvent.action == MotionEvent.ACTION_MOVE) {
if (!isStartInKeyboard) isStartInOutside = true
if (isStartInOutside) this.lockScroll(false)
}
return@setOnTouchListener false
}
when (motionEvent.action){
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
if (!isStartInOutside) isStartInKeyboard = true
}
MotionEvent.ACTION_UP -> {
viewClick.performClick()
}
}
if (isStartInKeyboard) this.lockScroll(true)
return@setOnTouchListener false
}
return KeyboardHolder(view)
}
override fun onViewAttachedToWindow(holder: KeyboardHolder) {
attachFragmentToContainer()
super.onViewAttachedToWindow(holder)
}
fun attachFragmentToContainer() {
val fragment = if (fragmentManager.fragments.firstOrNull { it is KeyboardFragment } == null)
KeyboardFragment()
else
null
if (fragment != null) {
fragmentManager.beginTransaction()
.add(R.id.container, fragment)
.commitNowAllowingStateLoss()
}
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(viewHolder: KeyboardHolder, position: Int) {
// Get element from your dataset at this position and replace the
// contents of the view with that element
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = 1
}

View file

@ -1,5 +1,6 @@
package com.danilkinkin.buckwheat.dao
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
@ -9,7 +10,7 @@ import com.danilkinkin.buckwheat.entities.Draw
@Dao
interface DrawDao {
@Query("SELECT * FROM draw")
fun getAll(): List<Draw>
fun getAll(): LiveData<List<Draw>>
@Insert
fun insertAll(vararg draw: Draw)

View file

@ -0,0 +1,36 @@
package com.danilkinkin.buckwheat.decorators
import android.graphics.Canvas
import android.util.Log
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.children
import androidx.recyclerview.widget.RecyclerView
import com.danilkinkin.buckwheat.R
import com.danilkinkin.buckwheat.adapters.KeyboardAdapter
import com.danilkinkin.buckwheat.utils.toDP
import java.lang.Integer.max
import kotlin.math.min
class KeyboardDecorator: RecyclerView.ItemDecoration() {
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
val view = parent.children.last()
if (parent.getChildViewHolder(view) is KeyboardAdapter.KeyboardHolder) {
val layout = view.layoutParams
layout.height = view.width
view.layoutParams = layout
val containerView = view.findViewById<ConstraintLayout>(R.id.container)
val layoutContainer = containerView.layoutParams
layoutContainer.height = min(max(parent.height - view.top, 250.toDP()), view.width)
containerView.layoutParams = layoutContainer
}
}
}

View file

@ -14,7 +14,7 @@ import kotlin.coroutines.CoroutineContext
class DrawsViewModel(application: Application) : AndroidViewModel(application) {
enum class Stage { IDLE, CREATING_DRAW, EDIT_DRAW, COMMITTING_DRAW }
val db = DatabaseModule.getInstance(application)
private val db = DatabaseModule.getInstance(application)
var stage: MutableLiveData<Stage> = MutableLiveData(Stage.IDLE)
var budgetValue: Double = 10000.0
@ -25,11 +25,7 @@ class DrawsViewModel(application: Application) : AndroidViewModel(application) {
}
private val draws: MutableLiveData<List<Draw>> by lazy {
MutableLiveData<List<Draw>>().also {
loadDraws()
}
}
private val draws = db.drawDao().getAll()
fun getDraws(): LiveData<List<Draw>> {
return draws
@ -48,19 +44,14 @@ class DrawsViewModel(application: Application) : AndroidViewModel(application) {
}
fun commitDraw() {
budgetValue -= drawValue
drawValue = 0.0
db.drawDao().insertAll(Draw(drawValue, Date()))
budgetValue -= drawValue
drawValue = 0.0
stage.value = Stage.COMMITTING_DRAW
stage.value = Stage.IDLE
}
private fun loadDraws() {
// Do an asynchronous operation to fetch users.
}
}

View file

@ -9,45 +9,12 @@
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content_container"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
app:stackFromEnd="true">
<com.google.android.material.card.MaterialCardView
android:id="@+id/container_budget_for_today"
style="?attr/materialCardViewFilledStyle"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/keyboard_container"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:id="@+id/calculator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:orientation="vertical"/>
</com.google.android.material.card.MaterialCardView>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/keyboard_container"
android:name="com.danilkinkin.buckwheat.KeyboardFragment"
android:layout_width="0dp"
android:layout_height="300dp"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container_budget_for_today" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.recyclerview.widget.RecyclerView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.card.MaterialCardView
android:id="@+id/container_budget_for_today"
style="?attr/materialCardViewFilledStyle"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:id="@+id/calculator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom"
android:orientation="vertical" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/value"
style="@style/TextAppearance.Buckwheat.DisplaySmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="4356 ₽" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/date"
style="@style/TextAppearance.Material3.LabelLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/value"
tools:text="@tools:sample/date/ddmmyy" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/wrapper"
android:layout_width="match_parent"
android:layout_height="400dp" >
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="400dp"
android:padding="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>