From 9646af65b2753145976ffd2adf83fb286cb411b2 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 2 Dec 2024 19:22:58 +0530 Subject: [PATCH 1/5] Localisation updates from https://translatewiki.net. (#5583) Translation updates --- app/src/main/res/values-ar/strings.xml | 115 +++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 251a04d4bcd..74898b487c0 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -14,6 +14,7 @@ التنزيلات مساعدة مشغل رحلة الاستكشاف + تشغيل الاستكشاف المساعدة أغلق تغيير الملف الشخصي @@ -36,6 +37,8 @@ تشغيل الصوت عبر الإنترنت قد يستخدم الكثير من بيانات الهاتف. لا تُظهِر هذه الرسالة مُجددًا بطاقة المفهوم + بطاقة المفاهيم 1 + بطاقة المفاهيم 2 بطاقة المراجعة هل تريد المغادرة إلى صفحة الموضوع؟ لن يتم حفظ تقدمك. @@ -69,13 +72,16 @@ التوجه إلى البطاقة السابقة التوجه إلى البطاقة القادمة تقديم + إرسال إعادة التشغيل العودة إلى الموضوع الردود السابقة (%s) يضغط على %s + اختر صورة للاستمرار تعلم مرة أخرى اعرض المزيد اعرض أقل + هذا نموذج عرض النص الأسئلة المتكررة الأسئلة المميزة الأسئلة المتكررة @@ -96,6 +102,7 @@ تم الانتهاء من الفصل %1$s بعنوان %2$s الفصل %1$s بعنوان %2$s قيد التنفيذ أكمل الفصل %1$s : %2$s لفتح هذا الفصل. + الفصل %1$s: هذا الفصل %2$s مغلق الآن. يرجى الانتهاء من فصل %3$s: %4$s لفتح هذا الفصل. أكمل الفصل السابق لفتح هذا الفصل. أدخل النص. أدخل كسر عشري في الصيغة (بسط/مقام)، أو عدد كسري في الصيغة (عدد بسط/مقام). @@ -103,8 +110,11 @@ أدخل رقم. أدخل أرقام بالوحدات هنا. اكتب تعبيرا هنا ، باستخدام الأرقام فقط. + أدخل تعبير عدديا للاستمرار. اكتب تعبيرا هنا. + أدخل تعبير جبري للاستمرار. اكتب معادلة هنا. + أدخل معادلة للاستمرار. يرجى إزالة المسافات بين الأرقام في إجابتك. الرجاء إغلاق أو إزالة الأقواس. الرجاء إزالة الأقواس حول الإجابة بالكامل: \' %s \'. @@ -166,14 +176,19 @@ من فضلك أدخل كسر عشري صالح (5/3 أو 1 2/3 على سبيل المثال) من فضلك لا تضع 0 في المقام لا يجب أن يحتوي أي رقم في الكسر العشري على أكثر من 7 أرقام. + أدخل كسراً للاستمرار. من فضلك إبدأ إجابتك برقم (0 او 0.5 على سبيل المثال) من فضلك قم بإدخال رقم صالح. (–يمكن أن تحتوي الإجابة على 15 رقمًا (0-9) على الأكثر أو الرموز (. أو + أدخل عدداً للاستمرار. يرجى كتابة نسبة تتكون من أرقام مفصولة بعلامات نقطية (على سبيل المثال 1:2 أو 1:2:3). الرجاء إدخال نسبة صالحة (على سبيل المثال 1:2 أو 1:2:3). إجابتك تحتوي على علامتي نقطتين (:) بجانب بعضهما البعض. عدد المصطلحات لا يساوي عدد المصطلحات المطلوبة. لا يمكن للنسب أن تحتوي على 0 كعنصر. + أدخل نسبة للاستمرار. + أدخل نصاً للاستمرار. + اختر إجابة للمتابعة. الحجم مجهول %s بايت %s ك.ب @@ -270,6 +285,8 @@ هذا الاسم مستخدَم بالفعل من قبل ملف شخصي آخر. من فضلك أدخل اسمًا لهذا الملف الشخصي. يمكن أن يحتوي الاسم على أحرفٍ فقط، جرّب اسمًا آخر؟ + نوع الملف الشخصي غير معروف. + حدث خطأ أثناء إنشاء الملف الشخصي. رقم التعريف الشخصي يجب أن يتكون من 3 أرقام. برجاء التأكد من تطابق رقمي التعريف الشخصي. المزيد من المعلومات عن رقم التعريف الشخصي المكون من 3 أرقام. @@ -328,6 +345,8 @@ الصفحة الرئيسية من الآن يمكنك مشاهدة الدروس المقترحة لك هنا. حدد موضوعًا للبدء + الرئيسية + الفصول الدراسية الملفات الشخصية تعديل الملفات الشخصية تم الإنشاء في %s @@ -364,6 +383,7 @@ مطلوب زر العودة التالي + تحليلات دراسة الطالب عام تعديل الحساب إدارة الملف الشخصي @@ -438,6 +458,7 @@ العودة إلى الدرس الشرح: إذا تساوى عنصران، قم بدمجهم. + رتب الصناديق للاستمرار. الربط بالعنصر %s إلغاء ربط العناصر عند %s تحريك العنصر إلى الأسفل إلى %s @@ -451,6 +472,17 @@ إصدار التطبيق غير مدعوم لم يعد هذا الإصدار من التطبيق مدعومًا. يرجى تحديثه عبر متجر Play. إغلاق التطبيق + مطلوب تحديث التطبيق + إصدار جديد من %s متاح الآن. الإصدار الجديد يوفر حماية أكثر ويحسن تجربتك التعليمية. \n\nهذا الإصدار لم يعد مدعوم. للاستمرار في استخدام التطبيق، يرجى تحديثه إلى الإصدار الأخير. + تحديث + إغلاق التطبيق + متوفر تحديث جديد + إصدار جديد من %s متاح الآن. نوصي بتحديث التطبيق لإصلاح الأخطاء وتحسين تجربة الدراسة. + تجاهل + تحديث + تحديث نظام تشغيل Android الخاص بك + نوصي بتحديث نظام التشغيل Android الخاص بك للاستفادة من الميزات والدروس الجديدة الموجودة في %s .\n\nقم بزيارة تطبيق الإعدادات على هاتفك لتحديث نظام التشغيل الخاص بك. + تجاهل بناء المطور ألفا بيتا @@ -465,6 +497,8 @@ ل أدخل النسبة بالصيغة x:y. انقر هنا لإدخال نص. + اكتب الرقم هنا. + اكتب هنا. أصغر حجمًا للنص أكبر حجمًا للنص قريباً @@ -532,4 +566,85 @@ اختر كل الإجابات الصحيحة من فضلك. يمكنك أن تختار المزيد من الخيارات لا يمكنك اختيار أكثر من %s خيار. + استطلاع + السابق + إرسال + اترك تعليقك هنا + متابعة الاستطلاع + خروج + مغادرة الاستطلاع + هل أنت متأكد أنك تريد مغادرة الاستطلاع؟ + تساعدنا ملاحظاتك في تقديم خدمة أفضل لمثلك من المتعلمين. هل ترغب في الخضوع لاستبيان قصير حول تجربتك؟ + بدء الاستطلاع + ربما لاحقا + نشكرك على إكمال الاستطلاع. نأمل أن تكون قد استمتعت باستخدام %s ! + مغادرة الاستطلاع + نرحب بملاحظاتك + شكرا لك + 0 - ليس من المرجح على الإطلاق + 10 - من المحتمل للغاية + الرجاء اختيار أحد الخيارات التالية: + أنا متعلم + أنا مدرس + أنا والد/والدة + أخرى + كيف ستشعر إذا لم يعد بإمكانك استخدام %s ؟ + محبط للغاية + محبط إلى حد ما + غير محبط + N/A - لم أعد أستخدم %s بعد الآن + يسعدنا أنك استمتعت بتجربتك مع %s . يرجى مشاركتنا بما الذي ساعدك أكثر: + شكرا على ملاحظتك! كيف يمكننا تقديم تجربة أفضل؟ + ساعدنا على تحسين تجربتك! يرجى مشاركتنا بالسبب الرئيسي وراء تقييمك: + على مقياس من 0 إلى 10، ما مدى احتمالية أن توصي %s لصديق أو زميل؟ + الموضوع الفرعي السابق هو %s + الموضوع الفرعي التالي هو %s + معلومات التطبيق + سهم تسليط الضوء + إغلاق زر تسليط الضوء + زر الانتقال للحالة السابقة + أيقونة خيارات المطور + أيقونة إدارة التحكم + قائمة الخيارات + الزر السابق + الزر التالي + أيقونة اللغة + أيقونة الإعدادات + عرض صورة الملف الشخصي + أيقونة القفل + إحصائيات التحميل + محتوى HTML + مرحبا بكم في %s ! + تعلم الرياضيات مجانا، في أي وقت! + مصمم للطلاب من سن 7 إلى 14 عاما + اختر لغة لتبدأ + يمكنك تغيير اختيار اللغة في أي وقت من إعدادات التطبيق + هيا بنا! + حدد نوع الملف الشخصي + أخبرنا المزيد عنك! + أنا طالب وأريد أن أتعلم أشياء جديدة! + أنا والد أو معلم أو ولي أمر طالب. + الخطوة 2 من 5 + إنشاء ملف شخصي + ماذا يجب أن نطلق عليك؟ + الاسم المستعار + اضغط هنا لإضافة صورة + اضغط على المربع أعلاه لكتابة اسمك المستعار. + تعديل صورة الملف الشخصي + صورة الملف الشخصي الحالية + الخطوة 3 من 5 + مرحبا بك + مرحبا، %s! + تعلم الرياضيات من خلال دروس ممتعة مبنية على القصص. + حاول حل الأسئلة التدريبية لاختبار معرفتك. + الحصول على الملاحظات لتطوير استخدام تصحيحات %s. + الخطوة 4 من 5 + قندس لطيف يرتدي نظارات. + قندس لطيف مع الكتب. + الأم والقندس الصغير. + العودة + التالي + في %s ، يمكنك الاستماع إلى الدروس! + اختر لغة المقاطع الصوتية للاستماع إلى الدروس + الخطوة 5 من 5 From 242fef45634ae1b54703d9357230fcadc95a24d4 Mon Sep 17 00:00:00 2001 From: Ayush Yadav <143514610+theayushyadav11@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:00:32 +0530 Subject: [PATCH 2/5] Fixes #4294 : Added Profile delete message with AlertDialog (#5577) Fixes #4294 : This pull request introduces a new feature to notify users when a profile is successfully deleted. It includes the creation of a new dialog fragment, updates to existing fragments to integrate this new feature, and necessary string resources. ### New Feature Implementation: * [`app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt`](diffhunk://#diff-2203d0609a904e5e457efadd299665f9c28d3f8bb4f8ef9d3479d872cd80a246R1-R62): Created a new `DeleteProfileSuccessDialogFragment` class to notify users after a profile is successfully deleted. ### Integration with Existing Components: * [`app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt`](diffhunk://#diff-9de2bd314b90b27a64b5f0198a1e117d1401aca4c3bf6d41efdf0c0927d39b3cR66): Added import and injection for `DeleteProfileSuccessDialogFragment`. [[1]](diffhunk://#diff-9de2bd314b90b27a64b5f0198a1e117d1401aca4c3bf6d41efdf0c0927d39b3cR66) [[2]](diffhunk://#diff-9de2bd314b90b27a64b5f0198a1e117d1401aca4c3bf6d41efdf0c0927d39b3cR132) * [`app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt`](diffhunk://#diff-12a41502c6dc988ca4dee178dfe871257b0be385b3453ed5aaef6bf16611c90fL154-R157): Updated to show the new `DeleteProfileSuccessDialogFragment` upon successful profile deletion. ### String Resources: * [`app/src/main/res/values/strings.xml`](diffhunk://#diff-5e01f7d37a66e4ca03deefc205d8e7008661cdd0284a05aaba1858e6b7bf9103R348): Added a new string resource for the profile deletion success message. ### Test File Exemptions: * [`scripts/assets/test_file_exemptions.textproto`](diffhunk://#diff-6ed782dd71126d847e7bac39eca30f830be55b72aa10b7e944612f2463003e24R2093-R2096): Added an exemption for the new `ProfileDeleteSuccessDialogFragment`. ### Video Demo https://github.com/user-attachments/assets/d3a71f37-b6e5-43cb-b411-76af5f56042b ### Video Demo on Tablet https://github.com/user-attachments/assets/04c0912e-3dd2-45c8-bc3b-0025887394da ## 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)). --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> --- .../app/fragment/FragmentComponentImpl.kt | 2 + .../ProfileDeleteSuccessDialogFragment.kt | 61 +++++ .../profile/ProfileEditFragmentPresenter.kt | 17 +- app/src/main/res/values/strings.xml | 2 + .../profile/ProfileEditActivityTest.kt | 8 + .../profile/ProfileEditFragmentTest.kt | 231 ++++++++++-------- scripts/assets/test_file_exemptions.textproto | 4 + 7 files changed, 216 insertions(+), 109 deletions(-) create mode 100644 app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt diff --git a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt index 9a861937346..cab45a1b1b6 100644 --- a/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt +++ b/app/src/main/java/org/oppia/android/app/fragment/FragmentComponentImpl.kt @@ -63,6 +63,7 @@ import org.oppia.android.app.profile.ResetPinDialogFragment import org.oppia.android.app.profileprogress.ProfilePictureEditDialogFragment import org.oppia.android.app.profileprogress.ProfileProgressFragment import org.oppia.android.app.resumelesson.ResumeLessonFragment +import org.oppia.android.app.settings.profile.ProfileDeleteSuccessDialogFragment import org.oppia.android.app.settings.profile.ProfileEditDeletionDialogFragment import org.oppia.android.app.settings.profile.ProfileEditFragment import org.oppia.android.app.settings.profile.ProfileListFragment @@ -128,6 +129,7 @@ interface FragmentComponentImpl : FragmentComponent, ViewComponentBuilderInjecto fun inject(cellularAudioDialogFragment: CellularAudioDialogFragment) fun inject(completedStoryListFragment: CompletedStoryListFragment) fun inject(conceptCardFragment: ConceptCardFragment) + fun inject(profileDeleteSuccessDialogFragment: ProfileDeleteSuccessDialogFragment) fun inject(developerOptionsFragment: DeveloperOptionsFragment) fun inject(downloadsTabFragment: DownloadsTabFragment) fun inject(dragDropTestFragment: DragDropTestFragment) diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt new file mode 100644 index 00000000000..cf8aa01e941 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt @@ -0,0 +1,61 @@ +package org.oppia.android.app.settings.profile + +import android.app.Dialog +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import org.oppia.android.R +import org.oppia.android.app.administratorcontrols.AdministratorControlsActivity +import org.oppia.android.app.fragment.FragmentComponentImpl +import org.oppia.android.app.fragment.InjectableDialogFragment +import org.oppia.android.app.translation.AppLanguageResourceHandler +import javax.inject.Inject + +/** [DialogFragment] that notifies the user after a profile is successfully deleted. */ +class ProfileDeleteSuccessDialogFragment : InjectableDialogFragment() { + @Inject + lateinit var resourceHandler: AppLanguageResourceHandler + + companion object { + /** Tag for [ProfileDeleteSuccessDialogFragment]. */ + const val DELETE_PROFILE_SUCCESS_DIALOG_FRAGMENT_TAG = "DELETE_PROFILE_SUCCESS_DIALOG_FRAGMENT" + + /** Returns a new instance of [ProfileDeleteSuccessDialogFragment]. */ + fun createNewInstance(): ProfileDeleteSuccessDialogFragment = + ProfileDeleteSuccessDialogFragment() + } + + override fun onAttach(context: Context) { + super.onAttach(context) + (fragmentComponent as FragmentComponentImpl).inject(this) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val alertDialog = + AlertDialog + .Builder(requireContext(), R.style.OppiaAlertDialogTheme) + .apply { + setMessage( + resourceHandler.getStringInLocale(R.string.profile_edit_delete_successful_message), + ) + setPositiveButton( + resourceHandler + .getStringInLocale(R.string.profile_edit_delete_success_dialog_positive_button), + ) { _, _ -> + if (requireContext().resources.getBoolean(R.bool.isTablet)) { + val intent = Intent(requireContext(), AdministratorControlsActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + } else { + val intent = Intent(requireContext(), ProfileListActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + } + } + }.create() + alertDialog.setCanceledOnTouchOutside(true) + return alertDialog + } +} diff --git a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt index c5df7aa5439..6b3cd984335 100644 --- a/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditFragmentPresenter.kt @@ -1,13 +1,11 @@ package org.oppia.android.app.settings.profile -import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.Observer -import org.oppia.android.R import org.oppia.android.app.administratorcontrols.AdministratorControlsActivity import org.oppia.android.app.administratorcontrols.ProfileEditDeletionDialogListener import org.oppia.android.app.devoptions.markchapterscompleted.MarkChaptersCompletedActivity @@ -151,16 +149,11 @@ class ProfileEditFragmentPresenter @Inject constructor( fragment, Observer { if (it is AsyncResult.Success) { - if (fragment.requireContext().resources.getBoolean(R.bool.isTablet)) { - val intent = - Intent(fragment.requireContext(), AdministratorControlsActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - fragment.startActivity(intent) - } else { - val intent = Intent(fragment.requireContext(), ProfileListActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - fragment.startActivity(intent) - } + ProfileDeleteSuccessDialogFragment.createNewInstance() + .showNow( + fragment.childFragmentManager, + ProfileDeleteSuccessDialogFragment.DELETE_PROFILE_SUCCESS_DIALOG_FRAGMENT_TAG + ) } } ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c43c5e2c26..48ed440dcf6 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -347,7 +347,9 @@ All progress will be deleted and cannot be recovered. Delete Cancel + Profile deleted successfully. Allow Download Access + OK User is able to download and delete content without Administrator password Profile Picture diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt index d63a296f378..59751b29705 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt @@ -244,6 +244,10 @@ class ProfileEditActivityTest { .inRoot(isDialog()) .perform(click()) testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_success_dialog_positive_button)) + .inRoot(isDialog()) + .perform(click()) + testCoroutineDispatchers.runCurrent() if (context.resources.getBoolean(R.bool.isTablet)) { intended(hasComponent(AdministratorControlsActivity::class.java.name)) } else { @@ -266,6 +270,10 @@ class ProfileEditActivityTest { .inRoot(isDialog()) .perform(click()) testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_success_dialog_positive_button)) + .inRoot(isDialog()) + .perform(click()) + testCoroutineDispatchers.runCurrent() if (context.resources.getBoolean(R.bool.isTablet)) { intended(hasComponent(AdministratorControlsActivity::class.java.name)) } else { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt index b6ae6b60b22..326627850b7 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt @@ -125,7 +125,6 @@ import javax.inject.Singleton @LooperMode(LooperMode.Mode.PAUSED) @Config(application = ProfileEditFragmentTest.TestApplication::class, qualifiers = "port-xxhdpi") class ProfileEditFragmentTest { - @get:Rule val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() @@ -167,49 +166,68 @@ class ProfileEditFragmentTest { } @Test - fun testProfileEdit_startWithUserProfile_clickProfileDeletionButton_checkOpensDeletionDialog() { + fun testProfileEdit_clickProfileDeletion_checkOpensDeletionDialog_checkOpensSuccessDialog() { launchFragmentTestActivity(internalProfileId = 1).use { onView(withId(R.id.profile_delete_button)).perform(click()) onView(withText(R.string.profile_edit_delete_dialog_message)) - .inRoot(isDialog()).check(matches(isDisplayed())) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + onView(withText(R.string.profile_edit_delete_dialog_positive)).perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_successful_message)) + .inRoot(isDialog()) + .check(matches(isCompletelyDisplayed())) } } @Test @Config(qualifiers = "land") - fun testProfileEdit_configChange_startWithUserProfile_clickDelete_checkOpensDeletionDialog() { + fun testProfileEdit_configChange_clickDelete_checkOpensDeletionDialog_checkOpensSuccessDialog() { launchFragmentTestActivity(internalProfileId = 1).use { onView(isRoot()).perform(orientationLandscape()) onView(withId(R.id.profile_delete_button)).perform(scrollTo()).perform(click()) testCoroutineDispatchers.runCurrent() onView(withText(R.string.profile_edit_delete_dialog_message)) - .inRoot(isDialog()).check(matches(isDisplayed())) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + onView(withText(R.string.profile_edit_delete_dialog_positive)).perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_successful_message)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) } } @Test @Config(qualifiers = "land") - fun testProfileEdit_startWithUserProfile_clickDelete_configChange_checkDeletionDialogIsVisible() { + fun testProfileEdit_clickDelete_landscapeMode_checkOpensDeletionDialog() { launchFragmentTestActivity(internalProfileId = 1).use { onView(withId(R.id.profile_delete_button)).perform(scrollTo()).perform(click()) onView(isRoot()).perform(orientationLandscape()) testCoroutineDispatchers.runCurrent() onView(withText(R.string.profile_edit_delete_dialog_message)) - .inRoot(isDialog()).check(matches(isCompletelyDisplayed())) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + onView(withText(R.string.profile_edit_delete_dialog_positive)).perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.profile_edit_delete_successful_message)) + .inRoot(isDialog()) + .check(matches(isCompletelyDisplayed())) } } @Test fun testProfileEdit_startWithUserHasDownloadAccess_downloadsDisabled_switchIsNotDisplayed() { TestPlatformParameterModule.forceEnableDownloadsSupport(false) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_container)).check(matches(not(isDisplayed()))) } @@ -226,14 +244,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_startWithUserHasDownloadAccess_downloadsEnabled_checkSwitchIsChecked() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_switch)).check(matches(isChecked())) } @@ -243,14 +262,15 @@ class ProfileEditFragmentTest { @Config(qualifiers = "land") fun testProfileEdit_configChange_userHasDownloadAccess_downloadsEnabled_checkSwitchIsChecked() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - val addProfileProvider = profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ) + val addProfileProvider = + profileManagementController.addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ) monitorFactory.waitForNextSuccessfulResult(addProfileProvider) launchFragmentTestActivity(internalProfileId = 4).use { onView(isRoot()).perform(orientationLandscape()) @@ -261,14 +281,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_userHasDownloadAccess_downloadsEnabled_clickAllowDownloads_checkChanged() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_switch)).check(matches(isChecked())) onView(withId(R.id.profile_edit_allow_download_container)).perform(click()) @@ -279,14 +300,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_userDoesNotHaveDownloadAccess_downloadsEnabled_switchIsNotClickable() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = false, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = false, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_switch)).check(matches(not(isClickable()))) } @@ -295,14 +317,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_userHasDownloadAccess_downloadsEnabled_switchContainerIsFocusable() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_container)).check(matches(isFocusable())) } @@ -311,14 +334,15 @@ class ProfileEditFragmentTest { @Test fun testProfileEdit_startWithUserHasDownloadAccess_downloadsEnabled_switchContainerIsDisplayed() { TestPlatformParameterModule.forceEnableDownloadsSupport(true) - profileManagementController.addProfile( - name = "James", - pin = "123", - avatarImagePath = null, - allowDownloadAccess = true, - colorRgb = -10710042, - isAdmin = false - ).toLiveData() + profileManagementController + .addProfile( + name = "James", + pin = "123", + avatarImagePath = null, + allowDownloadAccess = true, + colorRgb = -10710042, + isAdmin = false, + ).toLiveData() launchFragmentTestActivity(internalProfileId = 4).use { onView(withId(R.id.profile_edit_allow_download_container)).check(matches(isDisplayed())) } @@ -365,11 +389,13 @@ class ProfileEditFragmentTest { launchFragmentTestActivity(internalProfileId = 0).use { onView(withId(R.id.profile_mark_chapters_for_completion_button)).perform(click()) - val args = MarkChaptersCompletedActivityParams.newBuilder().apply { - this.internalProfileId = 0 - this.showConfirmationNotice = true - } - .build() + val args = + MarkChaptersCompletedActivityParams + .newBuilder() + .apply { + this.internalProfileId = 0 + this.showConfirmationNotice = true + }.build() intended(hasComponent(MarkChaptersCompletedActivity::class.java.name)) intended(hasProtoExtra(MARK_CHAPTERS_COMPLETED_ACTIVITY_PARAMS, args)) } @@ -428,10 +454,11 @@ class ProfileEditFragmentTest { fun testProfileEdit_featureOn_hasSwitchingPermission_enableLanguageSwitchingIsOn() { TestPlatformParameterModule.forceEnableFastLanguageSwitchingInLesson(true) - val updateLangProvider = profileManagementController.updateEnableInLessonQuickLanguageSwitching( - profileId = ProfileId.newBuilder().apply { internalId = 0 }.build(), - allowInLessonQuickLanguageSwitching = true - ) + val updateLangProvider = + profileManagementController.updateEnableInLessonQuickLanguageSwitching( + profileId = ProfileId.newBuilder().apply { internalId = 0 }.build(), + allowInLessonQuickLanguageSwitching = true, + ) monitorFactory.waitForNextSuccessfulResult(updateLangProvider) // With the permission to switch languages, the setting should be on by default. @@ -450,7 +477,7 @@ class ProfileEditFragmentTest { // The user should not have permission to switch languages (since the switch wasn't toggled). val profileProvider = profileManagementController.getProfile( - ProfileId.newBuilder().apply { internalId = 0 }.build() + ProfileId.newBuilder().apply { internalId = 0 }.build(), ) val profile = monitorFactory.waitForNextSuccessfulResult(profileProvider) @@ -470,7 +497,7 @@ class ProfileEditFragmentTest { // The user should have permission to switch languages (since the switch was toggled). val profileProvider = profileManagementController.getProfile( - ProfileId.newBuilder().apply { internalId = 0 }.build() + ProfileId.newBuilder().apply { internalId = 0 }.build(), ) val profile = monitorFactory.waitForNextSuccessfulResult(profileProvider) assertThat(profile.allowInLessonQuickLanguageSwitching).isTrue() @@ -481,22 +508,26 @@ class ProfileEditFragmentTest { launchFragmentTestActivity(internalProfileId = 1).use { scenario -> scenario.onActivity { activity -> - val activityArgs = activity.intent.getProtoExtra( - ProfileEditActivity.PROFILE_EDIT_ACTIVITY_PARAMS_KEY, - ProfileEditActivityParams.getDefaultInstance() - ) + val activityArgs = + activity.intent.getProtoExtra( + ProfileEditActivity.PROFILE_EDIT_ACTIVITY_PARAMS_KEY, + ProfileEditActivityParams.getDefaultInstance(), + ) val isMultipane = activityArgs?.isMultipane ?: false - val fragment = activity.supportFragmentManager - .findFragmentById(R.id.profile_edit_fragment_placeholder) as ProfileEditFragment - - val arguments = checkNotNull(fragment.arguments) { - "Expected variables to be passed to ProfileEditFragment" - } - val args = arguments.getProto( - ProfileEditFragment.PROFILE_EDIT_FRAGMENT_ARGUMENTS_KEY, - ProfileEditFragmentArguments.getDefaultInstance() - ) + val fragment = + activity.supportFragmentManager + .findFragmentById(R.id.profile_edit_fragment_placeholder) as ProfileEditFragment + + val arguments = + checkNotNull(fragment.arguments) { + "Expected variables to be passed to ProfileEditFragment" + } + val args = + arguments.getProto( + ProfileEditFragment.PROFILE_EDIT_FRAGMENT_ARGUMENTS_KEY, + ProfileEditFragmentArguments.getDefaultInstance(), + ) val receivedInternalProfileId = args.internalProfileId val receivedIsMultipane = args.isMultipane @@ -508,7 +539,7 @@ class ProfileEditFragmentTest { private fun launchFragmentTestActivity(internalProfileId: Int) = launch( - createProfileEditFragmentTestActivity(context, internalProfileId) + createProfileEditFragmentTestActivity(context, internalProfileId), ).also { testCoroutineDispatchers.runCurrent() } @Singleton @@ -539,10 +570,9 @@ class ProfileEditFragmentTest { SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class, ActivityRouterModule::class, CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, - TestAuthenticationModule::class - ] + TestAuthenticationModule::class, + ], ) - interface TestApplicationComponent : ApplicationComponent { @Component.Builder interface Builder : ApplicationComponent.Builder { @@ -552,9 +582,13 @@ class ProfileEditFragmentTest { fun inject(profileEditFragmentTest: ProfileEditFragmentTest) } - class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + class TestApplication : + Application(), + ActivityComponentFactory, + ApplicationInjectorProvider { private val component: TestApplicationComponent by lazy { - DaggerProfileEditFragmentTest_TestApplicationComponent.builder() + DaggerProfileEditFragmentTest_TestApplicationComponent + .builder() .setApplication(this) .build() as TestApplicationComponent } @@ -562,9 +596,12 @@ class ProfileEditFragmentTest { fun inject(profileEditFragmentTest: ProfileEditFragmentTest) = component.inject(profileEditFragmentTest) - override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { - return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() - } + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent = + component + .getActivityComponentBuilderProvider() + .get() + .setActivity(activity) + .build() override fun getApplicationInjector(): ApplicationInjector = component } diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index 08f1cf99f8e..c358c7d68a4 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -2086,6 +2086,10 @@ test_file_exemption { exempted_file_path: "app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditActivity.kt" source_file_is_incompatible_with_code_coverage: true } +test_file_exemption { + exempted_file_path: "app/src/main/java/org/oppia/android/app/settings/profile/ProfileDeleteSuccessDialogFragment.kt" + test_file_not_required: true +} test_file_exemption { exempted_file_path: "app/src/main/java/org/oppia/android/app/settings/profile/ProfileEditActivityPresenter.kt" test_file_not_required: true From 42b1c8fe7accd0f2207c1e4ac60f705b5fbc3335 Mon Sep 17 00:00:00 2001 From: Tobiloba Oyelekan Date: Thu, 5 Dec 2024 08:50:31 +0100 Subject: [PATCH 3/5] Fix #5168: appcompat custom view fragment tag usage lint error (#5582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Explanation Fixes [#5168](https://github.com/oppia/oppia-android/issues/5186) 1. For `AppCompatCustomView`, replaced it with AppCompat Views 2. For `FragmentTagUsage`, replaced `` with . Made some changes to `NavigationDrawerFragment` to: - ensured `setUpDrawer()` is called first to initialize the `drawerLayout`, `toolbar` and `menuItemId` - followed by `onCreateView()` ensuring to bind the fragment - followed on `onViewCreated()` to setup drawer functions/listeners 3. Faced an issue similar to [drawer not working](https://github.com/oppia/oppia-android/issues/5266), by minimising and resuming the app, (the drawer button stopped working). - Fixed this by removing `onRestart()` from respective Activies making a call to `setUpNavigationDrawer()` which at that point is not necessary, since the fragment has been inflated initially, and would be re-inflated if the fragment is recreated. 4. Also tested this [bug](https://github.com/oppia/oppia-android/issues/5284) following the steps to reproduce. No issues encountered **Lint report (Before):** ![Screenshot 2024-11-24 at 6 11 50 AM](https://github.com/user-attachments/assets/479da7e9-ea3d-47d5-9c69-ff5c97992750) **Lint report (After):** ![Screenshot 2024-11-24 at 8 06 27 AM](https://github.com/user-attachments/assets/68ef6cb9-b9b6-45ac-a901-ed64540ee674) ## Screen Record https://github.com/user-attachments/assets/6db98657-d470-4263-a5ac-d055f4d64e66 ## 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)). --- app/BUILD.bazel | 1 + .../app/classroom/ClassroomListActivity.kt | 17 ++++++++-- .../ClassroomListActivityPresenter.kt | 5 --- .../FractionInputInteractionView.kt | 14 ++++---- .../MathExpressionInteractionsView.kt | 15 ++++---- .../NumericInputInteractionView.kt | 14 ++++---- .../interaction/RatioInputInteractionView.kt | 14 ++++---- .../interaction/TextInputInteractionView.kt | 14 ++++---- .../app/drawer/NavigationDrawerFragment.kt | 13 ++++++- .../NavigationDrawerFragmentPresenter.kt | 34 +++++++++---------- .../oppia/android/app/home/HomeActivity.kt | 5 --- .../android/app/home/HomeActivityPresenter.kt | 4 --- .../testing/NavigationDrawerTestActivity.kt | 5 --- .../administrator_controls_activity.xml | 2 +- .../main/res/layout-sw600dp/help_activity.xml | 2 +- .../res/layout-sw600dp/option_activity.xml | 2 +- .../administrator_controls_activity.xml | 2 +- .../res/layout/classroom_list_activity.xml | 2 +- .../res/layout/developer_options_activity.xml | 2 +- app/src/main/res/layout/help_activity.xml | 2 +- app/src/main/res/layout/home_activity.xml | 2 +- app/src/main/res/layout/option_activity.xml | 2 +- testing/BUILD.bazel | 1 + third_party/maven_install.json | 3 +- third_party/versions.bzl | 1 + 25 files changed, 93 insertions(+), 85 deletions(-) diff --git a/app/BUILD.bazel b/app/BUILD.bazel index cbeefc86af0..7405410cf3e 100644 --- a/app/BUILD.bazel +++ b/app/BUILD.bazel @@ -610,6 +610,7 @@ android_library( "//third_party:androidx_databinding_databinding-common", "//third_party:androidx_databinding_databinding-runtime", "//third_party:androidx_drawerlayout_drawerlayout", + "//third_party:androidx_fragment_fragment", "//third_party:androidx_lifecycle_lifecycle-livedata-core", "//third_party:androidx_recyclerview_recyclerview", "//third_party:androidx_viewpager2_viewpager2", diff --git a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivity.kt b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivity.kt index a158e5adc4a..f6160ec4ba7 100644 --- a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivity.kt +++ b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivity.kt @@ -72,9 +72,20 @@ class ClassroomListActivity : title = resourceHandler.getStringInLocale(R.string.classroom_list_activity_title) } - override fun onRestart() { - super.onRestart() - classroomListActivityPresenter.handleOnRestart() + override fun onBackPressed() { + val previousFragment = + supportFragmentManager.findFragmentByTag(TAG_SWITCH_PROFILE_DIALOG) + if (previousFragment != null) { + supportFragmentManager.beginTransaction().remove(previousFragment).commitNow() + } + val exitProfileDialogArguments = + ExitProfileDialogArguments + .newBuilder() + .setHighlightItem(HighlightItem.NONE) + .build() + val dialogFragment = ExitProfileDialogFragment + .newInstance(exitProfileDialogArguments = exitProfileDialogArguments) + dialogFragment.showNow(supportFragmentManager, TAG_SWITCH_PROFILE_DIALOG) } override fun routeToRecentlyPlayed(recentlyPlayedActivityTitle: RecentlyPlayedActivityTitle) { diff --git a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivityPresenter.kt index ea6a0d8df3f..0b8c1d78f1e 100644 --- a/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/classroom/ClassroomListActivityPresenter.kt @@ -31,11 +31,6 @@ class ClassroomListActivityPresenter @Inject constructor(private val activity: A } } - /** Handles the activity restart. Re-initializes the navigation drawer. */ - fun handleOnRestart() { - setUpNavigationDrawer() - } - private fun setUpNavigationDrawer() { val toolbar = activity.findViewById(R.id.classroom_list_activity_toolbar) as Toolbar activity.setSupportActionBar(toolbar) diff --git a/app/src/main/java/org/oppia/android/app/customview/interaction/FractionInputInteractionView.kt b/app/src/main/java/org/oppia/android/app/customview/interaction/FractionInputInteractionView.kt index eec5f745241..547b38feaf2 100644 --- a/app/src/main/java/org/oppia/android/app/customview/interaction/FractionInputInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/customview/interaction/FractionInputInteractionView.kt @@ -8,7 +8,7 @@ import android.view.KeyEvent.ACTION_UP import android.view.KeyEvent.KEYCODE_BACK import android.view.View import android.view.inputmethod.EditorInfo -import android.widget.EditText +import androidx.appcompat.widget.AppCompatEditText import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener import org.oppia.android.app.utility.KeyboardHelper.Companion.hideSoftKeyboard import org.oppia.android.app.utility.KeyboardHelper.Companion.showSoftKeyboard @@ -20,19 +20,19 @@ import org.oppia.android.app.utility.KeyboardHelper.Companion.showSoftKeyboard // background="@drawable/edit_text_background" // maxLength="200". -/** The custom EditText class for fraction input interaction view. */ +/** The custom AppCompatEditText class for fraction input interaction view. */ class FractionInputInteractionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = android.R.attr.editTextStyle -) : EditText(context, attrs, defStyle), View.OnFocusChangeListener { +) : AppCompatEditText(context, attrs, defStyle), View.OnFocusChangeListener { private var hintText: CharSequence = "" private val stateKeyboardButtonListener: StateKeyboardButtonListener init { onFocusChangeListener = this // Assume multi-line for the purpose of properly showing long hints. - setSingleLine(hint != null) + isSingleLine = hint != null stateKeyboardButtonListener = context as StateKeyboardButtonListener } @@ -64,12 +64,12 @@ class FractionInputInteractionView @JvmOverloads constructor( private fun hideHint() { hint = "" typeface = Typeface.DEFAULT - setSingleLine(true) + isSingleLine = true } private fun restoreHint() { hint = hintText - if (text.isEmpty()) setTypeface(typeface, Typeface.ITALIC) - setSingleLine(false) + if (text?.isEmpty() == true) setTypeface(typeface, Typeface.ITALIC) + isSingleLine = false } } diff --git a/app/src/main/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsView.kt b/app/src/main/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsView.kt index 00de2602d42..63416dd5196 100644 --- a/app/src/main/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsView.kt +++ b/app/src/main/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsView.kt @@ -6,7 +6,7 @@ import android.util.AttributeSet import android.view.KeyEvent import android.view.View import android.view.inputmethod.EditorInfo -import android.widget.EditText +import androidx.appcompat.widget.AppCompatEditText import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener import org.oppia.android.app.utility.KeyboardHelper.Companion.hideSoftKeyboard import org.oppia.android.app.utility.KeyboardHelper.Companion.showSoftKeyboard @@ -19,7 +19,7 @@ import org.oppia.android.app.utility.KeyboardHelper.Companion.showSoftKeyboard // maxLength="200". /** - * The custom [EditText] class for math expression interactions interaction view. + * The custom [AppCompatEditText] class for math expression interactions interaction view. * * Note that the hint should be set via [setPlaceholder] to ensure that it's properly initialized if * using databinding, otherwise setting the hint through android:hint should work fine. @@ -28,14 +28,14 @@ class MathExpressionInteractionsView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = android.R.attr.editTextStyle -) : EditText(context, attrs, defStyle), View.OnFocusChangeListener { +) : AppCompatEditText(context, attrs, defStyle), View.OnFocusChangeListener { private var hintText: CharSequence = "" private val stateKeyboardButtonListener: StateKeyboardButtonListener init { onFocusChangeListener = this // Assume multi-line for the purpose of properly showing long hints. - setSingleLine(hint != null) + isSingleLine = hint != null stateKeyboardButtonListener = context as StateKeyboardButtonListener } @@ -79,12 +79,13 @@ class MathExpressionInteractionsView @JvmOverloads constructor( private fun hideHint() { hint = "" typeface = Typeface.DEFAULT - setSingleLine(true) + isSingleLine = true } private fun restoreHint() { hint = hintText - if (text.isEmpty()) setTypeface(typeface, Typeface.ITALIC) - setSingleLine(false) + + if (text?.isEmpty() == true) setTypeface(typeface, Typeface.ITALIC) + isSingleLine = false } } diff --git a/app/src/main/java/org/oppia/android/app/customview/interaction/NumericInputInteractionView.kt b/app/src/main/java/org/oppia/android/app/customview/interaction/NumericInputInteractionView.kt index b6745cad7d9..9af64db917a 100644 --- a/app/src/main/java/org/oppia/android/app/customview/interaction/NumericInputInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/customview/interaction/NumericInputInteractionView.kt @@ -8,7 +8,7 @@ import android.view.KeyEvent.ACTION_UP import android.view.KeyEvent.KEYCODE_BACK import android.view.View import android.view.inputmethod.EditorInfo -import android.widget.EditText +import androidx.appcompat.widget.AppCompatEditText import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener import org.oppia.android.app.utility.KeyboardHelper.Companion.hideSoftKeyboard import org.oppia.android.app.utility.KeyboardHelper.Companion.showSoftKeyboard @@ -20,19 +20,19 @@ import org.oppia.android.app.utility.KeyboardHelper.Companion.showSoftKeyboard // background="@drawable/edit_text_background" // maxLength="200". -/** The custom EditText class for numeric input interaction view. */ +/** The custom AppCompatEditText class for numeric input interaction view. */ class NumericInputInteractionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = android.R.attr.editTextStyle -) : EditText(context, attrs, defStyle), View.OnFocusChangeListener { +) : AppCompatEditText(context, attrs, defStyle), View.OnFocusChangeListener { private val stateKeyboardButtonListener: StateKeyboardButtonListener private var hintText: CharSequence = "" init { onFocusChangeListener = this // Assume multi-line for the purpose of properly showing long hints. - setSingleLine(hint != null) + isSingleLine = hint != null stateKeyboardButtonListener = context as StateKeyboardButtonListener } @@ -64,12 +64,12 @@ class NumericInputInteractionView @JvmOverloads constructor( private fun hideHint() { hint = "" typeface = Typeface.DEFAULT - setSingleLine(true) + isSingleLine = true } private fun restoreHint() { hint = hintText - if (text.isEmpty()) setTypeface(typeface, Typeface.ITALIC) - setSingleLine(false) + if (text?.isEmpty() == true) setTypeface(typeface, Typeface.ITALIC) + isSingleLine = false } } diff --git a/app/src/main/java/org/oppia/android/app/customview/interaction/RatioInputInteractionView.kt b/app/src/main/java/org/oppia/android/app/customview/interaction/RatioInputInteractionView.kt index fd29579a1fd..1ad77b316b2 100644 --- a/app/src/main/java/org/oppia/android/app/customview/interaction/RatioInputInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/customview/interaction/RatioInputInteractionView.kt @@ -6,23 +6,23 @@ import android.util.AttributeSet import android.view.KeyEvent import android.view.View import android.view.inputmethod.EditorInfo -import android.widget.EditText +import androidx.appcompat.widget.AppCompatEditText import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener import org.oppia.android.app.utility.KeyboardHelper -/** The custom EditText class for ratio input interaction view. */ +/** The custom AppCompatEditText class for ratio input interaction view. */ class RatioInputInteractionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = android.R.attr.editTextStyle -) : EditText(context, attrs, defStyle), View.OnFocusChangeListener { +) : AppCompatEditText(context, attrs, defStyle), View.OnFocusChangeListener { private var hintText: CharSequence = "" private val stateKeyboardButtonListener: StateKeyboardButtonListener init { onFocusChangeListener = this // Assume multi-line for the purpose of properly showing long hints. - setSingleLine(hint != null) + isSingleLine = hint != null stateKeyboardButtonListener = context as StateKeyboardButtonListener } @@ -54,12 +54,12 @@ class RatioInputInteractionView @JvmOverloads constructor( private fun hideHint() { hint = "" typeface = Typeface.DEFAULT - setSingleLine(true) + isSingleLine = true } private fun restoreHint() { hint = hintText - if (text.isEmpty()) setTypeface(typeface, Typeface.ITALIC) - setSingleLine(false) + if (text?.isEmpty() == true) setTypeface(typeface, Typeface.ITALIC) + isSingleLine = false } } diff --git a/app/src/main/java/org/oppia/android/app/customview/interaction/TextInputInteractionView.kt b/app/src/main/java/org/oppia/android/app/customview/interaction/TextInputInteractionView.kt index 10001acd258..56217ef9fa4 100644 --- a/app/src/main/java/org/oppia/android/app/customview/interaction/TextInputInteractionView.kt +++ b/app/src/main/java/org/oppia/android/app/customview/interaction/TextInputInteractionView.kt @@ -6,7 +6,7 @@ import android.util.AttributeSet import android.view.KeyEvent import android.view.View import android.view.inputmethod.EditorInfo -import android.widget.EditText +import androidx.appcompat.widget.AppCompatEditText import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener import org.oppia.android.app.utility.KeyboardHelper.Companion.hideSoftKeyboard import org.oppia.android.app.utility.KeyboardHelper.Companion.showSoftKeyboard @@ -17,19 +17,19 @@ import org.oppia.android.app.utility.KeyboardHelper.Companion.showSoftKeyboard // background="@drawable/edit_text_background" // maxLength="200". -/** The custom EditText class for text input interaction view. */ +/** The custom AppCompatEditText class for text input interaction view. */ class TextInputInteractionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = android.R.attr.editTextStyle -) : EditText(context, attrs, defStyle), View.OnFocusChangeListener { +) : AppCompatEditText(context, attrs, defStyle), View.OnFocusChangeListener { private var hintText: CharSequence = "" private val stateKeyboardButtonListener: StateKeyboardButtonListener init { onFocusChangeListener = this // Assume multi-line for the purpose of properly showing long hints. - setSingleLine(hint != null) + isSingleLine = hint != null stateKeyboardButtonListener = context as StateKeyboardButtonListener } @@ -61,12 +61,12 @@ class TextInputInteractionView @JvmOverloads constructor( private fun hideHint() { hint = "" typeface = Typeface.DEFAULT - setSingleLine(true) + isSingleLine = true } private fun restoreHint() { hint = hintText - if (text.isEmpty()) setTypeface(typeface, Typeface.ITALIC) - setSingleLine(false) + if (text?.isEmpty() == true) setTypeface(typeface, Typeface.ITALIC) + isSingleLine = false } } diff --git a/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragment.kt b/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragment.kt index ee307e47738..9408ebe67df 100644 --- a/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragment.kt +++ b/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragment.kt @@ -17,6 +17,10 @@ class NavigationDrawerFragment : RouteToProfileProgressListener, ExitProfileDialogInterface { + private lateinit var drawerLayout: DrawerLayout + private lateinit var toolbar: Toolbar + private var menuItemId: Int = 0 + @Inject lateinit var navigationDrawerFragmentPresenter: NavigationDrawerFragmentPresenter @@ -33,10 +37,17 @@ class NavigationDrawerFragment : return navigationDrawerFragmentPresenter.handleCreateView(inflater, container) } - fun setUpDrawer(drawerLayout: DrawerLayout, toolbar: Toolbar, menuItemId: Int) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) navigationDrawerFragmentPresenter.setUpDrawer(drawerLayout, toolbar, menuItemId) } + fun setUpDrawer(drawerLayout: DrawerLayout, toolbar: Toolbar, menuItemId: Int) { + this.drawerLayout = drawerLayout + this.toolbar = toolbar + this.menuItemId = menuItemId + } + override fun routeToProfileProgress(profileId: Int) { navigationDrawerFragmentPresenter.openProfileProgress(profileId) } diff --git a/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt index f606bafca88..e2a6e44d157 100644 --- a/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/drawer/NavigationDrawerFragmentPresenter.kt @@ -109,13 +109,13 @@ class NavigationDrawerFragmentPresenter @Inject constructor( drawerLayout.closeDrawers() footerViewModel.isDeveloperOptionsSelected.set(true) val intent = starter.createIntent(activity, profileId) - fragment.activity!!.startActivity(intent) - if (previousMenuItemId == 0) fragment.activity!!.finish() + fragment.requireActivity().startActivity(intent) + if (previousMenuItemId == 0) fragment.requireActivity().finish() else if (previousMenuItemId != null && NavigationDrawerItem.valueFromNavId(previousMenuItemId!!) != NavigationDrawerItem.HOME ) { - fragment.activity!!.finish() + fragment.requireActivity().finish() } drawerLayout.closeDrawers() } @@ -148,13 +148,13 @@ class NavigationDrawerFragmentPresenter @Inject constructor( activity, profileId ) - fragment.activity!!.startActivity(intent) - if (previousMenuItemId == -1) fragment.activity!!.finish() + fragment.requireActivity().startActivity(intent) + if (previousMenuItemId == -1) fragment.requireActivity().finish() else if (previousMenuItemId != null && NavigationDrawerItem.valueFromNavId(previousMenuItemId!!) != NavigationDrawerItem.HOME ) { - fragment.activity!!.finish() + fragment.requireActivity().finish() } drawerLayout.closeDrawers() } @@ -240,7 +240,7 @@ class NavigationDrawerFragmentPresenter @Inject constructor( ClassroomListActivity.createClassroomListActivity(activity, profileId) else HomeActivity.createHomeActivity(activity, profileId) - fragment.activity!!.startActivity(intent) + fragment.requireActivity().startActivity(intent) drawerLayout.closeDrawers() } NavigationDrawerItem.OPTIONS -> { @@ -248,9 +248,9 @@ class NavigationDrawerFragmentPresenter @Inject constructor( activity, profileId, /* isFromNavigationDrawer= */ true ) - fragment.activity!!.startActivity(intent) + fragment.requireActivity().startActivity(intent) if (checkIfPreviousActivityShouldGetFinished(menuItemId)) { - fragment.activity!!.finish() + fragment.requireActivity().finish() } drawerLayout.closeDrawers() } @@ -259,18 +259,18 @@ class NavigationDrawerFragmentPresenter @Inject constructor( activity, profileId, /* isFromNavigationDrawer= */ true ) - fragment.activity!!.startActivity(intent) + fragment.requireActivity().startActivity(intent) if (checkIfPreviousActivityShouldGetFinished(menuItemId)) { - fragment.activity!!.finish() + fragment.requireActivity().finish() } drawerLayout.closeDrawers() } NavigationDrawerItem.DOWNLOADS -> { val intent = MyDownloadsActivity.createMyDownloadsActivityIntent(activity, internalProfileId) - fragment.activity!!.startActivity(intent) + fragment.requireActivity().startActivity(intent) if (checkIfPreviousActivityShouldGetFinished(menuItemId)) { - fragment.activity!!.finish() + fragment.requireActivity().finish() } drawerLayout.closeDrawers() } @@ -412,7 +412,7 @@ class NavigationDrawerFragmentPresenter @Inject constructor( ) { override fun onDrawerOpened(drawerView: View) { super.onDrawerOpened(drawerView) - fragment.activity!!.invalidateOptionsMenu() + fragment.requireActivity().invalidateOptionsMenu() StatusBarColor.statusBarColorUpdate( R.color.component_color_shared_slide_drawer_open_status_bar_color, activity, @@ -451,7 +451,7 @@ class NavigationDrawerFragmentPresenter @Inject constructor( ) { override fun onDrawerOpened(drawerView: View) { super.onDrawerOpened(drawerView) - fragment.activity!!.invalidateOptionsMenu() + fragment.requireActivity().invalidateOptionsMenu() StatusBarColor.statusBarColorUpdate( R.color.component_color_shared_slide_drawer_open_status_bar_color, activity, @@ -461,7 +461,7 @@ class NavigationDrawerFragmentPresenter @Inject constructor( override fun onDrawerClosed(drawerView: View) { super.onDrawerClosed(drawerView) - fragment.activity!!.invalidateOptionsMenu() + fragment.requireActivity().invalidateOptionsMenu() StatusBarColor.statusBarColorUpdate( R.color.component_color_shared_activity_status_bar_color, activity, @@ -473,7 +473,7 @@ class NavigationDrawerFragmentPresenter @Inject constructor( /* Synchronize the state of the drawer indicator/affordance with the linked [drawerLayout]. */ drawerLayout.post { drawerToggle.syncState() } if (previousMenuItemId != NavigationDrawerItem.HOME.ordinal && previousMenuItemId != -1) { - fragment.activity!!.finish() + fragment.requireActivity().finish() } } } diff --git a/app/src/main/java/org/oppia/android/app/home/HomeActivity.kt b/app/src/main/java/org/oppia/android/app/home/HomeActivity.kt index a0ce5607f6d..3858d9872d8 100644 --- a/app/src/main/java/org/oppia/android/app/home/HomeActivity.kt +++ b/app/src/main/java/org/oppia/android/app/home/HomeActivity.kt @@ -69,11 +69,6 @@ class HomeActivity : title = resourceHandler.getStringInLocale(R.string.home_activity_title) } - override fun onRestart() { - super.onRestart() - homeActivityPresenter.handleOnRestart() - } - override fun routeToTopic(internalProfileId: Int, classroomId: String, topicId: String) { startActivity( TopicActivity.createTopicActivityIntent(this, internalProfileId, classroomId, topicId) diff --git a/app/src/main/java/org/oppia/android/app/home/HomeActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/home/HomeActivityPresenter.kt index 83e65950b7f..b2d6990a902 100644 --- a/app/src/main/java/org/oppia/android/app/home/HomeActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/home/HomeActivityPresenter.kt @@ -38,10 +38,6 @@ class HomeActivityPresenter @Inject constructor(private val activity: AppCompatA } } - fun handleOnRestart() { - setUpNavigationDrawer() - } - private fun setUpNavigationDrawer() { val toolbar = activity.findViewById(R.id.home_activity_toolbar) as Toolbar activity.setSupportActionBar(toolbar) diff --git a/app/src/main/java/org/oppia/android/app/testing/NavigationDrawerTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/NavigationDrawerTestActivity.kt index 57e9f72bc8b..ba9d5751e76 100644 --- a/app/src/main/java/org/oppia/android/app/testing/NavigationDrawerTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/testing/NavigationDrawerTestActivity.kt @@ -59,11 +59,6 @@ class NavigationDrawerTestActivity : title = resourceHandler.getStringInLocale(R.string.home_activity_title) } - override fun onRestart() { - super.onRestart() - homeActivityPresenter.handleOnRestart() - } - override fun routeToTopic(internalProfileId: Int, classroomId: String, topicId: String) { startActivity( TopicActivity.createTopicActivityIntent(this, internalProfileId, classroomId, topicId) diff --git a/app/src/main/res/layout-sw600dp/administrator_controls_activity.xml b/app/src/main/res/layout-sw600dp/administrator_controls_activity.xml index 9b778df86f3..e853bff7469 100644 --- a/app/src/main/res/layout-sw600dp/administrator_controls_activity.xml +++ b/app/src/main/res/layout-sw600dp/administrator_controls_activity.xml @@ -98,7 +98,7 @@ app:layout_constraintTop_toBottomOf="@id/administrator_controls_activity_toolbar" /> - - - - - - - - - Date: Tue, 10 Dec 2024 04:46:40 +0530 Subject: [PATCH 4/5] Localisation updates from https://translatewiki.net. (#5588) Translation updates --- app/src/main/res/values-ar/strings.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 74898b487c0..d9db3cc570d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -361,7 +361,9 @@ سيتم حذف كل التقدم ولن تتمكن من استعادته. حذف إلغاء + تم حذف الملف الشخصي بنجاح. السماح بالوصول للتنزيل + حسنا المستخدم قادر على تنزيل ومسح المحتوى بدون كلمة السر الخاصة بالمشرف. صورة الملف الشخصي صورة الملف الشخصي @@ -466,6 +468,30 @@ أعلى أسفل %1$s %2$s + + صفر دقيقة + منذ دقيقة واحدة + دقيقتان + دقائق قليلة + دقائق كثيرة + منذ %s دقائق + + + صفر ساعة + منذ ساعة واحدة + ساعتان + ساعات قليلة + ساعات كثيرة + منذ %s ساعات + + + صفر يوم + منذ يوم واحد + يومان + أيام قليلة + أيام كثيرة + منذ %s أيام + topic_revision_recyclerview_tag ongoing_recycler_view_tag برجاء اختيار خيار واحد على الأقل. From edb01a868d07bcdee620425aa194d0f1184db7f8 Mon Sep 17 00:00:00 2001 From: Manas <119405883+manas-yu@users.noreply.github.com> Date: Tue, 10 Dec 2024 23:29:28 +0530 Subject: [PATCH 5/5] Fix #455: Improve Html parser tests (#5589) ## Explanation Fix #455 - `testHtmlContentReplace_removesUnwantedNewlines`: Ensures that unwanted newlines are removed from HTML content when parsed. - `testHtmlContent_withImageTag_trimsLeadingAndTrailingNewlines`: Verifies that leading and trailing newlines are trimmed from HTML content containing an image tag. ## 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)). --- .../android/app/parser/HtmlParserTest.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/app/src/sharedTest/java/org/oppia/android/app/parser/HtmlParserTest.kt b/app/src/sharedTest/java/org/oppia/android/app/parser/HtmlParserTest.kt index 1e1ca843c6a..9b3b36f2d6a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/parser/HtmlParserTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/parser/HtmlParserTest.kt @@ -427,6 +427,57 @@ class HtmlParserTest { assertThat(htmlResult.toString()).endsWith(" ") } + @Test + fun testHtmlContentParsing_removesUnwantedNewlines() { + val htmlParser = htmlParserFactory.create( + resourceBucketName, + entityType = "", + entityId = "", + imageCenterAlign = true, + displayLocale = appLanguageLocaleHandler.getDisplayLocale() + ) + val (_, htmlResult) = activityScenarioRule.scenario.runWithActivity { + val textView: TextView = it.findViewById(R.id.test_html_content_text_view) + val htmlResult = htmlParser.parseOppiaHtml( + "
  • \n\tThe counting numbers (1, 2, 3, 4, 5 ….)\n\n
  • \n\tHow to tell" + + " whether one counting number is bigger or smaller than another.\n\n
