Skip to content

Commit d7623d0

Browse files
committed
Implement the skip step behaviour
1 parent fdef530 commit d7623d0

17 files changed

Lines changed: 484 additions & 71 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="24dp"
3+
android:height="24dp"
4+
android:viewportWidth="24"
5+
android:viewportHeight="24">
6+
<path
7+
android:fillColor="#FFF"
8+
android:pathData="M12 14a2 2 0 0 1 2 2 2 2 0 0 1-2 2 2 2 0 0 1-2-2 2 2 0 0 1 2-2m11.46-5.14l-1.59 6.89L15 14.16l3.8-2.38C17.39 9.5 14.87 8 12 8c-3.95 0-7.23 2.86-7.88 6.63l-1.97-0.35C2.96 9.58 7.06 6 12 6c3.58 0 6.73 1.89 8.5 4.72l2.96-1.86z" />
9+
</vector>

app-base/src/main/res/values/strings.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,12 @@
280280

281281
<string name="image_pick">Pick an image</string>
282282

283+
<string name="behaviour_skip">Skip</string>
284+
<string name="behaviour_skip_help">Skip this step in some loops</string>
285+
<string name="behaviour_skip_first">In the first loop</string>
286+
<string name="behaviour_skip_last">In the last loop</string>
287+
<string name="behaviour_skip_loops">Loops</string>
288+
283289
<!--Sample timers strings-->
284290
<string name="sample_timer_template_title">Timer Template</string>
285291
<string name="sample_timer_template_prepare">Prepare</string>

app-timer-edit/src/main/java/xyz/aprildown/timer/app/timer/edit/BehaviourSettingsView.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import xyz.aprildown.timer.app.base.data.PreferenceData.useVoiceContent2
1717
import xyz.aprildown.timer.app.base.media.getMediaDuration
1818
import xyz.aprildown.timer.app.timer.edit.media.BeepDialog
1919
import xyz.aprildown.timer.app.timer.edit.media.HalfDialog
20+
import xyz.aprildown.timer.app.timer.edit.media.SkipDialog
2021
import xyz.aprildown.timer.app.timer.edit.media.VibrationDialog
2122
import xyz.aprildown.timer.app.timer.edit.media.VoiceDialog
2223
import xyz.aprildown.timer.app.timer.edit.voice.VoiceVariableDialog
@@ -29,6 +30,7 @@ import xyz.aprildown.timer.domain.entities.HalfAction
2930
import xyz.aprildown.timer.domain.entities.MusicAction
3031
import xyz.aprildown.timer.domain.entities.NotificationAction
3132
import xyz.aprildown.timer.domain.entities.ScreenAction
33+
import xyz.aprildown.timer.domain.entities.SkipAction
3234
import xyz.aprildown.timer.domain.entities.VibrationAction
3335
import xyz.aprildown.timer.domain.entities.VoiceAction
3436
import xyz.aprildown.timer.domain.utils.Constants
@@ -333,3 +335,37 @@ internal fun MaterialPopupMenuBuilder.addImageItems(
333335
}
334336
}
335337
}
338+
339+
internal fun MaterialPopupMenuBuilder.addSkipItems(
340+
context: Context,
341+
action: SkipAction,
342+
onLoopsChange: (SkipAction.Target) -> Unit,
343+
) {
344+
section {
345+
item {
346+
label = buildString {
347+
append(context.getString(RBase.string.behaviour_skip_loops))
348+
append(": ")
349+
append(
350+
when (val target = action.target) {
351+
SkipAction.Target.First -> {
352+
context.getString(RBase.string.behaviour_skip_first)
353+
}
354+
SkipAction.Target.Last -> {
355+
context.getString(RBase.string.behaviour_skip_last)
356+
}
357+
is SkipAction.Target.Loops -> {
358+
target.loopNumbers.joinToString()
359+
}
360+
}
361+
)
362+
}
363+
callback = {
364+
SkipDialog(context).showTargetDialog(
365+
oldTarget = action.target,
366+
func = onLoopsChange,
367+
)
368+
}
369+
}
370+
}
371+
}

app-timer-edit/src/main/java/xyz/aprildown/timer/app/timer/edit/EditActivity.kt

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import xyz.aprildown.timer.domain.entities.toImageAction
6868
import xyz.aprildown.timer.domain.entities.toMusicAction
6969
import xyz.aprildown.timer.domain.entities.toNotificationAction
7070
import xyz.aprildown.timer.domain.entities.toScreenAction
71+
import xyz.aprildown.timer.domain.entities.toSkipAction
7172
import xyz.aprildown.timer.domain.entities.toVibrationAction
7273
import xyz.aprildown.timer.domain.entities.toVoiceAction
7374
import xyz.aprildown.timer.domain.usecases.Fruit
@@ -811,13 +812,28 @@ class EditActivity :
811812
onPick = { onImageAdd(position) },
812813
)
813814
}
815+
BehaviourType.SKIP -> {
816+
addSkipItems(
817+
context = this@EditActivity,
818+
action = current.toSkipAction(),
819+
onLoopsChange = { target ->
820+
changeBehaviour(BehaviourType.SKIP, position) {
821+
it.toSkipAction().copy(target = target).toBehaviourEntity()
822+
}
823+
postUpdateTotalTime()
824+
},
825+
)
826+
}
814827
else -> Unit
815828
}
816829
section {
817830
item {
818831
label = getString(RBase.string.delete)
819832
icon = RBase.drawable.ic_delete
820-
callback = { layout.removeBehaviour(type) }
833+
callback = {
834+
layout.removeBehaviour(type)
835+
postUpdateTotalTime()
836+
}
821837
}
822838
}
823839
}.show(this, view)
@@ -881,6 +897,12 @@ class EditActivity :
881897
}
882898
}
883899

900+
override fun onBehaviourAdded(type: BehaviourType) {
901+
if (type == BehaviourType.SKIP) {
902+
postUpdateTotalTime()
903+
}
904+
}
905+
884906
override fun onImageAdd(position: Int) {
885907
viewModel.imagePosition = position
886908
pickImageLauncher.launch(
@@ -905,7 +927,7 @@ class EditActivity :
905927
Runnable {
906928
val internalSteps = getStepEntityFromFastAdapter(
907929
doOnGroup = { group, steps ->
908-
group.totalTime = steps.accumulateTime() * group.loop
930+
group.totalTime = steps.accumulateTime(loop = group.loop)
909931
fastAdapter.notifyItemChanged(
910932
fastAdapter.getPosition(group),
911933
EditableGroup.TotalTimeChanged
@@ -915,9 +937,11 @@ class EditActivity :
915937
val startStep = startAdapter.getSingleStepFromAdapter()
916938
val endStep = endAdapter.getSingleStepFromAdapter()
917939
binding.viewEditStepInfo.setDuration(
918-
(startStep?.length ?: 0) +
919-
internalSteps.accumulateTime() * (viewModel.loop.value ?: 1) +
920-
(endStep?.length ?: 0)
940+
internalSteps.accumulateTime(
941+
loop = viewModel.loop.value ?: 1,
942+
start = startStep,
943+
end = endStep,
944+
)
921945
)
922946
}
923947
}

app-timer-edit/src/main/java/xyz/aprildown/timer/app/timer/edit/EditableStep.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import xyz.aprildown.timer.app.base.utils.setTime
2222
import xyz.aprildown.timer.component.key.RoundTextView
2323
import xyz.aprildown.timer.component.key.behaviour.EditableBehaviourLayout
2424
import xyz.aprildown.timer.domain.entities.BehaviourEntity
25+
import xyz.aprildown.timer.domain.entities.BehaviourType
2526
import xyz.aprildown.timer.domain.entities.ImageAction
2627
import xyz.aprildown.timer.domain.entities.StepType
2728
import xyz.aprildown.timer.app.base.R as RBase
@@ -59,6 +60,8 @@ class EditableStep(
5960
position: Int
6061
)
6162

63+
fun onBehaviourAdded(type: BehaviourType)
64+
6265
fun onImageAdd(position: Int)
6366
fun onImageCheck(position: Int, action: ImageAction)
6467

@@ -127,6 +130,10 @@ class EditableStep(
127130
handler.onImageAdd(bindingAdapterPosition)
128131
}
129132

133+
override fun onBehaviourAdded(type: BehaviourType) {
134+
handler.onBehaviourAdded(type)
135+
}
136+
130137
override fun onImageContentClick(action: ImageAction) {
131138
handler.onImageCheck(bindingAdapterPosition, action)
132139
}

app-timer-edit/src/main/java/xyz/aprildown/timer/app/timer/edit/UpdateStepDialog.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import xyz.aprildown.timer.domain.entities.toImageAction
3535
import xyz.aprildown.timer.domain.entities.toMusicAction
3636
import xyz.aprildown.timer.domain.entities.toNotificationAction
3737
import xyz.aprildown.timer.domain.entities.toScreenAction
38+
import xyz.aprildown.timer.domain.entities.toSkipAction
3839
import xyz.aprildown.timer.domain.entities.toVibrationAction
3940
import xyz.aprildown.timer.domain.entities.toVoiceAction
4041
import xyz.aprildown.timer.domain.utils.AppTracker
@@ -301,6 +302,17 @@ class UpdateStepDialog :
301302
BehaviourType.IMAGE -> {
302303
addImageItems(context = context, onPick = ::onImageAdding)
303304
}
305+
BehaviourType.SKIP -> {
306+
addSkipItems(
307+
context = context,
308+
action = current.toSkipAction(),
309+
onLoopsChange = { target ->
310+
changeBehaviour(BehaviourType.SKIP) {
311+
it.toSkipAction().copy(target = target).toBehaviourEntity()
312+
}
313+
},
314+
)
315+
}
304316
else -> Unit
305317
}
306318
section {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package xyz.aprildown.timer.app.timer.edit.media
2+
3+
import android.content.Context
4+
import android.view.LayoutInflater
5+
import androidx.appcompat.app.AlertDialog
6+
import com.github.deweyreed.tools.helper.focusAndShowKeyboard
7+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
8+
import xyz.aprildown.timer.app.timer.edit.databinding.DialogSkipTargetBinding
9+
import xyz.aprildown.timer.domain.entities.SkipAction
10+
import xyz.aprildown.timer.app.base.R as RBase
11+
12+
internal class SkipDialog(private val context: Context) {
13+
fun showTargetDialog(oldTarget: SkipAction.Target, func: (SkipAction.Target) -> Unit) {
14+
val builder = MaterialAlertDialogBuilder(context)
15+
.setTitle(RBase.string.behaviour_skip_loops)
16+
.setPositiveButton(RBase.string.ok, null)
17+
.setNegativeButton(RBase.string.cancel, null)
18+
19+
val binding = DialogSkipTargetBinding.inflate(LayoutInflater.from(context))
20+
21+
when (oldTarget) {
22+
SkipAction.Target.First -> binding.radioFirst.isChecked = true
23+
SkipAction.Target.Last -> binding.radioLast.isChecked = true
24+
is SkipAction.Target.Loops -> {
25+
binding.radioLoops.isChecked = true
26+
binding.edit.setText(oldTarget.loopNumbers.joinToString())
27+
}
28+
}
29+
binding.radioFirst.setOnCheckedChangeListener { _, isChecked ->
30+
if (isChecked) {
31+
binding.radioLast.isChecked = false
32+
binding.radioLoops.isChecked = false
33+
}
34+
}
35+
binding.radioLast.setOnCheckedChangeListener { _, isChecked ->
36+
if (isChecked) {
37+
binding.radioFirst.isChecked = false
38+
binding.radioLoops.isChecked = false
39+
}
40+
}
41+
binding.radioLoops.setOnCheckedChangeListener { _, isChecked ->
42+
if (isChecked) {
43+
binding.radioFirst.isChecked = false
44+
binding.radioLast.isChecked = false
45+
binding.edit.focusAndShowKeyboard()
46+
}
47+
}
48+
binding.edit.setOnFocusChangeListener { _, hasFocus ->
49+
if (hasFocus) {
50+
binding.radioLoops.isChecked = true
51+
binding.edit.selectAll()
52+
}
53+
}
54+
55+
builder.setView(binding.root)
56+
57+
val dialog = builder.create()
58+
dialog.show()
59+
60+
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
61+
val newTarget = when {
62+
binding.radioFirst.isChecked -> SkipAction.Target.First
63+
binding.radioLast.isChecked -> SkipAction.Target.Last
64+
binding.radioLoops.isChecked -> {
65+
val indices = binding.edit.text?.toString()?.split(Regex("\\D+"))
66+
?.mapNotNull { it.toIntOrNull() }
67+
?.map { it - 1 }
68+
?.toSet()
69+
if (indices.isNullOrEmpty()) {
70+
SkipAction.Target.Last
71+
} else {
72+
SkipAction.Target.Loops(loopIndices = indices)
73+
}
74+
}
75+
else -> SkipAction.Target.Last
76+
}
77+
dialog.dismiss()
78+
func.invoke(newTarget)
79+
}
80+
}
81+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
4+
android:layout_width="match_parent"
5+
android:layout_height="wrap_content"
6+
android:orientation="vertical"
7+
android:paddingTop="8dp">
8+
9+
<FrameLayout
10+
android:id="@+id/layoutFirst"
11+
android:layout_width="match_parent"
12+
android:layout_height="wrap_content"
13+
android:paddingStart="20dp"
14+
android:paddingEnd="20dp">
15+
16+
<RadioButton
17+
android:id="@+id/radioFirst"
18+
android:layout_width="match_parent"
19+
android:layout_height="?listPreferredItemHeightSmall"
20+
android:gravity="center_vertical"
21+
android:paddingStart="20dp"
22+
android:paddingEnd="0dp"
23+
android:text="@string/behaviour_skip_first"
24+
android:textAppearance="?android:textAppearanceMedium"
25+
android:textColor="?textColorAlertDialogListItem" />
26+
27+
</FrameLayout>
28+
29+
<FrameLayout
30+
android:id="@+id/layoutLast"
31+
android:layout_width="match_parent"
32+
android:layout_height="wrap_content"
33+
android:paddingStart="20dp"
34+
android:paddingEnd="20dp">
35+
36+
<RadioButton
37+
android:id="@+id/radioLast"
38+
android:layout_width="match_parent"
39+
android:layout_height="?listPreferredItemHeightSmall"
40+
android:gravity="center_vertical"
41+
android:paddingStart="20dp"
42+
android:paddingEnd="0dp"
43+
android:text="@string/behaviour_skip_last"
44+
android:textAppearance="?android:textAppearanceMedium"
45+
android:textColor="?textColorAlertDialogListItem" />
46+
47+
</FrameLayout>
48+
49+
<LinearLayout
50+
android:id="@+id/layoutLoops"
51+
android:layout_width="match_parent"
52+
android:layout_height="wrap_content"
53+
android:gravity="center_vertical"
54+
android:orientation="horizontal"
55+
android:paddingStart="20dp"
56+
android:paddingEnd="20dp">
57+
58+
<RadioButton
59+
android:id="@+id/radioLoops"
60+
android:layout_width="wrap_content"
61+
android:layout_height="?listPreferredItemHeightSmall"
62+
android:gravity="center_vertical"
63+
android:paddingStart="20dp"
64+
android:paddingEnd="0dp" />
65+
66+
<com.google.android.material.textfield.TextInputLayout
67+
android:id="@+id/textInput"
68+
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
69+
android:layout_width="match_parent"
70+
android:layout_height="wrap_content"
71+
android:hint="@string/behaviour_skip_loops">
72+
73+
<com.google.android.material.textfield.TextInputEditText
74+
android:id="@+id/edit"
75+
android:layout_width="match_parent"
76+
android:layout_height="wrap_content"
77+
android:hint="1 2 3"
78+
android:imeOptions="actionDone"
79+
android:importantForAutofill="no"
80+
android:inputType="number|text"
81+
android:selectAllOnFocus="true"
82+
tools:ignore="HardcodedText" />
83+
84+
</com.google.android.material.textfield.TextInputLayout>
85+
86+
</LinearLayout>
87+
88+
</LinearLayout>

0 commit comments

Comments
 (0)