Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 73 additions & 37 deletions AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.CheckResult
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
Expand All @@ -38,17 +37,17 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.textview.MaterialTextView
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.R
import com.ichi2.anki.asyncCatching
import com.ichi2.anki.databinding.DialogSetDueDateBinding
import com.ichi2.anki.databinding.SetDueDateRangeBinding
import com.ichi2.anki.databinding.SetDueDateSingleBinding
import com.ichi2.anki.launchCatchingTask
import com.ichi2.anki.libanki.CardId
import com.ichi2.anki.libanki.sched.Scheduler
Expand All @@ -68,6 +67,7 @@ import com.ichi2.utils.neutralButton
import com.ichi2.utils.positiveButton
import com.ichi2.utils.requireBoolean
import com.ichi2.utils.title
import dev.androidbroadcast.vbpd.viewBinding
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.launch
import timber.log.Timber
Expand All @@ -90,6 +90,8 @@ class SetDueDateDialog : DialogFragment() {

val viewModel: SetDueDateViewModel by activityViewModels<SetDueDateViewModel>()

private lateinit var binding: DialogSetDueDateBinding

// used to determine if a rotation has taken place
private var initialRotation: Int = 0

Expand Down Expand Up @@ -132,14 +134,20 @@ class SetDueDateDialog : DialogFragment() {
}
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
MaterialAlertDialogBuilder(requireContext())
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DialogSetDueDateBinding.inflate(layoutInflater)
return MaterialAlertDialogBuilder(requireContext())
.create {
title(text = TR.actionsSetDueDate().toSentenceCase(this@SetDueDateDialog, R.string.sentence_set_due_date))
title(
text =
TR
.actionsSetDueDate()
.toSentenceCase(this@SetDueDateDialog, R.string.sentence_set_due_date),
)
positiveButton(R.string.dialog_ok) { launchUpdateDueDate() }
negativeButton(R.string.dialog_cancel)
neutralButton(R.string.help)
setView(R.layout.dialog_set_due_date)
setView(binding.root)
}.apply {
show()

Expand All @@ -152,31 +160,34 @@ class SetDueDateDialog : DialogFragment() {
viewModel.isValidFlow.collect { isValid -> positiveButton.isEnabled = isValid }
}
// setup viewpager + tabs
val viewPager = findViewById<ViewPager2>(R.id.set_due_date_pager)!!
viewPager.adapter = DueDateStateAdapter(this@SetDueDateDialog)
val tabLayout = findViewById<TabLayout>(R.id.tab_layout)!!
TabLayoutMediator(tabLayout, viewPager) { tab: TabLayout.Tab, position: Int ->
binding.setDueDatePager.adapter = DueDateStateAdapter(this@SetDueDateDialog)
TabLayoutMediator(
binding.tabLayout,
binding.setDueDatePager,
) { tab: TabLayout.Tab, position: Int ->
SetDueDateViewModel.Tab.entries
.first { it.position == position }
.let { selectedTab ->
tab.setIcon(selectedTab.icon)
}
}.attach()
tabLayout.selectTab(tabLayout.getTabAt(0))
binding.tabLayout.selectTab(binding.tabLayout.getTabAt(0))

viewPager.registerOnPageChangeCallback(
binding.setDueDatePager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
SetDueDateViewModel.Tab.entries.first { it.position == position }.let { selectedTab ->
viewModel.currentTab = selectedTab
}
SetDueDateViewModel.Tab.entries
.first { it.position == position }
.let { selectedTab ->
viewModel.currentTab = selectedTab
}
super.onPageSelected(position)
}
},
)

// setup 'set interval to same value' checkbox
findViewById<MaterialCheckBox>(R.id.change_interval)!!.also { cb ->
binding.changeInterval.also { cb ->
// `.also` is used as .isVisible is an extension, so Kotlin prefers
// incorrectly setting Fragment.isVisible
cb.isVisible = viewModel.canSetUpdateIntervalToMatchDueDate
Expand All @@ -188,7 +199,7 @@ class SetDueDateDialog : DialogFragment() {

lifecycleScope.launch {
viewModel.currentInterval.collect { currentInterval ->
findViewById<MaterialTextView>(R.id.current_interval_text)!!.also { tv ->
binding.currentIntervalText.also { tv ->
// Current interval is set to null when multiple cards are selected
if (currentInterval != null) {
tv.isVisible = true
Expand All @@ -205,6 +216,7 @@ class SetDueDateDialog : DialogFragment() {
}
}
}
}

override fun setupDialog(
dialog: Dialog,
Expand All @@ -217,7 +229,8 @@ class SetDueDateDialog : DialogFragment() {
// The dialog is too wide on tablets
// Select either 450dp (tablets)
// or 100% of the screen width (smaller phones)
val intendedWidth = min(MAX_WIDTH_DP.dp.toPx(this.requireContext()), resources.displayMetrics.widthPixels)
val intendedWidth =
min(MAX_WIDTH_DP.dp.toPx(this.requireContext()), resources.displayMetrics.widthPixels)
Timber.d("updating width to %d", intendedWidth)
this.dialog?.window?.setLayout(
intendedWidth,
Expand All @@ -242,7 +255,10 @@ class SetDueDateDialog : DialogFragment() {
arguments =
bundleOf(
ARG_CARD_IDS to cardIds.toLongArray(),
ARG_FSRS to (getFSRSStatus() ?: false.also { Timber.w("FSRS Status error") }),
ARG_FSRS to (
getFSRSStatus()
?: false.also { Timber.w("FSRS Status error") }
),
)
Timber.i("Showing 'set due date' dialog for %d cards", cardIds.size)
}
Expand All @@ -264,18 +280,24 @@ class SetDueDateDialog : DialogFragment() {
class SelectSingleDateFragment : Fragment(R.layout.set_due_date_single) {
private val viewModel: SetDueDateViewModel by activityViewModels<SetDueDateViewModel>()

private val binding by viewBinding(SetDueDateSingleBinding::bind)

override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<TextInputLayout>(R.id.set_due_date_single_day_text).apply {
binding.setDueDateSingleDayInputLayout.apply {
editText!!.apply {
viewModel.nextSingleDayDueDate?.let { value -> setText(value.toString()) }
doOnTextChanged { text, _, _, _ ->
val currentValue = text?.toString()?.toIntOrNull()
viewModel.nextSingleDayDueDate = currentValue
suffixText = resources.getQuantityString(R.plurals.set_due_date_label_suffix, currentValue ?: 0)
suffixText =
resources.getQuantityString(
R.plurals.set_due_date_label_suffix,
currentValue ?: 0,
)
}
suffixText = resources.getQuantityString(R.plurals.set_due_date_label_suffix, 0)
helperText =
Expand All @@ -290,7 +312,10 @@ class SetDueDateDialog : DialogFragment() {
return@setOnEditorActionListener if (actionId == EditorInfo.IME_ACTION_DONE ||
event?.keyCode == KeyEvent.KEYCODE_ENTER
) {
parentFragmentManager.setFragmentResult(RESULT_SUBMIT_DUE_DATE, bundleOf())
parentFragmentManager.setFragmentResult(
RESULT_SUBMIT_DUE_DATE,
bundleOf(),
)
true
} else {
false
Expand All @@ -299,16 +324,17 @@ class SetDueDateDialog : DialogFragment() {
selectAllWhenFocused()
}
}
view.findViewById<TextView>(R.id.date_single_label).text =
resources.getQuantityString(R.plurals.set_due_date_single_day_label, viewModel.cardCount)
binding.dateSingleLabel.text =
resources.getQuantityString(
R.plurals.set_due_date_single_day_label,
viewModel.cardCount,
)
}

override fun onResume() {
super.onResume()
this.requireView().requestLayout() // update the height of the ViewPager

val editText = requireView().findViewById<TextInputLayout>(R.id.set_due_date_single_day_text).editText!!
AndroidUiUtils.setFocusAndOpenKeyboard(editText)
AndroidUiUtils.setFocusAndOpenKeyboard(binding.setDueDateSingleDayEditText)
}
}

Expand All @@ -318,39 +344,50 @@ class SetDueDateDialog : DialogFragment() {
class SelectDateRangeFragment : Fragment(R.layout.set_due_date_range) {
private val viewModel: SetDueDateViewModel by activityViewModels<SetDueDateViewModel>()

private val binding by viewBinding(SetDueDateRangeBinding::bind)

override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<TextInputLayout>(R.id.date_range_start_layout).apply {
binding.dateRangeStartLayout.apply {
editText!!.apply {
viewModel.dateRange.start?.let { start -> setText(start.toString()) }
doOnTextChanged { text, _, _, _ ->
val value = text.toString().toIntOrNull()
viewModel.setNextDateRangeStart(value)
suffixText =
resources.getQuantityString(R.plurals.set_due_date_label_suffix, value ?: 0)
resources.getQuantityString(
R.plurals.set_due_date_label_suffix,
value ?: 0,
)
}
suffixText = resources.getQuantityString(R.plurals.set_due_date_label_suffix, 0)
selectAllWhenFocused()
}
}
view.findViewById<TextInputLayout>(R.id.date_range_end_layout).apply {
binding.dateRangeEndLayout.apply {
editText!!.apply {
doOnTextChanged { text, _, _, _ ->
val value = text.toString().toIntOrNull()
viewModel.setNextDateRangeEnd(value)
suffixText =
resources.getQuantityString(R.plurals.set_due_date_label_suffix, value ?: 0)
resources.getQuantityString(
R.plurals.set_due_date_label_suffix,
value ?: 0,
)
}
suffixText = resources.getQuantityString(R.plurals.set_due_date_label_suffix, 0)
viewModel.dateRange.end?.let { end -> setText(end.toString()) }
setOnEditorActionListener { _, actionId, event ->
return@setOnEditorActionListener if (actionId == EditorInfo.IME_ACTION_DONE ||
event?.keyCode == KeyEvent.KEYCODE_ENTER
) {
parentFragmentManager.setFragmentResult(RESULT_SUBMIT_DUE_DATE, bundleOf())
parentFragmentManager.setFragmentResult(
RESULT_SUBMIT_DUE_DATE,
bundleOf(),
)
true
} else {
false
Expand All @@ -359,16 +396,15 @@ class SetDueDateDialog : DialogFragment() {
selectAllWhenFocused()
}
}
view.findViewById<TextView>(R.id.date_range_label).text =
binding.dateRangeLabel.text =
resources.getQuantityString(R.plurals.set_due_date_range_label, viewModel.cardCount)
}

override fun onResume() {
super.onResume()
this.requireView().requestLayout() // update the height of the ViewPager

val editText = requireView().findViewById<TextInputLayout>(R.id.date_range_start_layout).editText!!
AndroidUiUtils.setFocusAndOpenKeyboard(editText)
AndroidUiUtils.setFocusAndOpenKeyboard(binding.dateRangeStartEditText)
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions AnkiDroid/src/main/res/layout-w340dp/set_due_date_range.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@
app:layout_constraintTop_toBottomOf="@+id/date_range_label">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/date_range_start"
android:id="@+id/date_range_start_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="5"
android:imeOptions="actionNext"
android:nextFocusForward="@id/date_range_end" />
android:nextFocusForward="@id/date_range_end_edit_text" />
</com.google.android.material.textfield.TextInputLayout>


Expand Down Expand Up @@ -79,7 +79,7 @@
app:layout_constraintTop_toBottomOf="@+id/date_range_label">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/date_range_end"
android:id="@+id/date_range_end_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
Expand Down
2 changes: 0 additions & 2 deletions AnkiDroid/src/main/res/layout/dialog_set_due_date.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,12 @@
app:tabGravity="fill" >

<com.google.android.material.tabs.TabItem
android:id="@+id/set_single_day"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:icon="@drawable/calendar_single_day"
/>

<com.google.android.material.tabs.TabItem
android:id="@+id/set_date_range"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:icon="@drawable/calendar_date_range"
Expand Down
6 changes: 3 additions & 3 deletions AnkiDroid/src/main/res/layout/set_due_date_range.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@
app:layout_constraintTop_toBottomOf="@+id/date_range_label">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/date_range_start"
android:id="@+id/date_range_start_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/set_due_date_range_start"
android:inputType="number"
android:maxLength="5"
android:imeOptions="actionNext"
android:nextFocusForward="@id/date_range_end"
android:nextFocusForward="@id/date_range_end_edit_text"
/>
</com.google.android.material.textfield.TextInputLayout>

Expand All @@ -65,7 +65,7 @@
app:suffixText="days">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/date_range_end"
android:id="@+id/date_range_end_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/set_due_date_range_end"
Expand Down
3 changes: 2 additions & 1 deletion AnkiDroid/src/main/res/layout/set_due_date_single.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
app:layout_constraintStart_toStartOf="parent" />

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/set_due_date_single_day_text"
android:id="@+id/set_due_date_single_day_input_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
Expand All @@ -49,6 +49,7 @@
>

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/set_due_date_single_day_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ val SetDueDateDialog.positiveButtonIsEnabled get() =
(dialog as AlertDialog).positiveButton.isEnabled

val SetDueDateDialog.singleDayTextLayout: TextInputLayout get() =
dialog!!.findViewById(R.id.set_due_date_single_day_text)
dialog!!.findViewById(R.id.set_due_date_single_day_input_layout)

val SetDueDateDialog.singleDayText: EditText get() = singleDayTextLayout.editText!!

Expand Down