", + textView + ) + textView.text = htmlResult + return@runWithActivity textView to htmlResult + } + + assertThat(htmlResult.toString()).isEqualTo( + "The counting numbers (1, 2, 3, 4, 5 ….)\nHow to tell whether one counting " + + "number is bigger or smaller than another" + ) + } + + @Test + fun testHtmlContentParsing_withImageTag_trimsLeadingAndTrailingNewlines() { + val htmlParser = htmlParserFactory.create( + resourceBucketName, + entityType = "", + entityId = "", + imageCenterAlign = true, + displayLocale = appLanguageLocaleHandler.getDisplayLocale() + ) + val htmlResult = activityScenarioRule.scenario.runWithActivity { + val textView: TextView = it.findViewById(R.id.test_html_content_text_view) + return@runWithActivity htmlParser.parseOppiaHtml( + "\n" + + "\n", + textView + ) + } + val imageSpans = htmlResult.getSpansFromWholeString(ImageSpan::class) + assertThat(imageSpans).hasLength(1) + assertThat(imageSpans.first().source).isEqualTo("test.png") + + assertThat(htmlResult.toString().startsWith("\n")).isFalse() + assertThat(htmlResult.toString().endsWith("\n")).isFalse() + } + @Test fun testHtmlContent_changeDeviceToLtr_textViewDirectionIsSetToLtr() { val htmlParser = htmlParserFactory.create(