Skip to content

Commit 5404d92

Browse files
committed
Implementation of the PR feedbacks and previous features
1 parent eae0176 commit 5404d92

File tree

4 files changed

+318
-18
lines changed

4 files changed

+318
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88

99
### Fixed
10-
- Fixed checklist dialog disappearing on screen rotation ([#744])
10+
- Fixed the checklist dialog disappearing on screen rotation
1111
- Fixed inconsistent checklist sorting when the "Move checked items to the bottom" option is enabled ([#59])
1212

1313
## [1.6.0] - 2025-10-29
@@ -106,7 +106,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
106106
[#178]: https://github.com/FossifyOrg/Notes/issues/178
107107
[#190]: https://github.com/FossifyOrg/Notes/issues/190
108108
[#201]: https://github.com/FossifyOrg/Notes/issues/201
109-
[#744]: https://github.com/FossifyOrg/General-Discussion/issues/744
110109

111110
[Unreleased]: https://github.com/FossifyOrg/Notes/compare/1.6.0...HEAD
112111
[1.6.0]: https://github.com/FossifyOrg/Notes/compare/1.5.0...1.6.0
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.fossify.notes.dialogs
2+
3+
import android.content.DialogInterface
4+
import android.os.Bundle
5+
import androidx.appcompat.app.AlertDialog
6+
import androidx.core.os.bundleOf
7+
import androidx.fragment.app.DialogFragment
8+
import org.fossify.commons.extensions.getAlertDialogBuilder
9+
import org.fossify.commons.extensions.setupDialogStuff
10+
import org.fossify.commons.extensions.showKeyboard
11+
import org.fossify.commons.extensions.toast
12+
import org.fossify.notes.databinding.DialogRenameChecklistItemBinding
13+
import org.fossify.notes.extensions.maybeRequestIncognito
14+
import org.fossify.notes.models.Task
15+
16+
class EditTaskDialogFragment : DialogFragment() {
17+
18+
companion object {
19+
const val TAG = "EditTaskDialog"
20+
const val ARG_TASK_ID = "arg_task_id"
21+
const val ARG_OLD_TITLE = "arg_old_title"
22+
const val REQUEST_KEY = "edit_task_request"
23+
const val RESULT_TITLE = "result_title"
24+
const val RESULT_TASK_ID = "result_task_id"
25+
private const val STATE_TEXT = "state_text"
26+
27+
fun show(
28+
host: androidx.fragment.app.FragmentManager,
29+
task: Task
30+
) = EditTaskDialogFragment().apply {
31+
arguments = bundleOf(ARG_OLD_TITLE to task.title, ARG_TASK_ID to task.id)
32+
}.show(host, TAG)
33+
}
34+
35+
private lateinit var binding: DialogRenameChecklistItemBinding
36+
37+
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
38+
val activity = requireActivity()
39+
binding = DialogRenameChecklistItemBinding.inflate(activity.layoutInflater).also {
40+
val restored = savedInstanceState?.getString(STATE_TEXT)
41+
it.checklistItemTitle.setText(
42+
restored ?: requireArguments().getString(ARG_OLD_TITLE).orEmpty()
43+
)
44+
it.checklistItemTitle.maybeRequestIncognito()
45+
}
46+
47+
val builder = activity.getAlertDialogBuilder()
48+
.setPositiveButton(org.fossify.commons.R.string.ok, null)
49+
.setNegativeButton(org.fossify.commons.R.string.cancel, null)
50+
51+
var dialog: AlertDialog? = null
52+
activity.setupDialogStuff(binding.root, builder) { alert ->
53+
alert.showKeyboard(binding.checklistItemTitle)
54+
alert.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
55+
val newTitle = binding.checklistItemTitle.text?.toString().orEmpty()
56+
if (newTitle.isEmpty()) {
57+
activity.toast(org.fossify.commons.R.string.empty_name)
58+
} else {
59+
val taskId = requireArguments().getInt(ARG_TASK_ID)
60+
parentFragmentManager
61+
.setFragmentResult(
62+
REQUEST_KEY, bundleOf(RESULT_TASK_ID to taskId, RESULT_TITLE to newTitle)
63+
)
64+
alert.dismiss()
65+
}
66+
}
67+
dialog = alert
68+
}
69+
70+
return dialog!!
71+
}
72+
73+
override fun onSaveInstanceState(outState: Bundle) {
74+
val text = binding.checklistItemTitle.text?.toString().orEmpty()
75+
outState.putString(STATE_TEXT, text)
76+
super.onSaveInstanceState(outState)
77+
}
78+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
package org.fossify.notes.dialogs
2+
3+
import android.content.DialogInterface
4+
import android.os.Bundle
5+
import android.view.KeyEvent
6+
import android.view.View
7+
import android.view.inputmethod.EditorInfo
8+
import androidx.appcompat.app.AlertDialog
9+
import androidx.appcompat.widget.AppCompatEditText
10+
import androidx.core.os.bundleOf
11+
import androidx.fragment.app.DialogFragment
12+
import org.fossify.commons.extensions.beVisibleIf
13+
import org.fossify.commons.extensions.getAlertDialogBuilder
14+
import org.fossify.commons.extensions.getContrastColor
15+
import org.fossify.commons.extensions.getProperPrimaryColor
16+
import org.fossify.commons.extensions.setupDialogStuff
17+
import org.fossify.commons.extensions.showKeyboard
18+
import org.fossify.commons.extensions.toast
19+
import org.fossify.commons.helpers.SORT_BY_CUSTOM
20+
import org.fossify.notes.R
21+
import org.fossify.notes.databinding.DialogNewChecklistItemBinding
22+
import org.fossify.notes.databinding.ItemAddChecklistBinding
23+
import org.fossify.notes.extensions.config
24+
import org.fossify.notes.extensions.maybeRequestIncognito
25+
26+
class NewChecklistItemDialogFragment : DialogFragment() {
27+
28+
private val activeInputFields = mutableListOf<AppCompatEditText>()
29+
private var binding: DialogNewChecklistItemBinding? = null
30+
31+
// Track the index of the currently focused row
32+
private var lastFocusedIndex = -1
33+
34+
companion object {
35+
const val TAG = "NewChecklistItemDialogFragment"
36+
const val REQUEST_KEY = "new_checklist_item_request"
37+
const val RESULT_TEXT = "result_text"
38+
const val RESULT_ADD_TOP = "result_add_top"
39+
40+
private const val ARG_NOTE_ID = "arg_note_id"
41+
private const val STATE_TEXTS = "state_texts"
42+
43+
private const val STATE_FOCUSED_INDEX = "state_focused_index"
44+
45+
46+
fun show(
47+
host: androidx.fragment.app.FragmentManager,
48+
noteId: Long
49+
) = NewChecklistItemDialogFragment().apply {
50+
arguments = bundleOf(ARG_NOTE_ID to noteId)
51+
}.show(host, TAG)
52+
}
53+
54+
55+
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
56+
val activity = requireActivity()
57+
binding = DialogNewChecklistItemBinding.inflate(activity.layoutInflater)
58+
activeInputFields.clear()
59+
60+
// Restore state or add initial row
61+
if (savedInstanceState != null) {
62+
val savedTexts = savedInstanceState.getStringArrayList(STATE_TEXTS)
63+
if (!savedTexts.isNullOrEmpty()) {
64+
savedTexts.forEach { text -> addNewRow(text) }
65+
} else {
66+
addNewRow("")
67+
}
68+
// Restore the focus index
69+
lastFocusedIndex = savedInstanceState.getInt(STATE_FOCUSED_INDEX, -1)
70+
} else {
71+
addNewRow("")
72+
}
73+
74+
// Setup UI
75+
val noteId = requireArguments().getLong(ARG_NOTE_ID)
76+
val contrastColor = activity.getProperPrimaryColor().getContrastColor()
77+
binding!!.addItem.setColorFilter(contrastColor)
78+
79+
// Insert after the currently focused row
80+
binding!!.addItem.setOnClickListener {
81+
val insertIndex = if (lastFocusedIndex != -1 && lastFocusedIndex < activeInputFields.size) {
82+
lastFocusedIndex + 1
83+
} else {
84+
null // Append to end if nothing is focused
85+
}
86+
addNewRow("", focus = true, position = insertIndex)
87+
}
88+
89+
val config = activity.config
90+
binding!!.settingsAddChecklistTop.beVisibleIf(config.getSorting(noteId) == SORT_BY_CUSTOM)
91+
binding!!.settingsAddChecklistTop.isChecked = config.addNewChecklistItemsTop
92+
93+
val builder = activity.getAlertDialogBuilder()
94+
.setTitle(R.string.add_new_checklist_items)
95+
.setPositiveButton(org.fossify.commons.R.string.ok, null)
96+
.setNegativeButton(org.fossify.commons.R.string.cancel, null)
97+
98+
var dialog: AlertDialog? = null
99+
activity.setupDialogStuff(binding!!.root, builder) { alert ->
100+
101+
// Apply Focus : if we have a valid restored index, use it
102+
if (lastFocusedIndex != -1 && lastFocusedIndex < activeInputFields.size) {
103+
alert.showKeyboard(activeInputFields[lastFocusedIndex])
104+
} else if (activeInputFields.isNotEmpty()) {
105+
// Default to the last
106+
alert.showKeyboard(activeInputFields.last())
107+
}
108+
109+
alert.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
110+
// Collect all texts
111+
val combinedText = activeInputFields
112+
.map { it.text.toString().trim() }
113+
.filter { it.isNotEmpty() }
114+
.joinToString("\n")
115+
116+
if (combinedText.isEmpty()) {
117+
activity.toast(org.fossify.commons.R.string.empty_name)
118+
} else {
119+
config.addNewChecklistItemsTop = binding!!.settingsAddChecklistTop.isChecked
120+
121+
// Return result
122+
parentFragmentManager.setFragmentResult(
123+
REQUEST_KEY,
124+
bundleOf(
125+
RESULT_TEXT to combinedText,
126+
RESULT_ADD_TOP to binding!!.settingsAddChecklistTop.isChecked
127+
)
128+
)
129+
alert.dismiss()
130+
}
131+
}
132+
dialog = alert
133+
}
134+
135+
return dialog!!
136+
}
137+
138+
private fun addNewRow(text: String, focus: Boolean = false, position: Int? = null) {
139+
val rowBinding = ItemAddChecklistBinding.inflate(layoutInflater)
140+
141+
// Disable state saving for individual views to avoid rotation conflict
142+
rowBinding.titleEditText.isSaveEnabled = false
143+
rowBinding.titleEditText.setText(text)
144+
rowBinding.titleEditText.maybeRequestIncognito()
145+
146+
if (text.isNotEmpty()) {
147+
rowBinding.titleEditText.setSelection(text.length)
148+
}
149+
150+
// Track focus changes in real time
151+
rowBinding.titleEditText.setOnFocusChangeListener { view, hasFocus ->
152+
if (hasFocus) {
153+
// When this view gets focus, remember its index
154+
lastFocusedIndex = activeInputFields.indexOf(view)
155+
}
156+
}
157+
158+
// Add "Enter" key listener to create new rows automatically
159+
rowBinding.titleEditText.setOnEditorActionListener { v, actionId, event ->
160+
if (actionId == EditorInfo.IME_ACTION_NEXT ||
161+
actionId == EditorInfo.IME_ACTION_DONE ||
162+
event?.keyCode == KeyEvent.KEYCODE_ENTER) {
163+
164+
val currentIndex = activeInputFields.indexOf(v)
165+
addNewRow("", focus = true, position = currentIndex + 1)
166+
true
167+
} else {
168+
false
169+
}
170+
}
171+
172+
val inputField = rowBinding.titleEditText as AppCompatEditText
173+
174+
// Insert into list and view hierarchy at correct position
175+
if (position != null && position < activeInputFields.size) {
176+
activeInputFields.add(position, inputField)
177+
binding?.checklistHolder?.addView(rowBinding.root, position)
178+
} else {
179+
activeInputFields.add(inputField)
180+
binding?.checklistHolder?.addView(rowBinding.root)
181+
}
182+
183+
184+
if (focus) {
185+
binding?.dialogHolder?.post {
186+
// Only scroll to bottom if appending to the end
187+
if (position == null) {
188+
binding?.dialogHolder?.fullScroll(View.FOCUS_DOWN)
189+
}
190+
191+
inputField.requestFocus()
192+
requireActivity().showKeyboard(inputField)
193+
}
194+
}
195+
}
196+
197+
override fun onSaveInstanceState(outState: Bundle) {
198+
super.onSaveInstanceState(outState)
199+
val currentTexts = ArrayList(activeInputFields.map { it.text.toString() })
200+
outState.putStringArrayList(STATE_TEXTS, currentTexts)
201+
202+
// Save the index tracked via the listener
203+
outState.putInt(STATE_FOCUSED_INDEX, lastFocusedIndex)
204+
}
205+
206+
override fun onDestroyView() {
207+
super.onDestroyView()
208+
binding = null
209+
activeInputFields.clear()
210+
}
211+
}

app/src/main/kotlin/org/fossify/notes/fragments/TasksFragment.kt

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import org.fossify.notes.activities.SimpleActivity
1515
import org.fossify.notes.adapters.TasksAdapter
1616
import org.fossify.notes.databinding.FragmentChecklistBinding
1717
import org.fossify.notes.dialogs.ChecklistItemDialogFragment
18+
import org.fossify.notes.dialogs.EditTaskDialogFragment
19+
import org.fossify.notes.dialogs.NewChecklistItemDialogFragment
1820
import org.fossify.notes.extensions.config
1921
import org.fossify.notes.extensions.updateWidgets
2022
import org.fossify.notes.helpers.NOTE_ID
@@ -35,6 +37,9 @@ class TasksFragment : NoteFragment(), TasksActionListener {
3537

3638
var tasks = mutableListOf<Task>()
3739

40+
// Variable to track the callback function
41+
private var editTaskCallback: (() -> Unit)? = null
42+
3843
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
3944
binding = FragmentChecklistBinding.inflate(inflater, container, false)
4045
noteId = requireArguments().getLong(NOTE_ID, 0L)
@@ -45,19 +50,27 @@ class TasksFragment : NoteFragment(), TasksActionListener {
4550
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
4651
super.onViewCreated(view, savedInstanceState)
4752

48-
// Listen for results from the ChecklistItemDialogFragment
49-
childFragmentManager.setFragmentResultListener(ChecklistItemDialogFragment.REQUEST_KEY,
53+
// Listen for results from the NewChecklistItemDialogFragment
54+
childFragmentManager.setFragmentResultListener(NewChecklistItemDialogFragment.REQUEST_KEY,
5055
viewLifecycleOwner)
5156
{ _, bundle ->
52-
val text = bundle.getString(ChecklistItemDialogFragment.RESULT_TEXT_KEY) ?: return@setFragmentResultListener
53-
val taskId = bundle.getInt(ChecklistItemDialogFragment.RESULT_TASK_ID_KEY, -1)
54-
55-
if (taskId == -1) {
56-
// ID is -1, so we are adding a NEW item
57-
addNewChecklistItems(text)
58-
} else {
59-
// ID exists, so we are EDITING an existing item
60-
updateExistingTask(taskId, text)
57+
val text = bundle.getString(NewChecklistItemDialogFragment.RESULT_TEXT) ?: return@setFragmentResultListener
58+
addNewChecklistItems(text)
59+
}
60+
61+
// Listen for EditTaskDialogFragment
62+
childFragmentManager.setFragmentResultListener(EditTaskDialogFragment.REQUEST_KEY,
63+
viewLifecycleOwner)
64+
{ _, bundle ->
65+
val taskId = bundle.getInt(EditTaskDialogFragment.RESULT_TASK_ID)
66+
val newTitle = bundle.getString(EditTaskDialogFragment.RESULT_TITLE)
67+
68+
if (newTitle != null) {
69+
updateExistingTask(taskId, newTitle)
70+
71+
// Invoke the callback
72+
editTaskCallback?.invoke()
73+
editTaskCallback = null
6174
}
6275
}
6376
}
@@ -157,9 +170,7 @@ class TasksFragment : NoteFragment(), TasksActionListener {
157170
}
158171
}
159172
private fun showNewItemDialog() {
160-
// Pass -1 to indicate a NEW item
161-
ChecklistItemDialogFragment.newInstance(taskId = -1, text = "")
162-
.show(childFragmentManager, ChecklistItemDialogFragment.DIALOG_TAG)
173+
NewChecklistItemDialogFragment.show(childFragmentManager, noteId)
163174
}
164175

165176
private fun addNewChecklistItems(text: String) {
@@ -291,8 +302,9 @@ class TasksFragment : NoteFragment(), TasksActionListener {
291302
fun getTasks() = Gson().toJson(tasks)
292303

293304
override fun editTask(task: Task, callback: () -> Unit) {
294-
ChecklistItemDialogFragment.newInstance(taskId = task.id, text = task.title)
295-
.show(childFragmentManager, ChecklistItemDialogFragment.DIALOG_TAG)
305+
// Save the callback to be used when the result arrives
306+
this.editTaskCallback = callback
307+
EditTaskDialogFragment.show(childFragmentManager, task)
296308
}
297309

298310
private fun updateExistingTask(taskId: Int, newTitle: String) {

0 commit comments

Comments
 (0)