Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/oppia/oppia-android into #…
Browse files Browse the repository at this point in the history
  • Loading branch information
XichengSpencer committed Jan 17, 2024
2 parents 2fc2da8 + bb69b22 commit a489412
Show file tree
Hide file tree
Showing 40 changed files with 1,583 additions and 804 deletions.
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,15 @@
<activity android:name=".app.testing.DrawableBindingAdaptersTestActivity" />
<activity android:name=".app.testing.ExplorationInjectionActivity" />
<activity android:name=".app.testing.ExplorationTestActivity" />
<activity android:name=".app.testing.FractionInputInteractionViewTestActivity" />
<activity
android:name=".app.testing.TestFontScaleConfigurationUtilActivity"
android:theme="@style/OppiaThemeWithoutActionBar" />
<activity android:name=".app.testing.HomeFragmentTestActivity" />
<activity android:name=".app.testing.HtmlParserTestActivity" />
<activity android:name=".app.testing.HomeTestActivity" />
<activity android:name=".app.testing.InputInteractionViewTestActivity" />
<activity android:name=".app.testing.RatioInputInteractionViewTestActivity" />
<activity android:name=".app.testing.ImageRegionSelectionTestActivity" />
<activity android:name=".app.testing.ImageViewBindingAdaptersTestActivity" />
<activity android:name=".app.testing.ListItemLeadingMarginSpanTestActivity" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import org.oppia.android.app.testing.DragDropTestActivity
import org.oppia.android.app.testing.DrawableBindingAdaptersTestActivity
import org.oppia.android.app.testing.ExplorationInjectionActivity
import org.oppia.android.app.testing.ExplorationTestActivity
import org.oppia.android.app.testing.FractionInputInteractionViewTestActivity
import org.oppia.android.app.testing.HomeFragmentTestActivity
import org.oppia.android.app.testing.HomeTestActivity
import org.oppia.android.app.testing.HtmlParserTestActivity
Expand All @@ -76,6 +77,7 @@ import org.oppia.android.app.testing.NavigationDrawerTestActivity
import org.oppia.android.app.testing.PoliciesFragmentTestActivity
import org.oppia.android.app.testing.ProfileChooserFragmentTestActivity
import org.oppia.android.app.testing.ProfileEditFragmentTestActivity
import org.oppia.android.app.testing.RatioInputInteractionViewTestActivity
import org.oppia.android.app.testing.SplashTestActivity
import org.oppia.android.app.testing.SpotlightFragmentTestActivity
import org.oppia.android.app.testing.StateAssemblerMarginBindingAdaptersTestActivity
Expand Down Expand Up @@ -139,6 +141,7 @@ interface ActivityComponentImpl :
fun inject(faqSingleActivity: FAQSingleActivity)
fun inject(forceNetworkTypeActivity: ForceNetworkTypeActivity)
fun inject(forceNetworkTypeTestActivity: ForceNetworkTypeTestActivity)
fun inject(fractionInputInteractionViewTestActivity: FractionInputInteractionViewTestActivity)
fun inject(helpActivity: HelpActivity)
fun inject(homeActivity: HomeActivity)
fun inject(homeFragmentTestActivity: HomeFragmentTestActivity)
Expand All @@ -147,6 +150,7 @@ interface ActivityComponentImpl :
fun inject(imageRegionSelectionTestActivity: ImageRegionSelectionTestActivity)
fun inject(imageViewBindingAdaptersTestActivity: ImageViewBindingAdaptersTestActivity)
fun inject(inputInteractionViewTestActivity: InputInteractionViewTestActivity)
fun inject(ratioInputInteractionViewTestActivity: RatioInputInteractionViewTestActivity)
fun inject(licenseListActivity: LicenseListActivity)
fun inject(licenseTextViewerActivity: LicenseTextViewerActivity)
fun inject(listItemLeadingMarginSpanTestActivity: ListItemLeadingMarginSpanTestActivity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ enum class FractionParsingUiError(@StringRes private var error: Int?) {
DIVISION_BY_ZERO(error = R.string.fraction_error_divide_by_zero),

/** Corresponds to [FractionParsingError.NUMBER_TOO_LONG]. */
NUMBER_TOO_LONG(error = R.string.fraction_error_larger_than_seven_digits);
NUMBER_TOO_LONG(error = R.string.fraction_error_larger_than_seven_digits),

/** Corresponds to [FractionParsingError.EMPTY_INPUT]. */
EMPTY_INPUT(error = R.string.fraction_error_empty_input);

/**
* Returns the string corresponding to this error's string resources, or null if there is none.
Expand All @@ -39,6 +42,7 @@ enum class FractionParsingUiError(@StringRes private var error: Int?) {
FractionParsingError.INVALID_FORMAT -> INVALID_FORMAT
FractionParsingError.DIVISION_BY_ZERO -> DIVISION_BY_ZERO
FractionParsingError.NUMBER_TOO_LONG -> NUMBER_TOO_LONG
FractionParsingError.EMPTY_INPUT -> EMPTY_INPUT
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class StringToRatioParser {
val normalized = text.normalizeWhitespace()
val ratio = parseRatioOrNull(normalized)
return when {
normalized.isBlank() -> RatioParsingError.EMPTY_INPUT
!normalized.matches(invalidRatioRegex) || ratio == null -> RatioParsingError.INVALID_FORMAT
numberOfTerms != 0 && ratio.ratioComponentCount != numberOfTerms -> {
RatioParsingError.INVALID_SIZE
Expand Down Expand Up @@ -77,7 +78,8 @@ class StringToRatioParser {
INVALID_FORMAT(error = R.string.ratio_error_invalid_format),
INVALID_COLONS(error = R.string.ratio_error_invalid_colons),
INVALID_SIZE(error = R.string.ratio_error_invalid_size),
INCLUDES_ZERO(error = R.string.ratio_error_includes_zero);
INCLUDES_ZERO(error = R.string.ratio_error_includes_zero),
EMPTY_INPUT(error = R.string.ratio_error_empty_input);

/**
* Returns the string corresponding to this error's string resources, or null if there is none.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,17 @@ class FractionInteractionViewModel private constructor(
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck(
pendingAnswerError,
answerText.isNotEmpty()
true // Allow submit on empty answer.
)
}
}
errorMessage.addOnPropertyChangedCallback(callback)
isAnswerAvailable.addOnPropertyChangedCallback(callback)
// Force-update the UI to reflect the state of the errorMessage and isAnswerAvailable property:
errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck(
/* pendingAnswerError= */null,
/* inputAnswerAvailable= */true
)
}

