diff --git a/app/src/main/java/org/oppia/app/player/audio/AudioFragment.kt b/app/src/main/java/org/oppia/app/player/audio/AudioFragment.kt index 57328e23272..48308e90629 100755 --- a/app/src/main/java/org/oppia/app/player/audio/AudioFragment.kt +++ b/app/src/main/java/org/oppia/app/player/audio/AudioFragment.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import org.oppia.app.fragment.InjectableFragment +import org.oppia.app.player.state.listener.AudioContentIdListener import javax.inject.Inject private const val KEY_EXPLORATION_ID = "EXPLORATION_ID" @@ -75,4 +76,8 @@ class AudioFragment : InjectableFragment(), LanguageInterface { override fun onLanguageSelected(currentLanguageCode: String) { audioFragmentPresenter.languageSelected(currentLanguageCode) } + + fun setContentIdListener(audioContentIdListener: AudioContentIdListener) { + audioFragmentPresenter.setContentIdListener(audioContentIdListener) + } } diff --git a/app/src/main/java/org/oppia/app/player/audio/AudioFragmentPresenter.kt b/app/src/main/java/org/oppia/app/player/audio/AudioFragmentPresenter.kt index aa9aded8043..44e2ab5faf6 100755 --- a/app/src/main/java/org/oppia/app/player/audio/AudioFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/app/player/audio/AudioFragmentPresenter.kt @@ -14,6 +14,7 @@ import org.oppia.app.fragment.FragmentScope import org.oppia.app.model.Exploration import org.oppia.app.model.State import org.oppia.app.model.VoiceoverMapping +import org.oppia.app.player.state.listener.AudioContentIdListener import org.oppia.app.viewmodel.ViewModelProvider import org.oppia.domain.exploration.ExplorationDataController import org.oppia.util.data.AsyncResult @@ -40,9 +41,15 @@ class AudioFragmentPresenter @Inject constructor( private var languages = listOf() /** Sets up SeekBar listener, ViewModel, and gets VoiceoverMappings or restores saved state */ - fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, explorationId: String, stateId: String): View? { + fun handleCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + explorationId: String, + stateId: String + ): View? { val binding = AudioFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false) - binding.sbAudioProgress.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener { + binding.sbAudioProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { if (fromUser) { userProgress = progress @@ -131,11 +138,13 @@ class AudioFragmentPresenter @Inject constructor( processExplorationLiveData(explorationResultLiveData).observe(fragment, Observer { val state = it.statesMap[stateId] ?: State.getDefaultInstance() val contentId = state.content.contentId - val voiceoverMapping = (state.recordedVoiceoversMap[contentId] ?: VoiceoverMapping.getDefaultInstance()).voiceoverMappingMap + val voiceoverMapping = + (state.recordedVoiceoversMap[contentId] ?: VoiceoverMapping.getDefaultInstance()).voiceoverMappingMap languages = voiceoverMapping.keys.toList() selectedLanguageCode = languages.firstOrNull() ?: "" val viewModel = getAudioViewModel() viewModel.setVoiceoverMappings(voiceoverMapping) + viewModel.setContentId(contentId) viewModel.setAudioLanguageCode(selectedLanguageCode) }) } @@ -154,5 +163,9 @@ class AudioFragmentPresenter @Inject constructor( } return explorationResult.getOrDefault(Exploration.getDefaultInstance()) } + + fun setContentIdListener(audioContentIdListener: AudioContentIdListener) { + getAudioViewModel().setContentIdListener(audioContentIdListener) + } } diff --git a/app/src/main/java/org/oppia/app/player/audio/AudioViewModel.kt b/app/src/main/java/org/oppia/app/player/audio/AudioViewModel.kt index 62a039983da..d137b0a109e 100644 --- a/app/src/main/java/org/oppia/app/player/audio/AudioViewModel.kt +++ b/app/src/main/java/org/oppia/app/player/audio/AudioViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModel import org.oppia.app.fragment.FragmentScope import org.oppia.app.model.Voiceover +import org.oppia.app.player.state.listener.AudioContentIdListener import org.oppia.domain.audio.AudioPlayerController import org.oppia.domain.audio.AudioPlayerController.PlayProgress import org.oppia.domain.audio.AudioPlayerController.PlayStatus @@ -20,9 +21,12 @@ class AudioViewModel @Inject constructor( @DefaultResource private val gcsResource: String ) : ViewModel() { + private lateinit var contentId: String private lateinit var explorationId: String private var voiceoverMap = mapOf() + private var audioContentIdListener: AudioContentIdListener? = null + /** Mirrors PlayStatus in AudioPlayerController except adds LOADING state */ enum class UiAudioPlayStatus { LOADING, @@ -44,14 +48,22 @@ class AudioViewModel @Inject constructor( processPlayStatusLiveData() } - fun setVoiceoverMappings(map : Map) { + fun setVoiceoverMappings(map: Map) { voiceoverMap = map } + fun setContentId(id: String) { + contentId = id + } + fun setExplorationId(id: String) { explorationId = id } + fun setContentIdListener(audioContentIdListener: AudioContentIdListener) { + this.audioContentIdListener = audioContentIdListener + } + /** Sets language code for data binding and changes data source to correct audio */ fun setAudioLanguageCode(languageCode: String) { currentLanguageCode.set(languageCode) @@ -62,8 +74,14 @@ class AudioViewModel @Inject constructor( fun togglePlayPause(type: UiAudioPlayStatus?) { if (type == UiAudioPlayStatus.PLAYING) { audioPlayerController.pause() + if (audioContentIdListener != null) { + audioContentIdListener!!.contentIdForCurrentAudio(contentId, isPlaying = false) + } } else { audioPlayerController.play() + if (audioContentIdListener != null) { + audioContentIdListener!!.contentIdForCurrentAudio(contentId, isPlaying = true) + } } } diff --git a/app/src/main/java/org/oppia/app/player/state/StateFragment.kt b/app/src/main/java/org/oppia/app/player/state/StateFragment.kt index 1494bd02d97..54d80140903 100755 --- a/app/src/main/java/org/oppia/app/player/state/StateFragment.kt +++ b/app/src/main/java/org/oppia/app/player/state/StateFragment.kt @@ -9,10 +9,11 @@ import org.oppia.app.fragment.InjectableFragment import org.oppia.app.model.UserAnswer import org.oppia.app.player.audio.CellularDataInterface import org.oppia.app.player.state.answerhandling.InteractionAnswerReceiver +import org.oppia.app.player.state.listener.AudioContentIdListener import javax.inject.Inject /** Fragment that represents the current state of an exploration. */ -class StateFragment : InjectableFragment(), CellularDataInterface, InteractionAnswerReceiver { +class StateFragment : InjectableFragment(), CellularDataInterface, InteractionAnswerReceiver, AudioContentIdListener { companion object { /** * Creates a new instance of a StateFragment. @@ -55,4 +56,8 @@ class StateFragment : InjectableFragment(), CellularDataInterface, InteractionAn fun handlePlayAudio() = stateFragmentPresenter.handleAudioClick() fun handleKeyboardAction() = stateFragmentPresenter.handleKeyboardAction() + + override fun contentIdForCurrentAudio(contentId: String, isPlaying: Boolean) { + stateFragmentPresenter.handleContentCardHighlighting(contentId, isPlaying) + } } diff --git a/app/src/main/java/org/oppia/app/player/state/StateFragmentPresenter.kt b/app/src/main/java/org/oppia/app/player/state/StateFragmentPresenter.kt index c9d351ac945..6df6de8d38c 100755 --- a/app/src/main/java/org/oppia/app/player/state/StateFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/app/player/state/StateFragmentPresenter.kt @@ -1,11 +1,7 @@ package org.oppia.app.player.state -import android.animation.Animator -import android.animation.AnimatorListenerAdapter import android.content.Context import android.os.Handler -import android.util.Log -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -13,7 +9,6 @@ import android.view.animation.AccelerateInterpolator import android.view.animation.AlphaAnimation import android.view.animation.AnimationSet import android.view.animation.DecelerateInterpolator -import android.view.animation.TranslateAnimation import android.view.inputmethod.InputMethodManager import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil @@ -60,6 +55,7 @@ import org.oppia.app.player.state.itemviewmodel.StateNavigationButtonViewModel import org.oppia.app.player.state.itemviewmodel.StateNavigationButtonViewModel.ContinuationNavigationButtonType import org.oppia.app.player.state.itemviewmodel.SubmittedAnswerViewModel import org.oppia.app.player.state.itemviewmodel.TextInputViewModel +import org.oppia.app.player.state.listener.AudioContentIdListener import org.oppia.app.player.state.listener.PreviousResponsesHeaderClickListener import org.oppia.app.player.state.listener.StateNavigationButtonListener import org.oppia.app.recyclerview.BindableAdapter @@ -100,6 +96,7 @@ class StateFragmentPresenter @Inject constructor( private lateinit var binding: StateFragmentBinding private lateinit var recyclerViewAdapter: RecyclerView.Adapter<*> private lateinit var viewModel: StateViewModel + private var contentViewModel: ContentViewModel? = null private val ephemeralStateLiveData: LiveData> by lazy { explorationProgressController.getCurrentState() } @@ -152,7 +149,11 @@ class StateFragmentPresenter @Inject constructor( oldBottom: Int ) { if (bottom < oldBottom) { - binding.stateRecyclerView.postDelayed(Runnable { binding.stateRecyclerView.scrollToPosition(stateRecyclerViewAdapter.getItemCount()-1) }, 100) + binding.stateRecyclerView.postDelayed(Runnable { + binding.stateRecyclerView.scrollToPosition( + stateRecyclerViewAdapter.getItemCount() - 1 + ) + }, 100) } } }) @@ -181,6 +182,7 @@ class StateFragmentPresenter @Inject constructor( htmlParserFactory.create(entityType, explorationId, /* imageCenterAlign= */ true).parseOppiaHtml( (viewModel as ContentViewModel).htmlContent.toString(), binding.contentTextView ) + binding.viewModel = contentViewModel!! } ) .registerViewBinder( @@ -319,9 +321,11 @@ class StateFragmentPresenter @Inject constructor( if (currentYOffset == 0) { binding.stateRecyclerView.smoothScrollToPosition(0) } + (getAudioFragment() as AudioFragment).setContentIdListener(fragment as AudioContentIdListener) } else { if (getAudioFragment() != null) { fragment.childFragmentManager.beginTransaction().remove(getAudioFragment()!!).commitNow() + handleContentCardHighlighting(contentViewModel!!.contentId,false) } } } @@ -397,8 +401,8 @@ class StateFragmentPresenter @Inject constructor( answerOutcomeLiveData.observe(fragment, Observer { result -> // If the answer was submitted on behalf of the Continue interaction, automatically continue to the next state. if (result.state.interaction.id == "Continue") { - moveToNextState() - }else if (result.labelledAsCorrectAnswer){ + moveToNextState() + } else if (result.labelledAsCorrectAnswer) { showCongratulationMessageOnCorrectAnswer() } }) @@ -424,7 +428,7 @@ class StateFragmentPresenter @Inject constructor( Handler().postDelayed({ binding.congratulationTextview.clearAnimation() binding.congratulationTextview.visibility = View.INVISIBLE - },2000) + }, 2000) } /** Helper for subscribeToAnswerOutcome. */ @@ -452,8 +456,8 @@ class StateFragmentPresenter @Inject constructor( } fun handleKeyboardAction() { - hideKeyboard() - handleSubmitAnswer(viewModel.getPendingAnswer()) + hideKeyboard() + handleSubmitAnswer(viewModel.getPendingAnswer()) } override fun onContinueButtonClicked() { @@ -492,7 +496,8 @@ class StateFragmentPresenter @Inject constructor( private fun addContentItem(pendingItemList: MutableList, ephemeralState: EphemeralState) { val contentSubtitledHtml: SubtitledHtml = ephemeralState.state.content - pendingItemList += ContentViewModel(contentSubtitledHtml.html) + contentViewModel = ContentViewModel(contentSubtitledHtml.contentId, contentSubtitledHtml.html) + pendingItemList += contentViewModel!! } private fun addPreviousAnswers( @@ -607,4 +612,10 @@ class StateFragmentPresenter @Inject constructor( val inputManager: InputMethodManager = activity.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputManager.hideSoftInputFromWindow(fragment.view!!.windowToken, InputMethodManager.SHOW_FORCED) } + + fun handleContentCardHighlighting(contentId: String, playing: Boolean) { + if(contentViewModel!=null && contentViewModel!!.contentId == contentId){ + contentViewModel!!.updateIsAudioPlaying(playing) + } + } } diff --git a/app/src/main/java/org/oppia/app/player/state/itemviewmodel/ContentViewModel.kt b/app/src/main/java/org/oppia/app/player/state/itemviewmodel/ContentViewModel.kt index b6814400c46..229a11ef61d 100644 --- a/app/src/main/java/org/oppia/app/player/state/itemviewmodel/ContentViewModel.kt +++ b/app/src/main/java/org/oppia/app/player/state/itemviewmodel/ContentViewModel.kt @@ -1,4 +1,13 @@ package org.oppia.app.player.state.itemviewmodel +import androidx.databinding.ObservableField + /** [ViewModel] for content-card state. */ -class ContentViewModel(val htmlContent: CharSequence) : StateItemViewModel(ViewType.CONTENT) +class ContentViewModel(val contentId: String, val htmlContent: CharSequence) : + StateItemViewModel(ViewType.CONTENT){ + val isAudioPlaying = ObservableField(false) + + fun updateIsAudioPlaying(isPlaying: Boolean){ + isAudioPlaying.set(isPlaying) + } +} diff --git a/app/src/main/java/org/oppia/app/player/state/listener/AudioContentIdListener.kt b/app/src/main/java/org/oppia/app/player/state/listener/AudioContentIdListener.kt new file mode 100644 index 00000000000..fecde933901 --- /dev/null +++ b/app/src/main/java/org/oppia/app/player/state/listener/AudioContentIdListener.kt @@ -0,0 +1,6 @@ +package org.oppia.app.player.state.listener + +/** Listener when audio is played/paused to highlight the content-card. */ +interface AudioContentIdListener { + fun contentIdForCurrentAudio(contentId: String, isPlaying: Boolean) +} diff --git a/app/src/main/res/drawable/content_yellow_background.xml b/app/src/main/res/drawable/content_yellow_background.xml new file mode 100644 index 00000000000..f5573cd3b87 --- /dev/null +++ b/app/src/main/res/drawable/content_yellow_background.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/layout/content_item.xml b/app/src/main/res/layout/content_item.xml index 5f7e11cb911..f69e52cbd89 100644 --- a/app/src/main/res/layout/content_item.xml +++ b/app/src/main/res/layout/content_item.xml @@ -4,6 +4,10 @@ + + @@ -14,9 +18,9 @@ android:layout_height="wrap_content" android:layout_marginStart="20dp" android:layout_marginTop="@dimen/divider_margin_top" - android:layout_marginBottom="@dimen/divider_margin_bottom" android:layout_marginEnd="28dp" - android:background="@drawable/content_blue_background"> + android:layout_marginBottom="@dimen/divider_margin_bottom" + android:background="@{viewModel.isAudioPlaying.get()? @drawable/content_yellow_background : @drawable/content_blue_background}"> #61999999 #DDDDDD #80707070 + #F2D140 #FFFFFF #CCFFFFFF