Skip to content

Commit 8ce374b

Browse files
Fix #5615 : Scroll Position For Policies Screen On Orientation Change (#5616)
## Explanation This PR fixes #5615 the issue where the scroll position in the policies screen (Privacy Policy/Terms of Service) would reset after an orientation change. ### **Changes Made:** 1. **Updated `PoliciesFragmentPresenter`:** - Added a `scrollPosition` property to save and restore the scroll state. - Implemented `onSaveInstanceState` and `onRestoreInstanceState` handling using a constant key. - Ensured the scroll position is retained using `ScrollView.viewTreeObserver` in `onGlobalLayoutListener`. 2. **Modified `PoliciesFragment`:** - Delegated `onSaveInstanceState` calls to the presenter. - Passed the saved instance state to the presenter during fragment view creation. ### **Testing Performed:** - Verified that scroll position is correctly retained: - Across orientation changes (portrait ↔️ landscape). - On both Privacy Policy and Terms of Service screens --- ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: ". - [x] No unnecessary code changes or Android Studio formatting changes are included. - [x] The branch is **not** called "develop" and is up-to-date with "develop". - [x] Appropriate reviewers are assigned. --- After Changes : [Screen_recording_20241223_010758.webm](https://github.com/user-attachments/assets/f9648b03-5b27-4090-9eaa-82946214cd62) --------- Co-authored-by: Adhiambo Peres <[email protected]>
1 parent 68f20cb commit 8ce374b

File tree

4 files changed

+66
-3
lines changed

4 files changed

+66
-3
lines changed

app/src/main/java/org/oppia/android/app/policies/PoliciesFragment.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ class PoliciesFragment : InjectableFragment() {
4949
POLICIES_FRAGMENT_POLICY_PAGE_ARGUMENT_PROTO,
5050
PoliciesFragmentArguments.getDefaultInstance()
5151
)
52-
return policiesFragmentPresenter.handleCreateView(inflater, container, policies)
52+
return policiesFragmentPresenter
53+
.handleCreateView(inflater, container, policies, savedInstanceState)
54+
}
55+
56+
override fun onSaveInstanceState(outState: Bundle) {
57+
super.onSaveInstanceState(outState)
58+
policiesFragmentPresenter.handleSaveInstanceState(outState)
5359
}
5460
}

app/src/main/java/org/oppia/android/app/policies/PoliciesFragmentPresenter.kt

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package org.oppia.android.app.policies
22

3+
import android.os.Bundle
34
import android.view.LayoutInflater
45
import android.view.View
56
import android.view.ViewGroup
7+
import android.widget.ScrollView
68
import androidx.appcompat.app.AppCompatActivity
79
import org.oppia.android.R
810
import org.oppia.android.app.fragment.FragmentScope
@@ -22,21 +24,41 @@ class PoliciesFragmentPresenter @Inject constructor(
2224
private val resourceHandler: AppLanguageResourceHandler
2325
) : HtmlParser.PolicyOppiaTagActionListener {
2426

27+
private lateinit var binding: PoliciesFragmentBinding
28+
private var scrollPosition = 0
29+
2530
/** Handles onCreate() method of the [PoliciesFragment]. */
2631
fun handleCreateView(
2732
inflater: LayoutInflater,
2833
container: ViewGroup?,
29-
policiesFragmentArguments: PoliciesFragmentArguments
34+
policiesFragmentArguments: PoliciesFragmentArguments,
35+
savedInstanceState: Bundle?
3036
): View {
31-
val binding = PoliciesFragmentBinding.inflate(
37+
binding = PoliciesFragmentBinding.inflate(
3238
inflater,
3339
container,
3440
/* attachToRoot= */ false
3541
)
42+
43+
savedInstanceState?.let {
44+
scrollPosition = it.getInt(KEY_SCROLL_Y, 0)
45+
}
46+
3647
setUpContentForTextViews(policiesFragmentArguments.policyPage, binding)
3748

49+
(binding.root as ScrollView).viewTreeObserver.addOnGlobalLayoutListener {
50+
binding.root.scrollTo(0, scrollPosition)
51+
}
52+
3853
return binding.root
3954
}
55+
/**
56+
* Saves the current scroll position of the policies page into the given [outState].
57+
* This helps in restoring the scroll position after orientation changes.
58+
*/
59+
fun handleSaveInstanceState(outState: Bundle) {
60+
outState.putInt(KEY_SCROLL_Y, (binding.root as ScrollView).scrollY)
61+
}
4062

4163
private fun setUpContentForTextViews(
4264
policyPage: PolicyPage,
@@ -86,4 +108,8 @@ class PoliciesFragmentPresenter @Inject constructor(
86108
(activity as RouteToPoliciesListener).onRouteToPolicies(PolicyPage.TERMS_OF_SERVICE)
87109
}
88110
}
111+
112+
companion object {
113+
private const val KEY_SCROLL_Y = "policies_scroll_y"
114+
}
89115
}

app/src/main/res/layout/policies_fragment.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
xmlns:app="http://schemas.android.com/apk/res-auto">
44

55
<ScrollView
6+
android:id="@+id/policy_scroll_view"
67
android:layout_width="match_parent"
78
android:layout_height="match_parent"
89
android:background="@color/component_color_shared_screen_tertiary_background_color"

app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import android.app.Application
44
import android.app.Instrumentation.ActivityResult
55
import android.content.Context
66
import android.content.Intent
7+
import android.content.pm.ActivityInfo
78
import android.text.Spannable
89
import android.text.style.ClickableSpan
10+
import android.widget.ScrollView
911
import android.widget.TextView
1012
import androidx.appcompat.app.AppCompatActivity
1113
import androidx.test.core.app.ActivityScenario.launch
@@ -304,6 +306,34 @@ class PoliciesFragmentTest {
304306
}
305307
}
306308

309+
@Test
310+
fun testPoliciesFragment_privacyPolicy_retainsScrollPositionAfterOrientationChange() {
311+
launch<PoliciesFragmentTestActivity>(
312+
createPoliciesFragmentTestActivity(
313+
getApplicationContext(),
314+
PolicyPage.PRIVACY_POLICY
315+
)
316+
).use { scenario ->
317+
scenario.onActivity { activity ->
318+
val scrollView = activity.findViewById<ScrollView>(R.id.policy_scroll_view)
319+
scrollView.scrollTo(0, 500)
320+
testCoroutineDispatchers.runCurrent()
321+
322+
// Capture the current scroll position.
323+
val initialScrollPosition = scrollView.scrollY
324+
assertThat(initialScrollPosition).isEqualTo(500)
325+
326+
// Rotate the screen (simulate an orientation change).
327+
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
328+
testCoroutineDispatchers.runCurrent()
329+
330+
// Verify that the scroll position is retained after the orientation change.
331+
val newScrollPosition = scrollView.scrollY
332+
assertThat(newScrollPosition).isEqualTo(initialScrollPosition)
333+
}
334+
}
335+
}
336+
307337
@Test
308338
fun testPoliciesFragment_checkTermsOfServiceWebLink_opensTheLink() {
309339
launch<PoliciesFragmentTestActivity>(

0 commit comments

Comments
 (0)