From 238856f403252d4f9b650705e5b05599827b122b Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Sun, 9 Jun 2024 12:17:55 +0800 Subject: [PATCH] New feature: use Bottom Sheet to manage tasks --- .../cn/super12138/todo/constant/Constants.kt | 3 + .../cn/super12138/todo/logic/Repository.kt | 6 + .../super12138/todo/logic/dao/ToDoRoomDao.kt | 4 + .../cn/super12138/todo/utils/TextUtils.kt | 47 +++++ .../todo/views/bottomsheet/ToDoBottomSheet.kt | 184 ++++++++++++++++++ .../super12138/todo/views/todo/ToDoAdapter.kt | 67 +++---- .../todo/views/todo/ToDoFragment.kt | 94 ++------- .../todo/views/todo/ToDoFragmentViewModel.kt | 24 ++- app/src/main/res/layout/bottom_sheet_info.xml | 1 - app/src/main/res/layout/bottom_sheet_todo.xml | 172 ++++++++++++++++ app/src/main/res/layout/dialog_add_todo.xml | 118 ----------- app/src/main/res/layout/item_todo.xml | 8 +- app/src/main/res/values-zh-rCN/strings.xml | 7 +- app/src/main/res/values/strings.xml | 7 +- 14 files changed, 489 insertions(+), 253 deletions(-) create mode 100644 app/src/main/kotlin/cn/super12138/todo/utils/TextUtils.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/views/bottomsheet/ToDoBottomSheet.kt create mode 100644 app/src/main/res/layout/bottom_sheet_todo.xml delete mode 100644 app/src/main/res/layout/dialog_add_todo.xml diff --git a/app/src/main/kotlin/cn/super12138/todo/constant/Constants.kt b/app/src/main/kotlin/cn/super12138/todo/constant/Constants.kt index 8fe86fb..f38a1d9 100644 --- a/app/src/main/kotlin/cn/super12138/todo/constant/Constants.kt +++ b/app/src/main/kotlin/cn/super12138/todo/constant/Constants.kt @@ -17,7 +17,10 @@ object Constants { const val STRING_DEV_MODE = "/DEV_MODE" const val TAG_INFO_BOTTOM_SHEET = "InfoBottomSheet" + const val TAG_TODO_BOTTOM_SHEET = "ToDoBottomSheet" + const val BUNDLE_EDIT_MODE = "editMode" + const val BUNDLE_POSITION = "todoPosition" const val BUNDLE_TODO_CONTENT = "todoContent" const val BUNDLE_TODO_SUBJECT = "todoSubject" const val BUNDLE_TODO_STATE = "todoState" diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt b/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt index 657cb61..077682f 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/Repository.kt @@ -78,4 +78,10 @@ object Repository { todoDao.updateStateByUUID(uuid) } } + + suspend fun update(toDoRoom: ToDoRoom) { + withContext(Dispatchers.IO) { + todoDao.update(toDoRoom) + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/logic/dao/ToDoRoomDao.kt b/app/src/main/kotlin/cn/super12138/todo/logic/dao/ToDoRoomDao.kt index 73b925e..c05fbd3 100644 --- a/app/src/main/kotlin/cn/super12138/todo/logic/dao/ToDoRoomDao.kt +++ b/app/src/main/kotlin/cn/super12138/todo/logic/dao/ToDoRoomDao.kt @@ -3,6 +3,7 @@ package cn.super12138.todo.logic.dao import androidx.room.Dao import androidx.room.Insert import androidx.room.Query +import androidx.room.Update @Dao interface ToDoRoomDao { @@ -26,4 +27,7 @@ interface ToDoRoomDao { @Query("UPDATE todo SET state = 1 WHERE uuid = :uuid") suspend fun updateStateByUUID(uuid: String) + + @Update + suspend fun update(toDoRoom: ToDoRoom) } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/utils/TextUtils.kt b/app/src/main/kotlin/cn/super12138/todo/utils/TextUtils.kt new file mode 100644 index 0000000..993819e --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/utils/TextUtils.kt @@ -0,0 +1,47 @@ +package cn.super12138.todo.utils + +import android.text.Editable +import cn.super12138.todo.R +import cn.super12138.todo.ToDoApp + +object TextUtils { + private val subjectMap = mapOf( + globalGetString(R.string.subject_chinese) to R.id.subject_chinese, + globalGetString(R.string.subject_math) to R.id.subject_math, + globalGetString(R.string.subject_english) to R.id.subject_english, + globalGetString(R.string.subject_biology) to R.id.subject_biology, + globalGetString(R.string.subject_geography) to R.id.subject_geography, + globalGetString(R.string.subject_history) to R.id.subject_history, + globalGetString(R.string.subject_physics) to R.id.subject_physics, + globalGetString(R.string.subject_law) to R.id.subject_law, + globalGetString(R.string.subject_other) to R.id.subject_other + ) + + fun getSubjectName(id: Int): String { + return when (id) { + R.id.subject_chinese -> globalGetString(R.string.subject_chinese) + R.id.subject_math -> globalGetString(R.string.subject_math) + R.id.subject_english -> globalGetString(R.string.subject_english) + R.id.subject_biology -> globalGetString(R.string.subject_biology) + R.id.subject_geography -> globalGetString(R.string.subject_geography) + R.id.subject_history -> globalGetString(R.string.subject_history) + R.id.subject_physics -> globalGetString(R.string.subject_physics) + R.id.subject_law -> globalGetString(R.string.subject_law) + R.id.subject_other -> globalGetString(R.string.subject_other) + else -> globalGetString(R.string.subject_unknown) + } + } + + fun getSubjectID(name: String): Int? { + return subjectMap[name] + } + +} + +fun globalGetString(resID: Int): String { + return ToDoApp.context.resources.getString(resID) +} + +fun String.toEditable(): Editable? { + return Editable.Factory.getInstance().newEditable(this) +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/views/bottomsheet/ToDoBottomSheet.kt b/app/src/main/kotlin/cn/super12138/todo/views/bottomsheet/ToDoBottomSheet.kt new file mode 100644 index 0000000..2b47d71 --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/views/bottomsheet/ToDoBottomSheet.kt @@ -0,0 +1,184 @@ +package cn.super12138.todo.views.bottomsheet + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import cn.super12138.todo.R +import cn.super12138.todo.constant.Constants +import cn.super12138.todo.constant.GlobalValues +import cn.super12138.todo.databinding.BottomSheetTodoBinding +import cn.super12138.todo.logic.dao.ToDoRoom +import cn.super12138.todo.logic.model.ToDo +import cn.super12138.todo.utils.TextUtils +import cn.super12138.todo.utils.showToast +import cn.super12138.todo.utils.toEditable +import cn.super12138.todo.views.progress.ProgressFragmentViewModel +import cn.super12138.todo.views.todo.ToDoFragmentViewModel +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import java.util.UUID + +class ToDoBottomSheet : BottomSheetDialogFragment() { + private lateinit var binding: BottomSheetTodoBinding + + private val progressViewModel: ProgressFragmentViewModel by viewModels({ requireActivity() }) + private val todoViewModel: ToDoFragmentViewModel by viewModels({ requireActivity() }) + + private var editMode: Boolean = false + private var todoState: Int = 0 + private var todoPosition: Int = 0 + private lateinit var todoUUID: String + private lateinit var todoOrigSubject: String + private lateinit var todoOrigContent: String + + companion object { + const val TAG = Constants.TAG_TODO_BOTTOM_SHEET + + fun newInstance( + editMode: Boolean, + todoPosition: Int, + todoUUID: String, + todoState: Int, + todoSubject: String, + todoContent: String, + ) = ToDoBottomSheet().apply { + arguments = Bundle().apply { + putBoolean(Constants.BUNDLE_EDIT_MODE, editMode) + putInt(Constants.BUNDLE_POSITION, todoPosition) + putString(Constants.BUNDLE_TODO_UUID, todoUUID) + putInt(Constants.BUNDLE_TODO_STATE, todoState) + putString(Constants.BUNDLE_TODO_SUBJECT, todoSubject) + putString(Constants.BUNDLE_TODO_CONTENT, todoContent) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let { + editMode = it.getBoolean(Constants.BUNDLE_EDIT_MODE, false) + todoPosition = it.getInt(Constants.BUNDLE_POSITION, 0) + todoOrigContent = it.getString(Constants.BUNDLE_TODO_CONTENT, "") + todoOrigSubject = it.getString(Constants.BUNDLE_TODO_SUBJECT, "") + todoState = it.getInt(Constants.BUNDLE_TODO_STATE, 0) + todoUUID = it.getString(Constants.BUNDLE_TODO_UUID, "") + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = BottomSheetTodoBinding.inflate(inflater, container, false) + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // BottomSheet 基本参数设置 + val bottomSheetDialog = dialog as BottomSheetDialog + bottomSheetDialog.behavior.apply { + state = BottomSheetBehavior.STATE_EXPANDED + saveFlags = BottomSheetBehavior.SAVE_ALL + } + bottomSheetDialog.dismissWithAnimation = true + + val todoList = todoViewModel.todoList + + // 编辑模式 + if (editMode) { + binding.btnCancel.visibility = View.GONE + binding.btnDelete.visibility = View.VISIBLE + binding.todoSheetTitle.text = getString(R.string.update_task) + binding.todoContent.editText?.text = todoOrigContent.toEditable() + binding.btnSave.text = getString(R.string.update) + + TextUtils.getSubjectID(todoOrigSubject)?.let { binding.todoSubject.check(it) } + } + + binding.btnSave.setOnClickListener { + val todoContent = binding.todoContent.editText?.text.toString() + // 内容判空 + if (todoContent.isEmpty()) { + binding.todoContent.error = + getString(R.string.content_cannot_be_empty) + return@setOnClickListener + } else { + // 开发者模式 + if (todoContent == Constants.STRING_DEV_MODE) { + if (GlobalValues.devMode) { + GlobalValues.devMode = false + } else { + GlobalValues.devMode = true + "Dev Mode".showToast() + } + } else { + // 随机 UUID + val randomUUID = UUID.randomUUID().toString() + // 待办学科 + val todoSubject = TextUtils.getSubjectName(binding.todoSubject.checkedChipId) + + // 更新待办 + if (editMode) { + todoViewModel.updateTask( + todoPosition, + ToDoRoom( + todoUUID, + todoState, + todoSubject, + todoContent + ) + ) + todoViewModel.refreshData.value = 1 + } else { + // 添加到 RecyclerView + if (todoList.size + 1 > 0) { + todoViewModel.emptyTipVis.value = View.GONE + } + todoList.add( + ToDo(randomUUID, 0, todoContent, todoSubject) + ) + + // 插入数据库 + todoViewModel.insertTask( + ToDoRoom( + randomUUID, + 0, + todoSubject, + todoContent + ) + ) + progressViewModel.updateProgress() + todoViewModel.addData.value = 1 + } + } + } + dismiss() + } + + binding.btnCancel.setOnClickListener { + dismiss() + } + + binding.btnDelete.setOnClickListener { + todoViewModel.deleteTask(todoPosition, todoUUID) + progressViewModel.updateProgress() + todoViewModel.removeData.value = 1 + + // 空项目提示显示判断 + if (todoList.isEmpty()) { + todoViewModel.emptyTipVis.value = View.VISIBLE + } else { + todoViewModel.emptyTipVis.value = View.GONE + } + + dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoAdapter.kt b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoAdapter.kt index e595d1f..8fe5590 100644 --- a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoAdapter.kt +++ b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoAdapter.kt @@ -6,18 +6,21 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.TextView +import androidx.fragment.app.FragmentManager import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStoreOwner import androidx.recyclerview.widget.RecyclerView import cn.super12138.todo.R -import cn.super12138.todo.logic.dao.ToDoRoom +import cn.super12138.todo.constant.GlobalValues import cn.super12138.todo.logic.model.ToDo +import cn.super12138.todo.utils.showToast +import cn.super12138.todo.views.bottomsheet.ToDoBottomSheet import cn.super12138.todo.views.progress.ProgressFragmentViewModel -import com.google.android.material.snackbar.Snackbar class ToDoAdapter( private val todoList: MutableList, - private val viewModelStoreOwner: ViewModelStoreOwner + private val viewModelStoreOwner: ViewModelStoreOwner, + private val fragmentManager: FragmentManager ) : RecyclerView.Adapter() { @@ -25,7 +28,7 @@ class ToDoAdapter( val todoContext: TextView = view.findViewById(R.id.todo_content) val todoSubject: TextView = view.findViewById(R.id.todo_subject) val checkToDoBtn: Button = view.findViewById(R.id.check_item_btn) - val delToDoBtn: Button = view.findViewById(R.id.delete_item_btn) + // val delToDoBtn: Button = view.findViewById(R.id.delete_item_btn) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -60,7 +63,7 @@ class ToDoAdapter( notifyItemRemoved(position) notifyItemRangeChanged(position, todoList.size) - todoViewModel.updateTask(todo.uuid) + todoViewModel.updateTaskState(todo.uuid) // 设置空项目提示可见性 if (todoList.isEmpty()) { @@ -71,47 +74,23 @@ class ToDoAdapter( progressViewModel.updateProgress() } - holder.delToDoBtn.setOnClickListener { - it.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) - if (position < 0 || position >= todoList.size) { - return@setOnClickListener + holder.itemView.setOnClickListener { + if (GlobalValues.devMode) { + "Current position: $position".showToast() } + } - todoList.removeAt(position) - notifyItemRemoved(position) - notifyItemRangeChanged(position, todoList.size) - - todoViewModel.deleteTask(todo.uuid) - - // 设置空项目提示可见性 - if (todoList.isEmpty()) { - todoViewModel.emptyTipVis.value = View.VISIBLE - } else { - todoViewModel.emptyTipVis.value = View.GONE - } - - progressViewModel.updateProgress() - Snackbar.make(it, R.string.task_deleted, Snackbar.LENGTH_LONG) - .setAction(R.string.delete_undo) { - if (todoList.size + 1 > 0) { - todoViewModel.emptyTipVis.value = View.GONE - } - todoList.add(ToDo(todo.uuid, 0, todo.content, todo.subject)) - - todoViewModel.insertTask( - ToDoRoom( - todo.uuid, - 0, - todo.subject, - todo.content - ) - ) - - todoViewModel.refreshData.value = 1 - - progressViewModel.updateProgress() - } - .show() + holder.itemView.setOnLongClickListener { + val toDoBottomSheet = ToDoBottomSheet.newInstance( + true, + position, + todo.uuid, + todo.state, + todo.subject, + todo.content + ) + toDoBottomSheet.show(fragmentManager, ToDoBottomSheet.TAG) + true } } diff --git a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragment.kt b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragment.kt index de4dff0..c19779a 100644 --- a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragment.kt +++ b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragment.kt @@ -5,7 +5,6 @@ import android.view.HapticFeedbackConstants import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.Observer @@ -14,25 +13,18 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import cn.super12138.todo.R import cn.super12138.todo.ToDoApp -import cn.super12138.todo.constant.Constants -import cn.super12138.todo.constant.GlobalValues -import cn.super12138.todo.databinding.DialogAddTodoBinding import cn.super12138.todo.databinding.FragmentTodoBinding import cn.super12138.todo.logic.Repository -import cn.super12138.todo.logic.dao.ToDoRoom -import cn.super12138.todo.logic.model.ToDo -import cn.super12138.todo.utils.showToast +import cn.super12138.todo.views.bottomsheet.ToDoBottomSheet import cn.super12138.todo.views.progress.ProgressFragmentViewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.launch import me.zhanghai.android.fastscroll.FastScrollerBuilder -import java.util.UUID class ToDoFragment : Fragment() { private val progressViewModel: ProgressFragmentViewModel by viewModels({ requireActivity() }) private val todoViewModel: ToDoFragmentViewModel by viewModels({ requireActivity() }) private lateinit var binding: FragmentTodoBinding - private lateinit var toDoDialogBinding: DialogAddTodoBinding override fun onCreateView( inflater: LayoutInflater, @@ -60,7 +52,7 @@ class ToDoFragment : Fragment() { val layoutManager = LinearLayoutManager(ToDoApp.context) binding.todoList.layoutManager = layoutManager - val adapter = ToDoAdapter(todoList, requireActivity()) + val adapter = ToDoAdapter(todoList, requireActivity(), parentFragmentManager) binding.todoList.adapter = adapter FastScrollerBuilder(binding.todoList).apply { @@ -74,76 +66,8 @@ class ToDoFragment : Fragment() { binding.addItem.setOnClickListener { it.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) - toDoDialogBinding = DialogAddTodoBinding.inflate(layoutInflater) - - activity?.let { it1 -> - val dialog = MaterialAlertDialogBuilder(it1) - .setTitle(R.string.add_task) - .setView(toDoDialogBinding.root) - .setPositiveButton(R.string.ok, null) - .setNegativeButton(R.string.cancel, null) - .setCancelable(false) - .show() - - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - val todoContent = toDoDialogBinding.todoContent.editText?.text.toString() - if (todoContent.isEmpty()) { - toDoDialogBinding.todoContent.error = - getString(R.string.content_cannot_be_empty) - } else { - if (todoContent == Constants.STRING_DEV_MODE) { - if (GlobalValues.devMode) { - GlobalValues.devMode = false - } else { - GlobalValues.devMode = true - "Dev Mode".showToast() - } - dialog.dismiss() - } else { - // 随机UUID - val randomUUID = UUID.randomUUID().toString() - // 待办学科 - val todoSubject = when (toDoDialogBinding.todoSubject.checkedChipId) { - R.id.subject_chinese -> getString(R.string.subject_chinese) - R.id.subject_math -> getString(R.string.subject_math) - R.id.subject_english -> getString(R.string.subject_english) - R.id.subject_biology -> getString(R.string.subject_biology) - R.id.subject_geography -> getString(R.string.subject_geography) - R.id.subject_history -> getString(R.string.subject_history) - R.id.subject_physics -> getString(R.string.subject_physics) - R.id.subject_law -> getString(R.string.subject_law) - R.id.subject_other -> getString(R.string.subject_other) - else -> getString(R.string.subject_unknown) - } - - // 显示RecyclerView - if (todoList.size + 1 > 0) { - todoViewModel.emptyTipVis.value = View.GONE - } - - // 添加到RecyclerView - todoList.add( - ToDo(randomUUID, 0, todoContent, todoSubject) - ) - - // 插入数据库 - lifecycleScope.launch { - Repository.insert( - ToDoRoom( - randomUUID, - 0, - todoSubject, - todoContent - ) - ) - progressViewModel.updateProgress() - } - binding.todoList.adapter?.notifyItemInserted(todoList.size + 1) - dialog.dismiss() - } - } - } - } + val toDoBottomSheet = ToDoBottomSheet() + toDoBottomSheet.show(parentFragmentManager, ToDoBottomSheet.TAG) } binding.addItem.setOnLongClickListener { @@ -208,8 +132,16 @@ class ToDoFragment : Fragment() { } }) - todoViewModel.refreshData.observe(viewLifecycleOwner, Observer { + todoViewModel.addData.observe(viewLifecycleOwner, Observer { binding.todoList.adapter?.notifyItemInserted(todoList.size + 1) }) + + todoViewModel.removeData.observe(viewLifecycleOwner, Observer { + binding.todoList.adapter?.notifyItemRemoved(todoList.size + 1) + }) + + todoViewModel.refreshData.observe(viewLifecycleOwner, Observer { + binding.todoList.adapter?.notifyItemRangeChanged(0, todoList.size + 1) + }) } } diff --git a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragmentViewModel.kt b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragmentViewModel.kt index f365c94..b3a6ae8 100644 --- a/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragmentViewModel.kt +++ b/app/src/main/kotlin/cn/super12138/todo/views/todo/ToDoFragmentViewModel.kt @@ -11,7 +11,11 @@ import kotlinx.coroutines.launch class ToDoFragmentViewModel : ViewModel() { val emptyTipVis = MutableLiveData(View.GONE) + + val addData = MutableLiveData(0) + val removeData = MutableLiveData(0) val refreshData = MutableLiveData(0) + val todoList = ArrayList() init { @@ -34,7 +38,8 @@ class ToDoFragmentViewModel : ViewModel() { } } - fun deleteTask(uuid: String) { + fun deleteTask(position: Int, uuid: String) { + todoList.removeAt(position) viewModelScope.launch { Repository.deleteByUUID(uuid) } @@ -46,9 +51,24 @@ class ToDoFragmentViewModel : ViewModel() { } } - fun updateTask(uuid: String) { + fun updateTaskState(uuid: String) { viewModelScope.launch { Repository.updateStateByUUID(uuid) } } + + fun updateTask(position: Int, todo: ToDoRoom) { + todoList.removeAt(position) + todoList.add( + position, ToDo( + todo.uuid, + todo.state, + todo.content, + todo.subject + ) + ) + viewModelScope.launch { + Repository.update(todo) + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_info.xml b/app/src/main/res/layout/bottom_sheet_info.xml index 9488d67..7a626b0 100644 --- a/app/src/main/res/layout/bottom_sheet_info.xml +++ b/app/src/main/res/layout/bottom_sheet_info.xml @@ -25,7 +25,6 @@ android:id="@+id/todo_content_info" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="测试" android:textAlignment="center" android:textColor="?android:attr/textColorPrimary" android:textSize="25sp" /> diff --git a/app/src/main/res/layout/bottom_sheet_todo.xml b/app/src/main/res/layout/bottom_sheet_todo.xml new file mode 100644 index 0000000..ff6f72d --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_todo.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +