Skip to content

Commit 9834b90

Browse files
committed
feat(new study screen): Check pronunciation
1 parent 902f86f commit 9834b90

File tree

9 files changed

+73
-4
lines changed

9 files changed

+73
-4
lines changed

AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/LayoutValidationTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ class LayoutValidationTest : InstrumentedTest() {
104104
val ignoredLayoutIds =
105105
listOf(
106106
com.ichi2.anki.R.layout.introduction_activity,
107+
com.ichi2.anki.R.layout.reviewer2,
108+
com.ichi2.anki.R.layout.preferences,
107109
) +
108110
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
109111
listOf(com.ichi2.anki.R.layout.widget_small_unthemed)

AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/ViewerAction.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ enum class ViewerAction(
7070
TAG(R.id.action_edit_tags, R.drawable.ic_tag, R.string.menu_edit_tags, DISABLED),
7171
RESCHEDULE_NOTE(R.id.action_set_due_date, R.drawable.ic_reschedule, titleRes = R.string.empty_string, DISABLED),
7272
TOGGLE_AUTO_ADVANCE(R.id.action_toggle_auto_advance, R.drawable.ic_fast_forward, R.string.toggle_auto_advance, DISABLED),
73+
RECORD_VOICE(R.id.action_record_voice, R.drawable.ic_action_mic, R.string.record_voice, DISABLED),
7374
USER_ACTION_1(R.id.user_action_1, R.drawable.user_action_1, R.string.user_action_1, DISABLED),
7475
USER_ACTION_2(R.id.user_action_2, R.drawable.user_action_2, R.string.user_action_2, DISABLED),
7576
USER_ACTION_3(R.id.user_action_3, R.drawable.user_action_3, R.string.user_action_3, DISABLED),
@@ -109,6 +110,7 @@ enum class ViewerAction(
109110
TOGGLE_FLAG_PURPLE,
110111
SHOW_HINT,
111112
SHOW_ALL_HINTS,
113+
REPLAY_VOICE,
112114
EXIT,
113115
;
114116

@@ -133,6 +135,8 @@ enum class ViewerAction(
133135
TOGGLE_AUTO_ADVANCE -> listOf(keycode(KeyEvent.KEYCODE_A, shift()))
134136
SHOW_HINT -> listOf(keycode(KeyEvent.KEYCODE_H))
135137
SHOW_ALL_HINTS -> listOf(keycode(KeyEvent.KEYCODE_G))
138+
RECORD_VOICE -> listOf(keycode(KeyEvent.KEYCODE_V, shift()))
139+
REPLAY_VOICE -> listOf(keycode(KeyEvent.KEYCODE_V))
136140
TOGGLE_FLAG_RED ->
137141
listOf(
138142
keycode(KeyEvent.KEYCODE_1, ctrl()),

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import androidx.core.view.isVisible
4848
import androidx.core.view.updateLayoutParams
4949
import androidx.core.view.updatePadding
5050
import androidx.core.widget.addTextChangedListener
51+
import androidx.fragment.app.FragmentContainerView
5152
import androidx.fragment.app.viewModels
5253
import androidx.lifecycle.Lifecycle
5354
import androidx.lifecycle.flowWithLifecycle
@@ -188,6 +189,7 @@ class ReviewerFragment :
188189
setupToolbarPosition(view)
189190
setupAnswerTimer(view)
190191
setupMargins(view)
192+
setupCheckPronunciation(view)
191193
setupTimebox()
192194

193195
viewModel.actionFeedbackFlow
@@ -595,6 +597,13 @@ class ReviewerFragment :
595597
}
596598
}
597599

600+
private fun setupCheckPronunciation(view: View) {
601+
val container = view.findViewById<FragmentContainerView>(R.id.check_pronunciation_container)
602+
viewModel.voiceRecorderEnabledFlow.flowWithLifecycle(lifecycle).collectIn(lifecycleScope) { isEnabled ->
603+
container.isVisible = isEnabled
604+
}
605+
}
606+
598607
private fun setupTimebox() {
599608
viewModel.timeBoxReachedFlow.flowWithLifecycle(lifecycle).collectIn(lifecycleScope) { timebox ->
600609
Timber.i("ReviewerFragment: Timebox reached (reps %d - secs %d)", timebox.reps, timebox.secs)

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class ReviewerViewModel :
9999
val setDueDateFlow = MutableSharedFlow<CardId>()
100100
val answerTimerStatusFlow = MutableStateFlow<AnswerTimerStatus?>(null)
101101
val answerFeedbackFlow = MutableSharedFlow<Rating>()
102+
val voiceRecorderEnabledFlow = MutableStateFlow(false)
103+
val replayVoiceFlow = MutableSharedFlow<Unit>()
102104
val timeBoxReachedFlow = MutableSharedFlow<Collection.TimeboxReached>()
103105

104106
override val server: AnkiServer = AnkiServer(this, StudyScreenRepository.getServerPort()).also { it.start() }
@@ -656,6 +658,8 @@ class ReviewerViewModel :
656658
ViewerAction.FLIP_OR_ANSWER_EASE4 -> flipOrAnswer(Rating.EASY)
657659
ViewerAction.SHOW_HINT -> eval.emit("ankidroid.showHint()")
658660
ViewerAction.SHOW_ALL_HINTS -> eval.emit("ankidroid.showAllHints()")
661+
ViewerAction.RECORD_VOICE -> voiceRecorderEnabledFlow.emit(!voiceRecorderEnabledFlow.value)
662+
ViewerAction.REPLAY_VOICE -> replayVoiceFlow.emit(Unit)
659663
ViewerAction.EXIT -> finishResultFlow.emit(AbstractFlashcardViewer.RESULT_DEFAULT)
660664
ViewerAction.USER_ACTION_1 -> userAction(1)
661665
ViewerAction.USER_ACTION_2 -> userAction(2)

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioRecordView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ class AudioRecordView : ConstraintLayout {
175175

176176
override fun onLongPress(e: MotionEvent) {
177177
if (!Permissions.canRecordAudio(context)) return
178-
CompatHelper.compat.vibrate(context, 20.milliseconds)
178+
CompatHelper.compat.vibrate(context, 50.milliseconds)
179179
showCancelAndLockSliders()
180180
firstX = e.rawX
181181
firstY = e.rawY

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/CheckPronunciationFragment.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,17 @@ import androidx.fragment.app.viewModels
2929
import androidx.lifecycle.flowWithLifecycle
3030
import androidx.lifecycle.lifecycleScope
3131
import com.ichi2.anki.R
32+
import com.ichi2.anki.ui.windows.reviewer.ReviewerViewModel
3233
import com.ichi2.anki.utils.ext.collectIn
3334
import com.ichi2.utils.show
35+
import kotlinx.coroutines.launch
3436

3537
/**
3638
* Integrates [AudioRecordView] with [AudioPlayView] to play the recorded audios.
3739
*/
3840
class CheckPronunciationFragment : Fragment(R.layout.check_pronunciation_fragment) {
3941
private val viewModel: CheckPronunciationViewModel by viewModels()
42+
private val studyScreenViewModel: ReviewerViewModel by viewModels({ requireParentFragment() })
4043

4144
private lateinit var playView: AudioPlayView
4245
private lateinit var recordView: AudioRecordView
@@ -68,6 +71,7 @@ class CheckPronunciationFragment : Fragment(R.layout.check_pronunciation_fragmen
6871

6972
setupViewListeners()
7073
observeViewModel()
74+
observeStudyScreenViewModel()
7175
}
7276

7377
override fun onPause() {
@@ -116,6 +120,7 @@ class CheckPronunciationFragment : Fragment(R.layout.check_pronunciation_fragmen
116120
private fun observeViewModel() {
117121
viewModel.isPlaybackVisibleFlow.flowWithLifecycle(lifecycle).collectIn(lifecycleScope) { isVisible ->
118122
playView.isVisible = isVisible
123+
recordView.setRecordDisplayVisibility(!isVisible)
119124
}
120125
viewModel.playbackProgressFlow
121126
.flowWithLifecycle(lifecycle)
@@ -134,4 +139,25 @@ class CheckPronunciationFragment : Fragment(R.layout.check_pronunciation_fragmen
134139
playView.rotateReplayIcon()
135140
}
136141
}
142+
143+
private fun observeStudyScreenViewModel() {
144+
studyScreenViewModel.voiceRecorderEnabledFlow
145+
.flowWithLifecycle(lifecycle)
146+
.collectIn(lifecycleScope) { isEnabled ->
147+
if (!isEnabled) {
148+
viewModel.resetAll()
149+
recordView.forceReset()
150+
}
151+
}
152+
studyScreenViewModel.replayVoiceFlow
153+
.flowWithLifecycle(lifecycle)
154+
.collectIn(lifecycleScope) {
155+
viewModel.onPlayOrReplay()
156+
}
157+
studyScreenViewModel.onShowQuestionFlow.flowWithLifecycle(lifecycle).collectIn(lifecycleScope) { showingAnswer ->
158+
playView.isVisible = false
159+
viewModel.onCancelPlayback()
160+
recordView.setRecordDisplayVisibility(true)
161+
}
162+
}
137163
}

AnkiDroid/src/main/res/layout-sw600dp/reviewer2.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
<LinearLayout
1515
android:layout_width="match_parent"
1616
android:layout_height="match_parent"
17-
android:orientation="vertical">
17+
android:orientation="vertical"
18+
android:clipChildren="false">
1819

1920
<com.google.android.material.card.MaterialCardView
2021
android:id="@+id/webview_container"
@@ -101,6 +102,17 @@
101102
</com.google.android.material.card.MaterialCardView>
102103

103104

105+
<androidx.fragment.app.FragmentContainerView
106+
android:id="@+id/check_pronunciation_container"
107+
android:name="com.ichi2.anki.ui.windows.reviewer.audiorecord.CheckPronunciationFragment"
108+
android:layout_width="match_parent"
109+
android:layout_height="56dp"
110+
android:clipChildren="false"
111+
android:layout_marginHorizontal="@dimen/reviewer_side_margin"
112+
android:layout_gravity="center_horizontal"
113+
android:elevation="2dp"
114+
android:visibility="gone"/>
115+
104116
<androidx.constraintlayout.widget.ConstraintLayout
105117
android:id="@+id/tools_layout"
106118
android:layout_width="match_parent"

AnkiDroid/src/main/res/layout/reviewer2.xml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
android:focusableInTouchMode="true"
1212
tools:context=".ui.windows.reviewer.ReviewerFragment">
1313

14-
1514
<LinearLayout
1615
android:id="@+id/main_layout"
1716
android:layout_width="match_parent"
1817
android:layout_height="match_parent"
19-
android:orientation="vertical">
18+
android:orientation="vertical"
19+
android:clipChildren="false">
2020

2121
<androidx.constraintlayout.widget.ConstraintLayout
2222
android:id="@+id/tools_layout"
@@ -191,6 +191,17 @@
191191
</com.google.android.material.textfield.TextInputLayout>
192192
</com.google.android.material.card.MaterialCardView>
193193

194+
<androidx.fragment.app.FragmentContainerView
195+
android:id="@+id/check_pronunciation_container"
196+
android:name="com.ichi2.anki.ui.windows.reviewer.audiorecord.CheckPronunciationFragment"
197+
android:layout_width="match_parent"
198+
android:layout_height="56dp"
199+
android:clipChildren="false"
200+
android:layout_marginHorizontal="@dimen/reviewer_side_margin"
201+
android:layout_gravity="center_horizontal"
202+
android:elevation="2dp"
203+
android:visibility="gone"/>
204+
194205
<FrameLayout
195206
android:id="@+id/answer_area"
196207
android:layout_width="match_parent"

AnkiDroid/src/main/res/values/ids.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<item type="id" name="action_suspend_card"/>
4040
<item type="id" name="action_suspend_note"/>
4141
<item type="id" name="action_toggle_auto_advance"/>
42+
<item type="id" name="action_record_voice"/>
4243
<item type="id" name="action_set_due_date"/>
4344

4445
<item type="id" name="user_action_1"/>

0 commit comments

Comments
 (0)