From d823685ced3e53a54d2c7a6838785ab07d9c2a95 Mon Sep 17 00:00:00 2001 From: Manas <119405883+manas-yu@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:59:42 +0530 Subject: [PATCH 1/3] Fix #4170: Adding LatexImageSpan for vertical alignment of locally rendered and cached LaTeX (#5647) ## Explanation Fix #4170 The PR implements `LatexImageSpan`, which calculates the appropriate ascent, descent, and alignment offsets based on the font metrics and drawable height. This ensures that LaTeX equations appear properly aligned. ## Before ![before](https://github.com/user-attachments/assets/dce66cab-2790-43da-a6b8-3facd6154b8e) ## After ![after](https://github.com/user-attachments/assets/33da46e4-e666-409f-8553-0e5c97297051) ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). --- .../util/parser/html/MathTagHandler.kt | 92 ++++++++++++- .../util/parser/html/MathTagHandlerTest.kt | 128 ++++++++++++++++++ 2 files changed, 218 insertions(+), 2 deletions(-) diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt b/utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt index eabfa3836d7..8a4de9460c1 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/html/MathTagHandler.kt @@ -2,6 +2,9 @@ package org.oppia.android.util.parser.html import android.app.Application import android.content.res.AssetManager +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.drawable.Drawable import android.text.Editable import android.text.Spannable import android.text.style.ImageSpan @@ -60,12 +63,13 @@ class MathTagHandler( } is MathContent.MathAsLatex -> { if (cacheLatexRendering) { - ImageSpan( + LatexImageSpan( imageRetriever.loadMathDrawable( content.rawLatex, lineHeight, type = if (useInlineRendering) INLINE_TEXT_IMAGE else BLOCK_IMAGE - ) + ), + useInlineRendering ) } else { MathExpressionSpan( @@ -144,3 +148,87 @@ class MathTagHandler( return mathVal?.let { "Math content $it" } ?: "" } } + +/** An [ImageSpan] that vertically centers a LaTeX drawable within the surrounding text. */ +private class LatexImageSpan( + imageDrawable: Drawable?, + private val isInlineMode: Boolean +) : ImageSpan(imageDrawable ?: createEmptyDrawable()) { + + companion object { + private const val INLINE_VERTICAL_SHIFT_RATIO = 0.9f + + private fun createEmptyDrawable(): Drawable { + return object : Drawable() { + override fun draw(canvas: Canvas) {} + override fun setAlpha(alpha: Int) {} + override fun setColorFilter(colorFilter: android.graphics.ColorFilter?) {} + override fun getOpacity(): Int = android.graphics.PixelFormat.TRANSPARENT + + init { + setBounds(0, 0, 1, 1) + } + } + } + } + + override fun getSize( + paint: Paint, + text: CharSequence, + start: Int, + end: Int, + fontMetrics: Paint.FontMetricsInt? + ): Int { + val drawableBounds = drawable.bounds + val imageHeight = drawableBounds.height() + val textMetrics = paint.fontMetricsInt + val textHeight = textMetrics.descent - textMetrics.ascent + + fontMetrics?.let { metrics -> + if (isInlineMode) { + val verticalShift = (imageHeight - textHeight) / 2 + + (textMetrics.descent * INLINE_VERTICAL_SHIFT_RATIO).toInt() + metrics.ascent = textMetrics.ascent - verticalShift + metrics.top = metrics.ascent + metrics.descent = textMetrics.descent + verticalShift + metrics.bottom = metrics.descent + } else { + val totalHeight = (imageHeight * 1.2).toInt() + metrics.ascent = -totalHeight / 2 + metrics.top = metrics.ascent + metrics.descent = totalHeight / 2 + metrics.bottom = metrics.descent + } + } + return drawableBounds.right + } + + override fun draw( + canvas: Canvas, + text: CharSequence, + start: Int, + end: Int, + x: Float, + lineTop: Int, + baseline: Int, + lineBottom: Int, + paint: Paint + ) { + canvas.save() + + val imageHeight = drawable.bounds.height() + val yOffset = if (isInlineMode) { + val metrics = paint.fontMetricsInt + val ascent = metrics.ascent.toFloat() + val descent = metrics.descent.toFloat() + val expectedCenterY = baseline.toFloat() + (ascent + descent) / 2f + expectedCenterY - (imageHeight / 2f) + } else { + lineTop.toFloat() + (lineBottom - lineTop - imageHeight) / 2f + } + + canvas.translate(x, yOffset) + drawable.draw(canvas) + canvas.restore() + } +} diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt index d2c15cf1c27..e2a2365b61d 100644 --- a/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt +++ b/utility/src/test/java/org/oppia/android/util/parser/html/MathTagHandlerTest.kt @@ -2,7 +2,9 @@ package org.oppia.android.util.parser.html import android.app.Application import android.content.Context +import android.graphics.Canvas import android.graphics.Color +import android.graphics.Paint import android.text.Html import android.text.Spannable import android.text.style.ImageSpan @@ -21,6 +23,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @@ -39,6 +43,7 @@ import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject import javax.inject.Singleton +import kotlin.math.abs import kotlin.reflect.KClass private const val MATH_MARKUP_1 = @@ -107,6 +112,129 @@ class MathTagHandlerTest { } // TODO(#3085): Introduce test for verifying that the error log scenario is logged correctly. + @Test + fun testParseHtml_withMathMarkup_cachingOn_imageSpanHasCorrectMetrics() { + + val parsedHtml = CustomHtmlContentHandler.fromHtml( + html = MATH_WITHOUT_FILENAME_MARKUP, + imageRetriever = mockImageRetriever, + customTagHandlers = tagHandlersWithCachedMathSupport + ) + val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class) + assertThat(imageSpans).hasLength(1) + + val paint = Paint() + paint.textSize = 20f + val originalMetrics = Paint.FontMetricsInt() + paint.getFontMetricsInt(originalMetrics) + + val spanMetrics = Paint.FontMetricsInt() + imageSpans[0].getSize(paint, parsedHtml, 0, parsedHtml.length, spanMetrics) + + // The span's center should align with the text's center + val originalCenter = (originalMetrics.descent + originalMetrics.ascent) / 2 + val spanCenter = (spanMetrics.descent + spanMetrics.ascent) / 2 + assertThat(abs(originalCenter - spanCenter)).isLessThan(2) + } + + @Test + fun testParseHtml_withMathMarkup_cachingOn_drawsAtCorrectVerticalPosition() { + + val parsedHtml = CustomHtmlContentHandler.fromHtml( + html = MATH_WITHOUT_FILENAME_MARKUP, + imageRetriever = mockImageRetriever, + customTagHandlers = tagHandlersWithCachedMathSupport + ) + + val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class) + assertThat(imageSpans).hasLength(1) + + val mockCanvas = mock(Canvas::class.java) + val paint = Paint() + paint.textSize = 20f + + val metrics = paint.fontMetricsInt + val y = 100 + + imageSpans[0].draw( + mockCanvas, + parsedHtml, + 0, + parsedHtml.length, + 0f, + 0, + y, + 200, + paint + ) + + val textHeight = (metrics.descent - metrics.ascent).toFloat() + val textMidline = y.toFloat() - (textHeight / 2f) + val verticalShift = metrics.descent * 0.9f + val drawable = imageSpans[0].drawable + val expectedTranslation = textMidline + verticalShift - (drawable.bounds.height() / 2f) + + // The translation should position the drawable centered around the text baseline + verify(mockCanvas).save() + verify(mockCanvas).translate( + eq(0f), + capture(floatCaptor) + ) + assertThat(floatCaptor.value).isWithin(1f).of(expectedTranslation) + verify(mockCanvas).restore() + } + + @Test + fun testParseHtml_withMathMarkup_cachingOn_maintainsConsistentHeight() { + + val parsedHtml = CustomHtmlContentHandler.fromHtml( + html = MATH_WITHOUT_FILENAME_MARKUP, + imageRetriever = mockImageRetriever, + customTagHandlers = tagHandlersWithCachedMathSupport + ) + + val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class) + assertThat(imageSpans).hasLength(1) + + val paint = Paint() + paint.textSize = 20f + + val metrics1 = Paint.FontMetricsInt() + val metrics2 = Paint.FontMetricsInt() + + val size1 = imageSpans[0].getSize(paint, parsedHtml, 0, parsedHtml.length, metrics1) + val size2 = imageSpans[0].getSize(paint, parsedHtml, 0, parsedHtml.length, metrics2) + + assertThat(size1).isEqualTo(size2) + assertThat(metrics1.ascent).isEqualTo(metrics2.ascent) + assertThat(metrics1.descent).isEqualTo(metrics2.descent) + assertThat(metrics1.top).isEqualTo(metrics2.top) + assertThat(metrics1.bottom).isEqualTo(metrics2.bottom) + } + + @Test + fun testParseHtml_withMathMarkup_cachingOn_respectsLineHeight() { + + val parsedHtml = CustomHtmlContentHandler.fromHtml( + html = MATH_WITHOUT_FILENAME_MARKUP, + imageRetriever = mockImageRetriever, + customTagHandlers = tagHandlersWithCachedMathSupport + ) + + val imageSpans = parsedHtml.getSpansFromWholeString(ImageSpan::class) + assertThat(imageSpans).hasLength(1) + + val paint = Paint() + paint.textSize = 20f + + val metrics = Paint.FontMetricsInt() + imageSpans[0].getSize(paint, parsedHtml, 0, parsedHtml.length, metrics) + + // Verify that the total height does not exceed the line height + val totalHeight = metrics.bottom - metrics.top + val lineHeight = paint.textSize * 1.2f + assertThat(totalHeight.toFloat()).isLessThan(lineHeight) + } @Test fun testParseHtml_emptyString_doesNotIncludeImageSpan() { From db0094abac9fc494c485bbdb62568b801115429a Mon Sep 17 00:00:00 2001 From: Tanish Moral <134790673+TanishMoral11@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:41:32 +0530 Subject: [PATCH 2/3] Fix #4202: Add support for parsing HasIntegerPartEqualTo rule type. (#5610) Fixes #4202 : This PR adds support for parsing the "HasIntegerPartEqualTo" rule type for fractions in the Oppia Android app. ### Changes Made - **Updated** `test_exp_id_2.json` to include the "HasIntegerPartEqualTo" rule type to ensure the rule is tested properly. - **Updated** `StateRetriever.kt` to handle the "HasIntegerPartEqualTo" rule in the `createExactInputForFractionInput()` method. - **Added** parsing logic for the "HasIntegerPartEqualTo" rule by adding a new check in `createExactInputForFractionInput()` to handle this rule type. --- ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [ ] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> --- domain/src/main/assets/test_exp_id_2.json | 9 ++++++- .../src/main/assets/test_exp_id_2.textproto | 9 +++++++ .../android/domain/util/StateRetriever.kt | 4 +++ .../android/domain/util/StateRetrieverTest.kt | 26 +++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/domain/src/main/assets/test_exp_id_2.json b/domain/src/main/assets/test_exp_id_2.json index 494f1937677..69c992ad580 100644 --- a/domain/src/main/assets/test_exp_id_2.json +++ b/domain/src/main/assets/test_exp_id_2.json @@ -496,7 +496,14 @@ "denominator": 2 } } - }], + }, + { + "rule_type": "HasIntegerPartEqualTo", + "inputs": { + "x": 1 + } + } + ], "outcome": { "dest": "MultipleChoice", "feedback": { diff --git a/domain/src/main/assets/test_exp_id_2.textproto b/domain/src/main/assets/test_exp_id_2.textproto index 1fc383fbf27..2917343097e 100644 --- a/domain/src/main/assets/test_exp_id_2.textproto +++ b/domain/src/main/assets/test_exp_id_2.textproto @@ -806,6 +806,15 @@ states { } rule_type: "IsExactlyEqualTo" } + rule_specs { + input { + key: "x" + value { + signed_int: 1 + } + } + rule_type: "HasIntegerPartEqualTo" + } } solution { answer_is_exclusive: true diff --git a/domain/src/main/java/org/oppia/android/domain/util/StateRetriever.kt b/domain/src/main/java/org/oppia/android/domain/util/StateRetriever.kt index 227079a0640..a17bf9301e4 100644 --- a/domain/src/main/java/org/oppia/android/domain/util/StateRetriever.kt +++ b/domain/src/main/java/org/oppia/android/domain/util/StateRetriever.kt @@ -318,6 +318,10 @@ class StateRetriever @Inject constructor() { InteractionObject.newBuilder() .setNonNegativeInt(inputJson.getInt(keyName)) .build() + "HasIntegerPartEqualTo" -> + InteractionObject.newBuilder() + .setSignedInt(inputJson.getInt(keyName)) + .build() else -> InteractionObject.newBuilder() .setFraction(parseFraction(inputJson.getJSONObject(keyName))) diff --git a/domain/src/test/java/org/oppia/android/domain/util/StateRetrieverTest.kt b/domain/src/test/java/org/oppia/android/domain/util/StateRetrieverTest.kt index b0a2d882258..4a4f2adc574 100644 --- a/domain/src/test/java/org/oppia/android/domain/util/StateRetrieverTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/util/StateRetrieverTest.kt @@ -630,6 +630,32 @@ class StateRetrieverTest { assertThat(state.linkedSkillId).isEqualTo("test_skill_id_2") } + @Test + fun testParseState_withFractionInputInteraction_parsesRuleHasIntegerPartEqualToRuleSpec() { + val state = loadStateFromJson( + stateName = "Fractions", + explorationName = TEST_EXPLORATION_ID_2 + ) + + val ruleSpecMap = state.interaction.answerGroupsList + .flatMap(AnswerGroup::getRuleSpecsList) + .associateBy(RuleSpec::getRuleType) + assertThat(ruleSpecMap).containsKey("HasIntegerPartEqualTo") + } + + @Test + fun testParseState_withFractionInput_parsesRuleHasIntegerPartEqualToValueAtX() { + val state = loadStateFromJson( + stateName = "Fractions", + explorationName = TEST_EXPLORATION_ID_2 + ) + + val ruleSpecMap = lookUpRuleSpec(state, "HasIntegerPartEqualTo") + val expectedInputInteractionObject = + InteractionObject.newBuilder().setSignedInt(1).build() + assertThat(ruleSpecMap.inputMap["x"]).isEqualTo(expectedInputInteractionObject) + } + /** * Return the first [RuleSpec] in the specified [State] matching the specified rule type, or fails * if one cannot be found. From 0717ce843a42f8d417c4146fa83dddaf255a858d Mon Sep 17 00:00:00 2001 From: Kavyansh Khandelwal <152530268+kavyanshkhandelwal@users.noreply.github.com> Date: Tue, 28 Jan 2025 07:01:10 +0530 Subject: [PATCH 3/3] Fix 5273: Unused Attribute Lint Warning (#5662) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Explanation Fixes #5273 - Unused Attribute Lint Warning Here’s a summary of the changes I made: In "bottom_sheet_options_menu_fragment.xml", fixed "android:drawableTint to app:drawableTint" at 3 places. In "circular_progress_indicator_adapters_test_activity.xml", fixed android:min by using tools:targetApi="26". In "content_item.xml", fixed android:breakStrategy by using tools:targetApi="23". In "feedback_item.xml", fixed android:breakStrategy by using tools:targetApi="23". In "profile_list_control_buttons.xml", fixed android:min by using tools:targetApi="26". In "profile_progress_recently_played_story_card.xml", fixed android:forceDarkAllowed by using tools:targetApi="29". In "promoted_story_card.xml", fixed android:forceDarkAllowed by using tools:targetApi="29". In "recently_played_story_card.xml", fixed android:forceDarkAllowed by using tools:targetApi="29". In "resume_lesson_fragment.xml", changed android:drawableTint to appdrawableTint at 2 places. Inside the Android -> Lint -> Correctness (showing error): Changed the android:tint to app:tint in 5 XML files: "add_profile_activity.xml" "administrator_controls_activity.xml" "drag_drop_interaction_items.xml" "drawer_fragment.xml" "help_activity.xml" Unused Attributes Fixed :- drawableTint min breakStrategy forceDarkAllowed Here are the screenshots of Code Inspect before and after the changes. **Screenshot Before Changes:** ![Screenshot 2025-01-24 005141](https://github.com/user-attachments/assets/79549066-483e-434c-b286-9a2f195aada5) **Screenshot After Changes:** ![Screenshot 2025-01-24 013730](https://github.com/user-attachments/assets/020f6677-cdae-4c43-bc79-014c332405ca) ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> --- app/src/main/res/layout-land/resume_lesson_fragment.xml | 2 +- .../res/layout-sw600dp-land/resume_lesson_fragment.xml | 2 +- .../main/res/layout/bottom_sheet_options_menu_fragment.xml | 6 +++--- .../circular_progress_indicator_adapters_test_activity.xml | 7 +++++-- app/src/main/res/layout/content_item.xml | 4 +++- app/src/main/res/layout/feedback_item.xml | 4 +++- app/src/main/res/layout/profile_list_control_buttons.xml | 4 +++- .../layout/profile_progress_recently_played_story_card.xml | 4 +++- app/src/main/res/layout/promoted_story_card.xml | 4 +++- app/src/main/res/layout/recently_played_story_card.xml | 4 +++- 10 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/layout-land/resume_lesson_fragment.xml b/app/src/main/res/layout-land/resume_lesson_fragment.xml index e317a2eff27..ab5f816d7d2 100644 --- a/app/src/main/res/layout-land/resume_lesson_fragment.xml +++ b/app/src/main/res/layout-land/resume_lesson_fragment.xml @@ -94,7 +94,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:background="@drawable/state_button_primary_background" - android:drawableTint="@color/component_color_shared_white_background_color" + app:drawableTint="@color/component_color_shared_white_background_color" android:fontFamily="sans-serif-medium" android:gravity="center" android:minWidth="144dp" diff --git a/app/src/main/res/layout-sw600dp-land/resume_lesson_fragment.xml b/app/src/main/res/layout-sw600dp-land/resume_lesson_fragment.xml index 5997f1069a4..6d803c68388 100644 --- a/app/src/main/res/layout-sw600dp-land/resume_lesson_fragment.xml +++ b/app/src/main/res/layout-sw600dp-land/resume_lesson_fragment.xml @@ -124,7 +124,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:background="@drawable/state_button_primary_background" - android:drawableTint="@color/component_color_shared_white_background_color" + app:drawableTint="@color/component_color_shared_white_background_color" android:fontFamily="sans-serif-medium" android:gravity="center" android:minWidth="144dp" diff --git a/app/src/main/res/layout/bottom_sheet_options_menu_fragment.xml b/app/src/main/res/layout/bottom_sheet_options_menu_fragment.xml index fe98ab51455..9e0c7ad8408 100644 --- a/app/src/main/res/layout/bottom_sheet_options_menu_fragment.xml +++ b/app/src/main/res/layout/bottom_sheet_options_menu_fragment.xml @@ -15,7 +15,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:drawablePadding="8dp" - android:drawableTint="@color/component_color_shared_multipane_icon_color" + app:drawableTint="@color/component_color_shared_multipane_icon_color" android:gravity="center_vertical" android:minHeight="48dp" android:text="@string/menu_options" @@ -28,7 +28,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:drawablePadding="8dp" - android:drawableTint="@color/component_color_shared_multipane_icon_color" + app:drawableTint="@color/component_color_shared_multipane_icon_color" android:gravity="center_vertical" android:minHeight="48dp" android:text="@string/menu_help" @@ -41,7 +41,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:drawablePadding="8dp" - android:drawableTint="@color/component_color_shared_multipane_icon_color" + app:drawableTint="@color/component_color_shared_multipane_icon_color" android:gravity="center_vertical" android:minHeight="48dp" android:text="@string/bottom_sheet_options_menu_close" diff --git a/app/src/main/res/layout/circular_progress_indicator_adapters_test_activity.xml b/app/src/main/res/layout/circular_progress_indicator_adapters_test_activity.xml index c0f63136ec5..4d5161fb530 100644 --- a/app/src/main/res/layout/circular_progress_indicator_adapters_test_activity.xml +++ b/app/src/main/res/layout/circular_progress_indicator_adapters_test_activity.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -20,6 +21,7 @@ android:layout_height="wrap_content" android:max="@{viewModel.defaultMaximum}" android:min="@{viewModel.defaultMinimum}" + tools:targetApi="26" app:animatedProgress="@{viewModel.currentAutoProgress}" /> + android:min="@{viewModel.defaultMinimum}" + tools:targetApi="26"/> diff --git a/app/src/main/res/layout/content_item.xml b/app/src/main/res/layout/content_item.xml index 09b1eb682ce..a29a44814fe 100644 --- a/app/src/main/res/layout/content_item.xml +++ b/app/src/main/res/layout/content_item.xml @@ -1,5 +1,6 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -44,6 +45,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:breakStrategy="simple" + tools:targetApi="23" android:fontFamily="sans-serif" android:minWidth="48dp" android:minHeight="48dp" diff --git a/app/src/main/res/layout/feedback_item.xml b/app/src/main/res/layout/feedback_item.xml index f87b4d316b0..4bc646346bc 100644 --- a/app/src/main/res/layout/feedback_item.xml +++ b/app/src/main/res/layout/feedback_item.xml @@ -1,5 +1,6 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -44,6 +45,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:breakStrategy="simple" + tools:targetApi="23" android:fontFamily="sans-serif" android:minWidth="48dp" android:minHeight="48dp" diff --git a/app/src/main/res/layout/profile_list_control_buttons.xml b/app/src/main/res/layout/profile_list_control_buttons.xml index fa3809a5b29..ac6a92b7e05 100644 --- a/app/src/main/res/layout/profile_list_control_buttons.xml +++ b/app/src/main/res/layout/profile_list_control_buttons.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -52,6 +53,7 @@ android:layout_height="wrap_content" android:max="@{viewModel.forceUploadProgress.totalEventsToUpload}" android:min="0" + tools:targetApi="26" app:animatedProgress="@{viewModel.forceUploadProgress.eventsUploaded}" android:visibility="@{viewModel.forceUploadProgress.hasEventsToUpload() ? View.VISIBLE : View.GONE, default=gone}" app:layout_constraintBottom_toBottomOf="@+id/learner_analytics_upload_logs_now_button" diff --git a/app/src/main/res/layout/profile_progress_recently_played_story_card.xml b/app/src/main/res/layout/profile_progress_recently_played_story_card.xml index 5730c5b283e..9c5602c9f26 100755 --- a/app/src/main/res/layout/profile_progress_recently_played_story_card.xml +++ b/app/src/main/res/layout/profile_progress_recently_played_story_card.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -38,6 +39,7 @@ app:entityId="@{viewModel.promotedStory.storyId}" app:entityType="@{viewModel.entityType}" android:forceDarkAllowed="false" + tools:targetApi="29" app:layout_constraintDimensionRatio="4:3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/promoted_story_card.xml b/app/src/main/res/layout/promoted_story_card.xml index 91e711e0dae..84f2062cd9c 100755 --- a/app/src/main/res/layout/promoted_story_card.xml +++ b/app/src/main/res/layout/promoted_story_card.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -36,6 +37,7 @@ app:entityId="@{viewModel.promotedStory.storyId}" app:entityType="@{viewModel.entityType}" android:forceDarkAllowed="false" + tools:targetApi="29" app:layout_constraintDimensionRatio="H, 16:9" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/recently_played_story_card.xml b/app/src/main/res/layout/recently_played_story_card.xml index 0994de8f994..d177628db8f 100755 --- a/app/src/main/res/layout/recently_played_story_card.xml +++ b/app/src/main/res/layout/recently_played_story_card.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -39,6 +40,7 @@ app:entityId="@{viewModel.promotedStory.storyId}" app:entityType="@{viewModel.entityType}" android:forceDarkAllowed="false" + tools:targetApi="29" app:layout_constraintDimensionRatio="4:3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"