override fun getPendingAnswer(): UserAnswer = UserAnswer.newBuilder().apply {
Expand All @@ -64,23 +69,25 @@ class FractionInteractionViewModel private constructor(

/** It checks the pending error for the current fraction input, and correspondingly updates the error string based on the specified error category. */
override fun checkPendingAnswerError(category: AnswerErrorCategory): String? {
if (answerText.isNotEmpty()) {
when (category) {
AnswerErrorCategory.REAL_TIME -> {
when (category) {
AnswerErrorCategory.REAL_TIME -> {
if (answerText.isNotEmpty()) {
pendingAnswerError =
FractionParsingUiError.createFromParsingError(
fractionParser.getRealTimeAnswerError(answerText.toString())
).getErrorMessageFromStringRes(resourceHandler)
} else {
pendingAnswerError = null
}
AnswerErrorCategory.SUBMIT_TIME -> {
pendingAnswerError =
FractionParsingUiError.createFromParsingError(
fractionParser.getSubmitTimeError(answerText.toString())
).getErrorMessageFromStringRes(resourceHandler)
}
}
errorMessage.set(pendingAnswerError)
AnswerErrorCategory.SUBMIT_TIME -> {
pendingAnswerError =
FractionParsingUiError.createFromParsingError(
fractionParser.getSubmitTimeError(answerText.toString())
).getErrorMessageFromStringRes(resourceHandler)
}
}
errorMessage.set(pendingAnswerError)
return pendingAnswerError
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ class MathExpressionInteractionsViewModel private constructor(
* bound to the corresponding edit text.
*/
var answerText: CharSequence = ""
// The value of ths field is set from the Binding and from the TextWatcher. Any
// programmatic modification needs to be done here, so that the Binding and the TextWatcher
// do not step on each other.
set(value) {
field = value.toString().trim()
}

/**
* Defines whether an answer is currently available to parse. This is expected to be directly
Expand Down Expand Up @@ -166,7 +172,7 @@ class MathExpressionInteractionsViewModel private constructor(
}

override fun onTextChanged(answer: CharSequence, start: Int, before: Int, count: Int) {
answerText = answer.toString().trim()
answerText = answer
val isAnswerTextAvailable = answerText.isNotEmpty()
if (isAnswerTextAvailable != isAnswerAvailable.get()) {
isAnswerAvailable.set(isAnswerTextAvailable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,18 @@ class RatioExpressionInputInteractionViewModel private constructor(
override fun onPropertyChanged(sender: Observable, propertyId: Int) {
errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck(
pendingAnswerError,
answerText.isNotEmpty()
inputAnswerAvailable = true // Allow blank answer submission.
)
}
}
errorMessage.addOnPropertyChangedCallback(callback)
isAnswerAvailable.addOnPropertyChangedCallback(callback)

// Initializing with default values so that submit button is enabled by default.
errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck(
pendingAnswerError = null,
inputAnswerAvailable = true
)
}

override fun getPendingAnswer(): UserAnswer = UserAnswer.newBuilder().apply {
Expand All @@ -67,23 +73,24 @@ class RatioExpressionInputInteractionViewModel private constructor(
}
}.build()

/** It checks the pending error for the current ratio input, and correspondingly updates the error string based on the specified error category. */
/**
* It checks the pending error for the current ratio input, and correspondingly
* updates the error string based on the specified error category.
*/
override fun checkPendingAnswerError(category: AnswerErrorCategory): String? {
if (answerText.isNotEmpty()) {
when (category) {
AnswerErrorCategory.REAL_TIME ->
pendingAnswerError =
stringToRatioParser.getRealTimeAnswerError(answerText.toString())
.getErrorMessageFromStringRes(resourceHandler)
AnswerErrorCategory.SUBMIT_TIME ->
pendingAnswerError =
stringToRatioParser.getSubmitTimeError(
answerText.toString(),
numberOfTerms = numberOfTerms
).getErrorMessageFromStringRes(resourceHandler)
}
errorMessage.set(pendingAnswerError)
pendingAnswerError = when (category) {
AnswerErrorCategory.REAL_TIME ->
if (answerText.isNotEmpty())
stringToRatioParser.getRealTimeAnswerError(answerText.toString())
.getErrorMessageFromStringRes(resourceHandler)
else null
AnswerErrorCategory.SUBMIT_TIME ->
stringToRatioParser.getSubmitTimeError(
answerText.toString(),
numberOfTerms = numberOfTerms
).getErrorMessageFromStringRes(resourceHandler)
}
errorMessage.set(pendingAnswerError)
return pendingAnswerError
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.oppia.android.app.testing

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.databinding.DataBindingUtil
import org.oppia.android.R
import org.oppia.android.app.activity.ActivityComponentImpl
import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity
import org.oppia.android.app.customview.interaction.FractionInputInteractionView
import org.oppia.android.app.model.InputInteractionViewTestActivityParams
import org.oppia.android.app.model.Interaction
import org.oppia.android.app.model.UserAnswer
import org.oppia.android.app.model.WrittenTranslationContext
import org.oppia.android.app.player.state.answerhandling.AnswerErrorCategory
import org.oppia.android.app.player.state.answerhandling.InteractionAnswerErrorOrAvailabilityCheckReceiver
import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiver
import org.oppia.android.app.player.state.itemviewmodel.FractionInteractionViewModel
import org.oppia.android.app.player.state.itemviewmodel.StateItemViewModel
import org.oppia.android.app.player.state.itemviewmodel.StateItemViewModel.InteractionItemFactory
import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener
import org.oppia.android.databinding.ActivityFractionInputInteractionViewTestBinding
import org.oppia.android.util.extensions.getProtoExtra
import org.oppia.android.util.extensions.putProtoExtra
import javax.inject.Inject

/**
* This is a dummy activity to test [FractionInputInteractionView].
*/
class FractionInputInteractionViewTestActivity :
InjectableAutoLocalizedAppCompatActivity(),
StateKeyboardButtonListener,
InteractionAnswerErrorOrAvailabilityCheckReceiver,
InteractionAnswerReceiver {
private lateinit var binding: ActivityFractionInputInteractionViewTestBinding

@Inject
lateinit var fractionInteractionViewModelFactory: FractionInteractionViewModel.FactoryImpl

/** Gives access to the [FractionInteractionViewModel]. */
val fractionInteractionViewModel by lazy {
fractionInteractionViewModelFactory.create<FractionInteractionViewModel>()
}

/** Gives access to the translation context. */
lateinit var writtenTranslationContext: WrittenTranslationContext

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activityComponent as ActivityComponentImpl).inject(this)
binding = DataBindingUtil.setContentView<ActivityFractionInputInteractionViewTestBinding>(
this, R.layout.activity_fraction_input_interaction_view_test
)

val params =
intent.getProtoExtra(
TEST_ACTIVITY_PARAMS_ARGUMENT_KEY,
InputInteractionViewTestActivityParams.getDefaultInstance()
)
writtenTranslationContext = params.writtenTranslationContext
binding.fractionInteractionViewModel = fractionInteractionViewModel
}

/** Checks submit-time errors. */
fun getPendingAnswerErrorOnSubmitClick(v: View) {
fractionInteractionViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME)
}

override fun onPendingAnswerErrorOrAvailabilityCheck(
pendingAnswerError: String?,
inputAnswerAvailable: Boolean
) {
}

override fun onAnswerReadyForSubmission(answer: UserAnswer) {
}

override fun onEditorAction(actionCode: Int) {
}

private inline fun <reified T : StateItemViewModel> InteractionItemFactory.create(
interaction: Interaction = Interaction.getDefaultInstance()
): T {
return create(
entityId = "fake_entity_id",
hasConversationView = false,
interaction = interaction,
interactionAnswerReceiver = this@FractionInputInteractionViewTestActivity,
answerErrorReceiver = this@FractionInputInteractionViewTestActivity,
hasPreviousButton = false,
isSplitView = false,
writtenTranslationContext,
timeToStartNoticeAnimationMs = null
) as T
}

companion object {
private const val TEST_ACTIVITY_PARAMS_ARGUMENT_KEY =
"FractionInputInteractionViewTestActivity.params"

/** Creates an intent to open this activity. */
fun createIntent(
context: Context,
extras: InputInteractionViewTestActivityParams
): Intent {
return Intent(context, FractionInputInteractionViewTestActivity::class.java).also {
it.putProtoExtra(TEST_ACTIVITY_PARAMS_ARGUMENT_KEY, extras)
}
}
}
}
Loading

0 comments on commit a489412

Please sign in to comment.