diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d2c51eaa7e2..fdf7a2db439 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -163,6 +163,7 @@ # Utilities that are primarily used for frontend/UI purposes. /utility/src/*/java/org/oppia/android/util/accessibility/ @oppia/android-frontend-reviewers /utility/src/*/java/org/oppia/android/util/statusbar/ @oppia/android-frontend-reviewers +/utility/src/main/java/org/oppia/android/util/enumfilter/ @oppia/android-frontend-reviewers /utility/src/*/java/org/oppia/android/util/extensions/ @oppia/android-frontend-reviewers /utility/src/*/java/org/oppia/android/util/parser/html @oppia/android-frontend-reviewers /utility/src/*/res/**/*.xml @oppia/android-frontend-reviewers diff --git a/.github/workflows/comment_coverage_report.yml b/.github/workflows/comment_coverage_report.yml index 16c1ae0da63..0cc70da29a2 100644 --- a/.github/workflows/comment_coverage_report.yml +++ b/.github/workflows/comment_coverage_report.yml @@ -17,6 +17,8 @@ jobs: check_code_coverage_completed: name: Check code coverage completed runs-on: ubuntu-latest + outputs: + conclusion: ${{ steps.wait-for-coverage.outputs.run-conclusion }} steps: - name: Wait for code coverage to complete id: wait-for-coverage @@ -27,6 +29,13 @@ jobs: allowed-conclusions: | success failure + action_required + + - name: Conclusion Analysis + if: steps.wait-for-coverage.outputs.run-conclusion == 'action_required' + run: | + echo "::error::First-time contributor workflows require manual approval. After approval, please re-run the comment coverage workflows to post the coverage report." + exit 1 comment_coverage_report: name: Comment Code Coverage Report @@ -60,7 +69,7 @@ jobs: const run = runs[0]; if(!run) { - core.setFailed('Could not find a succesful workflow run for the PR'); + core.setFailed('Could not find a successful workflow run for the PR'); return; } diff --git a/app/src/main/java/org/oppia/android/app/drawer/ExitProfileDialogFragment.kt b/app/src/main/java/org/oppia/android/app/drawer/ExitProfileDialogFragment.kt index 7900163e9a5..2dfdd918fc7 100644 --- a/app/src/main/java/org/oppia/android/app/drawer/ExitProfileDialogFragment.kt +++ b/app/src/main/java/org/oppia/android/app/drawer/ExitProfileDialogFragment.kt @@ -70,7 +70,6 @@ class ExitProfileDialogFragment : InjectableDialogFragment() { dialog.dismiss() } .setPositiveButton(R.string.home_activity_back_dialog_exit) { _, _ -> - // TODO(#3641): Investigate on using finish instead of intent. val intent = ProfileChooserActivity.createProfileChooserActivity(activity!!) if (!restoreLastCheckedItem) { intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) diff --git a/app/src/main/java/org/oppia/android/app/profile/AddProfileActivity.kt b/app/src/main/java/org/oppia/android/app/profile/AddProfileActivity.kt index 1acff5fd581..c7d0e60857c 100644 --- a/app/src/main/java/org/oppia/android/app/profile/AddProfileActivity.kt +++ b/app/src/main/java/org/oppia/android/app/profile/AddProfileActivity.kt @@ -44,10 +44,7 @@ class AddProfileActivity : InjectableAutoLocalizedAppCompatActivity() { } override fun onSupportNavigateUp(): Boolean { - // TODO(#3641): Investigate on using finish instead of intent. - val intent = Intent(this, ProfileChooserActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - startActivity(intent) + finish() return false } diff --git a/app/src/main/java/org/oppia/android/app/survey/surveyitemviewmodel/MarketFitItemsViewModel.kt b/app/src/main/java/org/oppia/android/app/survey/surveyitemviewmodel/MarketFitItemsViewModel.kt index e2b7fe43a5e..bef9347e3d0 100644 --- a/app/src/main/java/org/oppia/android/app/survey/surveyitemviewmodel/MarketFitItemsViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/survey/surveyitemviewmodel/MarketFitItemsViewModel.kt @@ -12,6 +12,7 @@ import org.oppia.android.app.survey.PreviousAnswerHandler import org.oppia.android.app.survey.SelectedAnswerAvailabilityReceiver import org.oppia.android.app.survey.SelectedAnswerHandler import org.oppia.android.app.translation.AppLanguageResourceHandler +import org.oppia.android.util.enumfilter.filterByEnumCondition import javax.inject.Inject /** [SurveyAnswerItemViewModel] for the market fit question options. */ @@ -98,8 +99,12 @@ class MarketFitItemsViewModel @Inject constructor( private fun getMarketFitOptions(): ObservableList { val appName = resourceHandler.getStringInLocale(R.string.app_name) val observableList = ObservableArrayList() - observableList += MarketFitAnswer.values() - .filter { it.isValid() } + val filteredmarketFitAnswer = filterByEnumCondition( + MarketFitAnswer.values().toList(), + { marketFitAnswer -> marketFitAnswer }, + { marketFitAnswer -> marketFitAnswer.isValid() } + ) + observableList += filteredmarketFitAnswer .mapIndexed { index, marketFitAnswer -> when (marketFitAnswer) { MarketFitAnswer.VERY_DISAPPOINTED -> MultipleChoiceOptionContentViewModel( diff --git a/app/src/main/java/org/oppia/android/app/survey/surveyitemviewmodel/UserTypeItemsViewModel.kt b/app/src/main/java/org/oppia/android/app/survey/surveyitemviewmodel/UserTypeItemsViewModel.kt index 14482c2775e..a03a78d10c8 100644 --- a/app/src/main/java/org/oppia/android/app/survey/surveyitemviewmodel/UserTypeItemsViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/survey/surveyitemviewmodel/UserTypeItemsViewModel.kt @@ -12,6 +12,7 @@ import org.oppia.android.app.survey.SelectedAnswerAvailabilityReceiver import org.oppia.android.app.survey.SelectedAnswerHandler import org.oppia.android.app.translation.AppLanguageResourceHandler import org.oppia.android.app.viewmodel.ObservableArrayList +import org.oppia.android.util.enumfilter.filterByEnumCondition import javax.inject.Inject /** [SurveyAnswerItemViewModel] for providing the type of user question options. */ @@ -97,46 +98,36 @@ class UserTypeItemsViewModel @Inject constructor( private fun getUserTypeOptions(): ObservableArrayList { val observableList = ObservableArrayList() - observableList += UserTypeAnswer.values() - .filter { it.isValid() } - .mapIndexed { index, userTypeOption -> - when (userTypeOption) { - UserTypeAnswer.LEARNER -> - MultipleChoiceOptionContentViewModel( - resourceHandler.getStringInLocale( - R.string.user_type_answer_learner - ), - index, - this - ) - UserTypeAnswer.TEACHER -> MultipleChoiceOptionContentViewModel( - resourceHandler.getStringInLocale( - R.string.user_type_answer_teacher - ), - index, - this - ) - - UserTypeAnswer.PARENT -> - MultipleChoiceOptionContentViewModel( - resourceHandler.getStringInLocale( - R.string.user_type_answer_parent - ), - index, - this - ) - - UserTypeAnswer.OTHER -> - MultipleChoiceOptionContentViewModel( - resourceHandler.getStringInLocale( - R.string.user_type_answer_other - ), - index, - this - ) - else -> throw IllegalStateException("Invalid UserTypeAnswer") - } + val filteredUserTypes = filterByEnumCondition( + UserTypeAnswer.values().toList(), + { userTypeAnswer -> userTypeAnswer }, + { userTypeAnswer -> userTypeAnswer.isValid() } + ) + observableList += filteredUserTypes.mapIndexed { index, userTypeOption -> + when (userTypeOption) { + UserTypeAnswer.LEARNER -> MultipleChoiceOptionContentViewModel( + resourceHandler.getStringInLocale(R.string.user_type_answer_learner), + index, + this + ) + UserTypeAnswer.TEACHER -> MultipleChoiceOptionContentViewModel( + resourceHandler.getStringInLocale(R.string.user_type_answer_teacher), + index, + this + ) + UserTypeAnswer.PARENT -> MultipleChoiceOptionContentViewModel( + resourceHandler.getStringInLocale(R.string.user_type_answer_parent), + index, + this + ) + UserTypeAnswer.OTHER -> MultipleChoiceOptionContentViewModel( + resourceHandler.getStringInLocale(R.string.user_type_answer_other), + index, + this + ) + else -> throw IllegalStateException("Invalid UserTypeAnswer") } + } return observableList } diff --git a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt index 66a5a32b2df..1f38909e9d3 100644 --- a/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentPresenter.kt @@ -30,6 +30,7 @@ import org.oppia.android.domain.oppialogger.OppiaLogger import org.oppia.android.util.accessibility.AccessibilityService import org.oppia.android.util.data.AsyncResult import org.oppia.android.util.data.DataProviders.Companion.toLiveData +import org.oppia.android.util.enumfilter.filterByEnumCondition import javax.inject.Inject /** The presenter for [TopicLessonsFragment]. */ @@ -161,18 +162,18 @@ class TopicLessonsFragmentPresenter @Inject constructor( val chapterSummaries = storySummaryViewModel .storySummary.chapterList - val completedChapterCount = - chapterSummaries.map(ChapterSummary::getChapterPlayState) - .filter { - it == ChapterPlayState.COMPLETED - } - .size + val completedChapterCount = filterByEnumCondition( + chapterSummaries.map(ChapterSummary::getChapterPlayState), + { it }, + { it == ChapterPlayState.COMPLETED } + ).size + val inProgressChapterCount = - chapterSummaries.map(ChapterSummary::getChapterPlayState) - .filter { - it == ChapterPlayState.IN_PROGRESS_SAVED - } - .size + filterByEnumCondition( + chapterSummaries.map(ChapterSummary::getChapterPlayState), + { it }, + { it == ChapterPlayState.IN_PROGRESS_SAVED } + ).size val storyPercentage: Int = (completedChapterCount * 100) / storySummaryViewModel.storySummary.chapterCount diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 37238d2f821..251a04d4bcd 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -24,13 +24,13 @@ تشغيل الصوت إيقاف الصوت مؤقتًا صوت %s غير متوفر. - موافق + نعم إلغاء لغة الصوت غير متصل بالإنترنت من فضلك تأكد من وجود اتصال بالإنترنت عن طريق شبكة الوايفاي أو بيانات الهاتف، ثم حاول مرة أخرى. - حسنًا - موافق + نعم + نعم إلغاء متصل بالإنترنت عن طريق بيانات الهاتف تشغيل الصوت عبر الإنترنت قد يستخدم الكثير من بيانات الهاتف. @@ -92,10 +92,10 @@ الموضوع: %s موضوع المواضيع قيد التقدم - فصل %s: %s - تم انهاء الفصل %s الذي يحمل عنوان %s - الفصل %s الذي يحمل عنوان %s ما زال قيد التقدم - قم بإنهاء الفصل %s: %s لكي تفتح هذا الفصل. + الفصل %1$s: %2$s + تم الانتهاء من الفصل %1$s بعنوان %2$s + الفصل %1$s بعنوان %2$s قيد التنفيذ + أكمل الفصل %1$s : %2$s لفتح هذا الفصل. أكمل الفصل السابق لفتح هذا الفصل. أدخل النص. أدخل كسر عشري في الصيغة (بسط/مقام)، أو عدد كسري في الصيغة (عدد بسط/مقام). @@ -150,7 +150,7 @@ المواضيع التي تم تنزيلها تم التنزيل وضع الممارسة - السؤال %s من أصل %s + السؤال %1$s من %2$s تمّ تم الإنتهاء تم الإنتهاء من جميع الأسئلة! يمكنك بدأ مجموعة أسئلة أخرى، أو العودة للموضوع. @@ -169,11 +169,11 @@ من فضلك إبدأ إجابتك برقم (0 او 0.5 على سبيل المثال) من فضلك قم بإدخال رقم صالح. (–يمكن أن تحتوي الإجابة على 15 رقمًا (0-9) على الأكثر أو الرموز (. أو - من فضلك قم بكتابة نسبة تتكون من أرقام مفصولة بنقطتين رأسيتين (1:2 أو 1:2:3 على سبيل المثال). - من فضلك أدخل نسبة صحيحة (1:2 أو 1:2:3 على سبيل المثال) - إجابتك تحتوي على نقطتين رأسيتين (:) متتاليتين. - عدد الحدود لا يساوي العدد المطلوب. - لا يمكن أن تحتوي النسب على 0 كعنصر. + يرجى كتابة نسبة تتكون من أرقام مفصولة بعلامات نقطية (على سبيل المثال 1:2 أو 1:2:3). + الرجاء إدخال نسبة صالحة (على سبيل المثال 1:2 أو 1:2:3). + إجابتك تحتوي على علامتي نقطتين (:) بجانب بعضهما البعض. + عدد المصطلحات لا يساوي عدد المصطلحات المطلوبة. + لا يمكن للنسب أن تحتوي على 0 كعنصر. الحجم مجهول %s بايت %s ك.ب @@ -181,7 +181,7 @@ %s ج.ب صحيح! الموضوع: %s - + و لا فصل فصلًا واحدًا فصلان @@ -189,7 +189,7 @@ %s من الفصول %s من الفصول - + و لا قصة قصة واحدة قصتان @@ -198,14 +198,14 @@ %s من القصص - و لا فصل مكتمل - فصل %s مكتمل من %s - فصلان %s مكتملان من %s - %sفصول مكتملة من %s - %s فصول مكتملة من %s - %s فصول مكتملة من %s + لا فصل + %1$s من %2$s فصل مكتمل + فصلان + فصول قليلة + فصول كثيرة + %1$s من %2$s فصل مكتمل - + و لا درس درسًا واحدًا درسان @@ -213,7 +213,7 @@ %s درس %s درس - + و لا قصة مكتملة قصة واحدة مكتملة قصتان مكتملتان @@ -221,7 +221,7 @@ %s قصة مكتملة %s قصة مكتملة - + و لا موضوع في تقدم موضوع واحد في تقدم موضوعان في تقدم @@ -286,7 +286,7 @@ تخطي التالي ابدأ - شاشة العرض %s من %s + الشريحة %1$s من %2$s أهلًا، %s! من فضلك أدخل رقم التعريف الشخصي الخاص بالمشرف. من فضلك أدخل رقم التعريف الشخصي الخاص بك. @@ -351,7 +351,7 @@ اختيار من المعرض إعادة تسمية الملف الشخصي الاسم الجديد - حفظ + يحفظ إعادة ضبط رقم التعريف الشخصي أدخل رقم تعريف شخصي جديد للمستخدم كي يستخدمه عند الدخول إلى ملفه الشخصي. رقم تعريف شخصي مكون من 3 أرقام @@ -384,12 +384,12 @@ تم تثبيت آخر إصدار في %s. قم باستخدام رقم الإصدار في الأعلى للإبلاغ عن أعطال التطبيق. إصدار التطبيق لغة التطبيق - لغة الصوت الافتراضية + لغة الصوت المفضلة حجم النص المقروء حجم النص المقروء سيظهر نص القصة بهذا الشكل. أ - الصوت الافتراضي + اللغة الصوتية المفضلة لغة التطبيق حجم النص المقروء صغير @@ -419,7 +419,7 @@ لا يوجد تلميح جديد متاح إظهار الملاحظات والحل تلميح %s - العودة للسابق + يغلق الملاحظات عرض الحل عرض الحل @@ -444,26 +444,26 @@ تحريك العنصر إلى الأعلى عند %s أعلى أسفل - %s %s + %1$s %2$s topic_revision_recyclerview_tag ongoing_recycler_view_tag برجاء اختيار خيار واحد على الأقل. - إصدار تطبيق غير مدعوم - هذا الإصدار من التطبيق لم يعد مدعومًا. من فضلك قم بتحديث التطبيق من خلال متجر بلاي (Play Store) - إغلاق التطبيق + إصدار التطبيق غير مدعوم + لم يعد هذا الإصدار من التطبيق مدعومًا. يرجى تحديثه عبر متجر Play. + إغلاق التطبيق بناء المطور ألفا بيتا إشعار بيتا مرحبًا! يتم الآن تحديث تطبيقك إلى الإصدار التجريبي. إذا واجهت مشاكل أثناء استخدام التطبيق ، أو كانت لديك أسئلة ، فيرجى الاتصال بنا على android-feedback@oppia.org. لا تظهر هذه الرسالة مرة أخرى - نعم + نعم إشعار التوفر العام مرحبًا! يتم الآن تحديث تطبيقك إلى إصدار التوفر العام. إذا واجهت مشاكل أثناء استخدام التطبيق ، أو كانت لديك أسئلة ، فيرجى الاتصال بنا على android-feedback@oppia.org. لا تظهر هذه الرسالة مرة أخرى - نعم - إلى - أدخل نسبة في الصيغة س:ص. + نعم + ل + أدخل النسبة بالصيغة x:y. انقر هنا لإدخال نص. أصغر حجمًا للنص أكبر حجمًا للنص @@ -502,26 +502,26 @@ ما هي %s؟ من هو المشرف؟ كيف يمكنني إنشاء ملف تعريف(حساب) جديد؟ - كيف يمكنني الحصول على التطبيق بلغتي؟ - وجدت خلل. كيف يمكنني الإبلاغ عنه؟ - لماذا هناك فقط دروس رياضيات؟ - هل ستقومون بإنشاء مزيد من الدروس؟ + كيف أحصل على التطبيق باللغة الخاصة بي؟ + لقد وجدت خطأً. كيف يمكنني الإبلاغ عنه؟ + لماذا هناك دروس الرياضيات فقط؟ + هل ستقوم بعمل المزيد من الدروس؟ لماذا لا يتم تحميل مشغل الاستكشاف؟ لماذا لا يتم تشغيل الصوت الخاص بي؟ كيف يمكنني حذف ملف التعريف(حساب)؟ - كيف يمكنني تحديث التطبيق؟ - كيف يمكنني تحديث نظام التشغيل Android الخاص بي؟ + كيف أقوم بتحديث التطبيق؟ + كيف أقوم بتحديث نظام التشغيل Android الخاص بي؟ لا أجد سؤالي هنا. ماذا الان؟ <p>%1$s <i>\"أو-بي-يا\"</i>(فنلندية) - \"للتعلم\"</p><p><br></p><p>%1$sمهمتنا هي مساعدة أي شخص على تعلم أي شيء يريده بطريقة فعالة وممتعة.</p><p><br></p><p>من خلال إنشاء مجموعة من الدروس المجانية عالية الجودة والفعالة بشكل واضح بمساعدة معلمين من جميع أنحاء العالم ، تهدف %1$s إلى تزويد الطلاب بتعليم جيد - بغض النظر عن مكان وجودهم أو الموارد التقليدية التي يمكنهم الوصول إليها.</p><p><br></p><p>كطالب ، يمكنك أن تبدأ مغامرتك التعليمية من خلال تصفح الموضوعات المدرجة في الصفحة الرئيسية!</p> - <p>المشرف هو المستخدم الرئيسي الذي يدير ملفات التعريف والإعدادات لكل ملف تعريف على حسابه. هم على الأرجح والدك أو معلمك أو وصي عليك الذي أنشأ هذا الملف الشخصي لك.</p><p><br></p><p>يمكن للمسؤولين إدارة الملفات الشخصية وتعيين أرقام التعريف الشخصية وتغيير الإعدادات الأخرى ضمن حساباتهم. بناءً على ملف التعريف الخاص بك ، قد تكون أذونات المسؤول مطلوبة لبعض الميزات مثل تنزيل الموضوعات وتغيير رقم التعريف الشخصي وغير ذلك.</p><p><br></p><p>لمعرفة من هو المسؤول لديك ، انتقل إلى منتقي الملف الشخصي. الملف الشخصي الأول المدرج ولديه \"المسؤول\" مكتوب باسمه هو المسؤول.</p> - <p>إذا كانت هذه هي المرة الأولى التي تنشئ فيها ملفًا شخصيًا وليس لديك رقم تعريف شخصي: <ol><li> من منتقي الملف الشخصي ، اضغط على<strong> قم بإعداد ملفات تعريف متعددة</strong></li><li> قم بإنشاء رقم تعريف شخصي و<strong>احفظ</strong></li><li> املأ جميع البيانات للملف الشخصي.<ol><li>(اختياري) قم بتحميل صورة.</li> <li>إدخال اسم.</li> <li>(اختياري) قم بتعيين رقم تعريف شخصي مكون من 3 أرقام.</li></ol></li><li> اضغط<strong>إنشاء</strong> . تمت إضافة هذا الملف الشخصي إلى منتقي ملف التعريف الخاص بك!</li></ol></p><p> إذا قمت بإنشاء ملف تعريف من قبل ولديك رقم تعريف شخصي:<ol><li> من منتقي الملف الشخصي ، اضغط على<strong>إضافة الملف الشخصي</strong></li> <li> أدخل رقم التعريف الشخصي الخاص بك وانقر فوق<strong>إرسال</strong></li><li> املأ جميع الحقول للملف الشخصي. <ol> <li>(اختياري) قم بتحميل صورة.</li> <li>إدخال اسم.</li> <li>(اختياري) قم بتعيين رقم تعريف شخصي مكون من 3 أرقام. </li></ol></li><li> اضغط<strong>إنشاء</strong> . تمت إضافة هذا الملف الشخصي إلى منتقي ملف التعريف الخاص بك!</li></ol></p><br><p> ملاحظة: فقط ال<u>مدير</u> قادر على إدارة الملفات الشخصية.</p> + <p>المدير هو المستخدم الرئيسي الذي يدير الملفات الشخصية وإعدادات كل ملف على حسابه. من المحتمل أن يكونوا والديك أو معلمك أو وليكك الشخص الذي قام بإنشاء هذا الملف الشخصي لك.</p><p><br/></p><p>لدى المديرين القدرة على إدارة الملفات الشخصية وتعيين الرمز الرقمية والتحديد الإعدادات الأخرى تحت حسابهم. اعتمادا على ملفك الشخصي، قد تكون هناك حاجة إلى إذنات الإدارة لميزات معينة مثل تغيير رمز الرقم الشخصي، وغيرها.</p><p><br/></p><p>لمعرفة من هو مديرك، اذهب إلى خيار الملفات الشخصية. أول ملف تعريف مدرج ويتم كتابة \"المنظم\" تحت اسمهم هو المدير.</p> + <p>إذا كانت هذه هي المرة الأولى التي تقوم فيها بإنشاء ملف تعريف و لم يكن لديك رمز PIN: من خيار الملف تعريف، اضغط على إعداد ملف تعبير عدة. إخلق رقم PIN و حفظ. املأ جميع الحقول للملف الشخصي. (اختياري) تحميل صورة. أدخل اسمك (اختياري) تعيين رقم PIN 3 رقمي. اضغط على \"إنشاء\" هذا الملف الشخصي يضاف إلى اختيار الملف الشخصى الخاص بك!<ol><li>من اختيار الملفات الشخصية، اضغط على إعداد الملفات الملفات الأخرى.</li><li><strong>إبق</strong> رقم PIN و حفظ.</li><li>املأ جميع الحقول للملف الشخصي. (اختياري) تحميل صورة. أدخل اسمك (اختياري) تعيين رقم PIN 3 رقمي.<ol><li> (اختياري) تحميل صورة.</li><li>أدخل اسمك</li><li> (اختياري) تعيين رقم PIN 3 رقمي.</li></ol></li><li>اضغط على \"إنشاء\" هذا الملف الشخصي يضاف إلى اختيار الملف الشخصى الخاص بك!</li></ol></p><p>إذا كنت قد أنشأت ملفاً ملفاَ ملفاُ ملفاٌ ملفاٍ ملفاِ ملفا ً ملفا (PIN): من خيار الملفاً الملفاَ، اضغط على إضافة الملفاُ الملفاِ. أدخل رقم رسومك وتقرّب على \"إرسال\". املأ جميع الحقول للملف الشخصي. (اختياري) تحميل صورة. أدخل اسمك (اختياري) تعيين رقم PIN 3 رقمي. اضغط على \"إنشاء\" هذا الملف الشخصي يضاف إلى اختيار الملف الشخصى الخاص بك!<ol><li>من اختيار الملفات الشخصية، اضغط على إضافة الملفات.</li><li>أدخل رقم رسومك وتقرّب على \"<strong>إرسال</strong>\".</li><li>املأ جميع الحقول للملف الشخصي. (اختياري) تحميل صورة. أدخل اسمك (اختياري) تعيين رقم PIN 3 رقمي.<ol><li> (اختياري) تحميل صورة.</li><li>أدخل اسمك</li><li> (اختياري) تعيين رقم PIN 3 رقمي.</li></ol></li><li>اضغط على \"إنشاء\" هذا الملف الشخصي يضاف إلى اختيار الملف الشخصى الخاص بك!</li></ol></p><p><br/></p><p>ملاحظة: إدارة الملفات الشخصية يمكن <u>المدير</u> فقط.</p> <p>التطبيق %s يدعم حاليا اللغة الإنجليزية والبرتغالية البرازيلية والعربية والسواحيلية والبيدجينية النيجيرية. اختر إحدى هذه اللغات من القائمة، في الخيارات. لطلب التطبيق باللغة التي تتحدث بها، يرجى الاتصال بنا على <strong>admin@oppia.org</strong>.</p> <p><ol><li>من خلال %s شاشة التطبيق الرئيسية، اضغط على القائمة في أعلى الزاوية اليسرى</li><li>اضغط على <strong>مشاركة التعليقات</strong>.</li><li>اتبع التعليمات للإبلاغ عن الأخطاء أو مشاركة التعليقات</li></ol></p> <p>%1$s مهمتها مساعدة المتعلمين على اكتساب المهارات الحياتية اللازمة. الرياضيات مهارة أساسية في الحياة اليومية1%$s سوف تقدم دروسا جديدة في العلوم وغيرها من المواد قريبا!</p> <p>أجل, %s سوف تقدم دروسا جديدة في العلوم وفي مواد أخرى قريبا. الرجاء زيارتنا مجددا لمعرفة كل ماهو جديد!</p> - <p>إذا لم يتم تحميل مشغل الاستكشاف</p><p><br></p><p>تحقق لمعرفة ما إذا كان التطبيق محدثًا أم لا:</p><p> <ul> <li> انتقل إلى متجر Play وتأكد من تحديث التطبيق إلى أحدث إصدار </li></ul><p><br></p><p>تحقق من اتصالك بالإنترنت:</p><ul><li> إذا كان اتصالك بالإنترنت بطيئًا ، فحاول إعادة الاتصال بشبكة Wi-Fi أو الاتصال بشبكة أخرى. </li></ul><p>اطلب من المشرف التحقق من أجهزتهم واتصال الإنترنت:</p><ul><li> اطلب من المشرف استكشاف الأخطاء وإصلاحها باستخدام الخطوات المذكورة أعلاه </li></ul><p>أخبرنا إذا كنت لا تزال تواجه مشكلات في التحميل:</p><ul><li> أبلغ عن مشكلة عن طريق الاتصال بنا على admin@oppia.org. </li></ul> - <p>إذا لم يتم تشغيل الصوت الخاص بك</p><p><br></p><p>تحقق لمعرفة ما إذا كان التطبيق محدثًا أم لا:</p><ul><li>انتقل إلى متجر Play وتأكد من تحديث التطبيق إلى أحدث إصدار</li></ul><p><br></p><p>تحقق من اتصالك بالإنترنت:</p><ul><li>إذا كان اتصالك بالإنترنت بطيئًا ، فحاول إعادة الاتصال بشبكة Wi-Fi أو الاتصال بشبكة أخرى. قد يتسبب الإنترنت البطيء في تحميل الصوت بشكل غير منتظم ، مما يجعل من الصعب تشغيله.</li></ul><p><br></p><p>اطلب من المسؤول التحقق من أجهزتهم واتصال الإنترنت:</p><ul><li>اطلب من المسؤول استكشاف الأخطاء وإصلاحها باستخدام الخطوات المذكورة أعلاه</li></ul><p><br></p><p>أخبرنا إذا كنت لا تزال تواجه مشكلات في التحميل:</p><ul><li>أبلغ عن مشكلة عن طريق الاتصال بنا على admin@oppia.org.</li></ul> - <p>بمجرد حذف ملف التعريف:</p><ol><li>لا يمكن استعادة ملف التعريف.</li><li>سيتم حذف معلومات الملف الشخصي مثل الاسم والصور والتقدم بشكل دائم.</li></ol><p>لحذف ملف تعريف (باستثناء<u>المسؤول</u>):</p><ol><li> من الصفحة الرئيسية للمسؤول ، اضغط على زر القائمة أعلى اليسار.</li><li> اضغط على<strong>ضوابط المسؤول</strong>. </li><li> اضغط على<strong>تحرير ملفات التعريف</strong>.</li><li> اضغط على الملف الشخصي الذي ترغب في حذفه.</li><li> في الجزء السفلي من الشاشة ، انقر فوق<strong>حذف الملف الشخصي</strong>. </li><li> اضغط<strong>حذف</strong>لتأكيد الحذف. </li></ol><p><br></p><p> ملاحظة:<u>المسؤول</u>فقط هو القادر على إدارة الملفات الشخصية.</p> + <p>إذا لم يتم تحميل جهاز التحقيق</p><p><br/></p><p>تحقق من تحديث التطبيق:</p><ul><li>اذهب إلى متجر Play وتأكد من تحديث التطبيق إلى أحدث إصدار</li></ul><p><br/></p><p>تحقق من اتصالك بالإنترنت:</p><ul><li>إذا كان اتصال الإنترنت بطيء، حاول إعادة الاتصال بشبكة واي فاي الخاصة بك أو الاتصال بشبك آخر.</li></ul><p>اطلب من المدير التحقق من جهازك و اتصالك بالإنترنت:</p><ul><li>احصل على المدير لإصلاح المشاكل باستخدام الخطوات أعلاه</li></ul><p><br/></p><p>أخبرنا إذا كان لديك مشاكل مع الحملة:</p><ul><li>إبلاغ عن مشكلة عن طريق الاتصال بنا على admin@oppia.org.</li></ul> + <p>إذا كان صوتك لا يعزف</p><p><br/></p><p>تحقق من تحديث التطبيق:</p><ul><li>اذهب إلى متجر Play وتأكد من تحديث التطبيق إلى أحدث إصدار</li></ul><p><br/></p><p>تحقق من اتصالك بالإنترنت:</p><ul><li>إذا كان اتصال الإنترنت بطيء، حاول إعادة الاتصال بشبكة واي فاي الخاصة بك أو الاتصال بشبك آخر. إن الإنترنت البطيء قد يسبب تحميل الصوت بشكل غير منتظم مما يجعل من الصعب تشغيله.</li></ul><p><br/></p><p>اطلب من المدير التحقق من جهازك و اتصالك بالإنترنت:</p><ul><li>احصل على المدير لإصلاح المشاكل باستخدام الخطوات أعلاه</li></ul><p><br/></p><p>أخبرنا إذا كان لديك مشاكل مع الحملة:</p><ul><li>إبلاغ عن مشكلة عن طريق الاتصال بنا على admin@oppia.org.</li></ul> + <p>بمجرد حذف الملف الشخصي:</p><ol><li>لا يمكن استعادة الملف</li><li>سيتم حذف معلومات الملف الشخصي مثل الاسم والصور والتقدم بشكل دائم.</li></ol><p>لإزالة ملف تعريف (باستثناء <u>المدير</u>):</p><ol><li>من صفحة الرئيسية للمدير، اضغط على زر القائمة في الجزء العلوي اليسرى.</li><li>اضغط على <strong>التحكمات الإدارية</strong></li><li>اضغط على <strong>تحرير الملفات الشخصية</strong>.</li><li>اضغط على الملف الشخصي الذي تريد حذفه.</li><li>في أسفل الشاشة، اضغط على <strong>حذف الملف الشخصي</strong>.</li><li>اضغط على حذف لتأكيد الحذف.</li></ol><p><br/></p><p>ملاحظة: إدارة الملفات الشخصية يمكن <u>المدير</u> فقط.</p> <p><ol><li>افتح تطبيق Google Play Store</li><li>ابحث عن %s التطبيق</li><li>اضغط على تحديث</li></ol></p> <p><ol><li>اضغط على تطبيق الإعدادات في هاتفك</li><li>اضغط على تحديثات النظام</li><li>اضغط على تحديثات النظام واتبع التعليمات لتحديث نظام تشغيل Android الخاص بك</li></ol></p> <p> إذا لم تتمكن من العثور على سؤالك أو كنت ترغب في الإبلاغ عن خطأ ، فاتصل بنا على admin@oppia.org. </p> diff --git a/app/src/main/res/values-pcm-rNG/strings.xml b/app/src/main/res/values-pcm-rNG/strings.xml index 36fdceba18c..0774dae0c11 100644 --- a/app/src/main/res/values-pcm-rNG/strings.xml +++ b/app/src/main/res/values-pcm-rNG/strings.xml @@ -94,11 +94,11 @@ Topic: %s Topic Topics wey dey in Progress - Chapter %s: %s - Chapter %s with title %s don complete - Chapter %s with title %s dey in progress - Complete Chapter %s: %s to unlock dis chapter. - Chapter %s: %s dey locked currently. Abeg complete chapter %s: %s to fit unlock dis chapter. + Chapter %s: %s + Chapter %s with title %s don complete + Chapter %s with title %s dey in progress + Complete Chapter %s: %s to unlock dis chapter. + Chapter %s: %s dey locked currently. Abeg complete chapter %s: %s to fit unlock dis chapter. Finish the chapter wey dey before to fit open dis chapter Enter text. Enter fraction wey dey in di form x/x, or mixed nomba wey dey in di form x x/x. @@ -153,7 +153,7 @@ Topic Wey You Don Download You don download am Practice Mode - Question %s of %s + Question %s of %s Complete Finished You don finish all of di questions! You fit choose to play anoda set of questions, or go back to di topic. @@ -192,7 +192,7 @@ 1 Story %s Stories - + %s of %s Chapter Completed %s of %s Chapters Completed @@ -267,7 +267,7 @@ Skip Next Get Started - Slide %s of %s + Slide %s of %s Hi, %s! Abeg put your Administrator PIN. Abeg put your PIN. @@ -426,7 +426,7 @@ Move item up to %s Up Down - %s %s + %s %s 0 minutes ago a minute ago diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a514d6dc65e..63042bc8413 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -104,11 +104,11 @@ Tópico: %s Tópico Tópicos em Andamento - Capítulo %s: %s - O capítulo %s com o título %s foi concluído - O capítulo %s com o título %s está em andamento - Conclua o capítulo %s: %s para desbloquear este capítulo. - O capítulo %s: %s está bloqueado no momento. Conclua o capítulo %s: %s para desbloquear esse capítulo. + Capítulo %s: %s + O capítulo %s com o título %s foi concluído + O capítulo %s com o título %s está em andamento + Conclua o capítulo %s: %s para desbloquear este capítulo. + O capítulo %s: %s está bloqueado no momento. Conclua o capítulo %s: %s para desbloquear esse capítulo. Conclua o capítulo anterior para desbloquear este capítulo. Inserir texto. Insira uma fração na forma x/x, ou um número misto na forma x x/x. @@ -166,7 +166,7 @@ Tópico Baixado Baixado Modo de Prática - Questão %s de %s + Questão %s de %s Concluído Concluído Você concluiu todas as perguntas! Você pode escolher reproduzir outro conjunto de perguntas ou retornar ao tópico. @@ -210,7 +210,7 @@ %s História %s Histórias - + %s de %s Capítulo Concluído %s de %s Capítulos Concluídos @@ -283,7 +283,7 @@ Pular Próximo Começar - Slide %s de %s + Slide %s de %s Olá, %s! Por favor, insira o PIN do Administrador. Por favor, insira seu PIN. @@ -445,7 +445,7 @@ Mover o item para cima para %s Para cima Para baixo - %s %s + %s %s topic_revision_recyclerview_tag ongoing_recycler_view_tag Por favor, selecione todas as alternativas corretas. diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml index ddb536c4e3e..f827cae27ef 100644 --- a/app/src/main/res/values-sw/strings.xml +++ b/app/src/main/res/values-sw/strings.xml @@ -77,10 +77,10 @@ mada: %s mada mada zinazoendelea - Sura ya %s: %s - Sura ya %s yenye kichwa %s imekamilika - Sura ya %s yenye kichwa %s inaendelea - Kamilisha Sura ya %s: %s ili kufungua sura hii. + Sura ya %s: %s + Sura ya %s yenye kichwa %s imekamilika + Sura ya %s yenye kichwa %s inaendelea + Kamilisha Sura ya %s: %s ili kufungua sura hii. Ingiza maandishi. Ingiza sehemu katika mpangilio huu x/x, au nambari iliyochanganywa katika mpangilio huu x/x. Ingiza sehemu katika mpangilio huu X/X. @@ -134,7 +134,7 @@ Mada Imepakuliwa Imepakuliwa Hali ya Mazoezi - Swali %s kati ya %s + Swali %s kati ya %s imekamilika imekamilika Umemaliza maswali yote! Unaweza kuchagua kucheza seti nyingine ya maswali, au kurudi kwenye mada. @@ -173,7 +173,7 @@ Hadithi 1 Hadithi %s - + %s kati ya Sura %s Imekamilika %s ya Sura %s Imekamilika @@ -238,7 +238,7 @@ Ruka Inayofuata Anza - Slaidi %s ya %s + Slaidi %s ya %s Habari, %s! Tafadhali weka Nambari yako ya Siri ya Msimamizi. Tafadhali weka Nambari yako ya Siri. @@ -375,7 +375,7 @@ Hamisha kipengee juu hadi %s Juu Chini - %s %s + %s %s mada_marudio_mtazamo wa kuchakata tena_tag Inaendelea_kuchakata tena_mtazamo_tag Tafadhali chagua angalau chaguo moja. diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt index 0152d1056d7..b9aa2114cd6 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt @@ -13,6 +13,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.test.core.app.ActivityScenario.launch import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.closeSoftKeyboard import androidx.test.espresso.action.ViewActions.pressImeActionButton @@ -1748,6 +1749,15 @@ class AddProfileActivityTest { assertThat(currentScreenName).isEqualTo(ScreenName.ADD_PROFILE_ACTIVITY) } + @Test + fun testAddProfileActivity_onBackPressed_finishActivity() { + val scenario = launch(AddProfileActivity::class.java) + onView(isRoot()).perform(ViewActions.pressBack()) + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + assertThat(activity.isFinishing).isTrue() + } + } private fun createAddProfileActivityIntent(): Intent { return AddProfileActivity.createAddProfileActivityIntent( ApplicationProvider.getApplicationContext(), diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/BUILD.bazel b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/BUILD.bazel index f27fbd4b280..b5c7b54bf0b 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/BUILD.bazel +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/BUILD.bazel @@ -49,6 +49,7 @@ kt_android_library( "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", "//model/src/main/proto:performance_metrics_event_logger_java_proto_lite", "//utility/src/main/java/org/oppia/android/util/data:data_provider", + "//utility/src/main/java/org/oppia/android/util/enumfilter:enum_filter_util", "//utility/src/main/java/org/oppia/android/util/logging:console_logger", "//utility/src/main/java/org/oppia/android/util/logging:exception_logger", "//utility/src/main/java/org/oppia/android/util/logging/performancemetrics:performance_metrics_assessor", diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsController.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsController.kt index f518ff6121e..242eccbe1d3 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsController.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/PerformanceMetricsController.kt @@ -7,6 +7,7 @@ import org.oppia.android.app.model.ScreenName import org.oppia.android.data.persistence.PersistentCacheStore import org.oppia.android.domain.oppialogger.PerformanceMetricsLogStorageCacheSize import org.oppia.android.util.data.DataProvider +import org.oppia.android.util.enumfilter.filterByEnumCondition import org.oppia.android.util.logging.ConsoleLogger import org.oppia.android.util.logging.ExceptionLogger import org.oppia.android.util.logging.performancemetrics.PerformanceMetricsAssessor @@ -128,9 +129,11 @@ class PerformanceMetricsController @Inject constructor( * priority is returned. */ private fun getLeastRecentMetricLogIndex(oppiaMetricLogs: OppiaMetricLogs): Int? = - oppiaMetricLogs.oppiaMetricLogList.withIndex() - .filter { it.value.priority == Priority.LOW_PRIORITY } - .minByOrNull { it.value.timestampMillis }?.index + filterByEnumCondition( + oppiaMetricLogs.oppiaMetricLogList.withIndex().toList(), + { it.value.priority }, + { it == Priority.LOW_PRIORITY } + ).minByOrNull { it.value.timestampMillis }?.index ?: getLeastRecentMediumPriorityEventIndex(oppiaMetricLogs) /** @@ -142,9 +145,11 @@ class PerformanceMetricsController @Inject constructor( * priority is returned. */ private fun getLeastRecentMediumPriorityEventIndex(oppiaMetricLogs: OppiaMetricLogs): Int? = - oppiaMetricLogs.oppiaMetricLogList.withIndex() - .filter { it.value.priority == Priority.MEDIUM_PRIORITY } - .minByOrNull { it.value.timestampMillis }?.index + filterByEnumCondition( + oppiaMetricLogs.oppiaMetricLogList.withIndex().toList(), + { it.value.priority }, + { it == Priority.MEDIUM_PRIORITY } + ).minByOrNull { it.value.timestampMillis }?.index ?: getLeastRecentGeneralEventIndex(oppiaMetricLogs) /** Returns the index of the least recent event regardless of their priority. */ diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index 2f4c4422ce9..1ea6be33967 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -4310,6 +4310,10 @@ test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/extensions/ContextExtensions.kt" test_file_not_required: true } +test_file_exemption { + exempted_file_path: "utility/src/main/java/org/oppia/android/util/enumfilter/EnumFilterUtil.kt" + test_file_not_required: true +} test_file_exemption { exempted_file_path: "utility/src/main/java/org/oppia/android/util/extensions/StringExtensions.kt" source_file_is_incompatible_with_code_coverage: true diff --git a/scripts/assets/todo_open_exemptions.textproto b/scripts/assets/todo_open_exemptions.textproto index 8dd53a51ce9..035e618e357 100644 --- a/scripts/assets/todo_open_exemptions.textproto +++ b/scripts/assets/todo_open_exemptions.textproto @@ -333,6 +333,11 @@ todo_open_exemption { line_number: 689 line_number: 737 line_number: 741 + line_number: 790 + line_number: 791 + line_number: 792 + line_number: 798 + line_number: 800 } todo_open_exemption { exempted_file_path: "scripts/static_checks.sh" diff --git a/scripts/src/java/org/oppia/android/scripts/common/GitHubClient.kt b/scripts/src/java/org/oppia/android/scripts/common/GitHubClient.kt index fd7cc105037..23348c05136 100644 --- a/scripts/src/java/org/oppia/android/scripts/common/GitHubClient.kt +++ b/scripts/src/java/org/oppia/android/scripts/common/GitHubClient.kt @@ -68,13 +68,14 @@ class GitHubClient( val call = gitHubService.fetchOpenIssues(repoOwner, repoName, authorizationBearer, pageNumber) // Deferred blocking I/O operation to the dedicated I/O dispatcher. val response = withContext(Dispatchers.IO) { call.execute() } + check(response.isSuccessful()) { "Failed to fetch issues at page $pageNumber: ${response.code()}\n${call.request()}" + "\n${response.errorBody()}." } return@async checkNotNull(response.body()) { "No issues response from GitHub for page: $pageNumber." - } + }.filter { it.pullRequest == null } } } diff --git a/scripts/src/java/org/oppia/android/scripts/common/model/GitHubIssue.kt b/scripts/src/java/org/oppia/android/scripts/common/model/GitHubIssue.kt index 7a824fc3120..67b814440fa 100644 --- a/scripts/src/java/org/oppia/android/scripts/common/model/GitHubIssue.kt +++ b/scripts/src/java/org/oppia/android/scripts/common/model/GitHubIssue.kt @@ -10,4 +10,18 @@ import com.squareup.moshi.JsonClass * 'issues/' in an issue's GitHub URL) */ @JsonClass(generateAdapter = true) -data class GitHubIssue(@Json(name = "number") val number: Int) +data class GitHubIssue( + @Json(name = "number") val number: Int, + @Json(name = "pull_request") val pullRequest: PullRequestInfo? = null +) + +/** + * Data class representing information about a pull request associated with a GitHub issue. + * + * @property url the URL of the pull request, if it exists. This provides the link to the specific + * pull request associated with the issue on GitHub. + */ +@JsonClass(generateAdapter = true) +data class PullRequestInfo( + @Json(name = "url") val url: String? = null +) diff --git a/scripts/src/javatests/org/oppia/android/scripts/todo/TodoOpenCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/todo/TodoOpenCheckTest.kt index b614bc361d7..b5a3f783b70 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/todo/TodoOpenCheckTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/todo/TodoOpenCheckTest.kt @@ -777,11 +777,67 @@ class TodoOpenCheckTest { assertThat(outContent.toString().trim()).isEqualTo(failureMessage) } - private fun setUpGitHubService(issueNumbers: List) { - val issueJsons = issueNumbers.joinToString(separator = ",") { "{\"number\":$it}" } + @Test + fun testTodoCheck_PrPresent_checkShouldFail() { + setUpGitHubService( + issueNumbers = listOf(11004, 11003, 11002, 11001), + pullRequestNumbers = listOf(11005) + ) + val tempFile1 = tempFolder.newFile("testfiles/TempFile1.kt") + val tempFile2 = tempFolder.newFile("testfiles/TempFile2.kt") + val testContent1 = + """ + // TODO(#11002): test summary 1. + # TODO(#11004): test summary 2. + # TODO(#11001): test summary 3. + test Todo + test TODO + """.trimIndent() + val testContent2 = + """ + // TODO(#11005): test summary 3. + todo + + + """.trimIndent() + tempFile1.writeText(testContent1) + tempFile2.writeText(testContent2) + + val exception = assertThrows() { runScript() } + + assertThat(exception).hasMessageThat().contains(TODO_SYNTAX_CHECK_FAILED_OUTPUT_INDICATOR) + val failureMessage = + """ + TODOs not corresponding to open issues on GitHub: + - TempFile2.kt:1 + + $wikiReferenceNote + + $regenerateNote + """.trimIndent() + assertThat(outContent.toString().trim()).isEqualTo(failureMessage) + } + + private fun setUpGitHubService( + issueNumbers: List, + pullRequestNumbers: List = emptyList() + ) { + // Create JSON for issues with "pull_request" set to null + val issueJsons = issueNumbers + .joinToString(separator = ",") { "{\"number\":$it,\"pull_request\":null}" } + + // Create JSON for pull requests with "pull_request" as an empty object + val pullRequestJsons = pullRequestNumbers + .joinToString(separator = ",") { "{\"number\":$it,\"pull_request\":{}}" } + + // Combine issues and pull requests into one JSON array + val combinedJsons = + "[$issueJsons${if (pullRequestNumbers.isNotEmpty()) ", $pullRequestJsons" else ""}]" + val mockWebServer = MockWebServer() mockWebServer.enqueue(MockResponse().setBody("[$issueJsons]")) - mockWebServer.enqueue(MockResponse().setBody("[]")) // No more issues. + mockWebServer.enqueue(MockResponse().setBody(combinedJsons)) + mockWebServer.enqueue(MockResponse().setBody("[]")) GitHubClient.remoteApiUrl = mockWebServer.url("/").toString() } diff --git a/utility/src/main/java/org/oppia/android/util/enumfilter/BUILD.bazel b/utility/src/main/java/org/oppia/android/util/enumfilter/BUILD.bazel new file mode 100644 index 00000000000..876c262fe1a --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/enumfilter/BUILD.bazel @@ -0,0 +1,13 @@ +""" +General purpose utility for filtering enums. +""" + +load("@io_bazel_rules_kotlin//kotlin:android.bzl", "kt_android_library") + +kt_android_library( + name = "enum_filter_util", + srcs = [ + "EnumFilterUtil.kt", + ], + visibility = ["//:oppia_api_visibility"], +) diff --git a/utility/src/main/java/org/oppia/android/util/enumfilter/EnumFilterUtil.kt b/utility/src/main/java/org/oppia/android/util/enumfilter/EnumFilterUtil.kt new file mode 100644 index 00000000000..6c2aae441d6 --- /dev/null +++ b/utility/src/main/java/org/oppia/android/util/enumfilter/EnumFilterUtil.kt @@ -0,0 +1,20 @@ +package org.oppia.android.util.enumfilter + +/** + * Filters a collection based on a condition applied to an enum property of each element. + * + * @param E the type of enum values. + * @param T the type of elements in the collection. + * @param collection the collection of elements to filter. + * @param enumExtractor a function that extracts the enum value from each element. + * @param condition a predicate function that determines if an enum value should be included in the result. + * @return a list of elements from the collection that satisfy the condition when their enum property is evaluated. + */ + +inline fun , T> filterByEnumCondition( + collection: Collection, + enumExtractor: (T) -> E, + condition: (E) -> Boolean +): List { + return collection.filter { condition(enumExtractor(it)) } +} diff --git a/utility/src/main/java/org/oppia/android/util/logging/ConsoleLogger.kt b/utility/src/main/java/org/oppia/android/util/logging/ConsoleLogger.kt index 2b1cd2637a1..3cd6f8707e1 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/ConsoleLogger.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/ConsoleLogger.kt @@ -11,6 +11,8 @@ import org.oppia.android.app.model.EventLog.ConsoleLoggerContext import org.oppia.android.util.locale.OppiaLocale import org.oppia.android.util.threading.BlockingDispatcher import java.io.File +import java.io.FileWriter +import java.io.PrintWriter import javax.inject.Inject import javax.inject.Singleton @@ -33,6 +35,8 @@ class ConsoleLogger @Inject constructor( */ val logErrorMessagesFlow: SharedFlow = _logErrorMessagesFlow + private var printWriter: PrintWriter? = null + /** Logs a verbose message with the specified tag. */ fun v(tag: String, msg: String) { writeLog(LogLevel.VERBOSE, tag, msg) @@ -73,12 +77,12 @@ class ConsoleLogger @Inject constructor( writeError(LogLevel.WARNING, tag, msg, tr) } - /** Logs a error message with the specified tag. */ + /** Logs an error message with the specified tag. */ fun e(tag: String, msg: String) { writeLog(LogLevel.ERROR, tag, msg) } - /** Logs a error message with the specified tag, message and exception. */ + /** Logs an error message with the specified tag, message and exception. */ fun e(tag: String, msg: String, tr: Throwable?) { writeError(LogLevel.ERROR, tag, msg, tr) } @@ -109,7 +113,7 @@ class ConsoleLogger @Inject constructor( } // Add the log to the error message flow so it can be logged to firebase. - CoroutineScope(blockingDispatcher).launch { + blockingScope.launch { // Only log error messages to firebase. if (logLevel == LogLevel.ERROR) { _logErrorMessagesFlow.emit( @@ -124,10 +128,17 @@ class ConsoleLogger @Inject constructor( } /** - * Writes the specified text line to file in a background thread to ensure that saving messages don't block the main - * thread. A blocking dispatcher is used to ensure messages are written in order. + * Writes the specified text line to file in a background thread to ensure that saving messages + * doesn't block the main thread. A blocking dispatcher is used to ensure messages are written + * in order. */ private fun logToFileInBackground(text: String) { - blockingScope.launch { logDirectory.printWriter().use { out -> out.println(text) } } + blockingScope.launch { + if (printWriter == null) { + printWriter = PrintWriter(FileWriter(logDirectory, true)) // Open in append mode. + } + printWriter?.println(text) + printWriter?.flush() + } } } diff --git a/utility/src/test/java/org/oppia/android/util/logging/ConsoleLoggerTest.kt b/utility/src/test/java/org/oppia/android/util/logging/ConsoleLoggerTest.kt index 6a314de75fe..05df6b8c3c7 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/ConsoleLoggerTest.kt +++ b/utility/src/test/java/org/oppia/android/util/logging/ConsoleLoggerTest.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -31,6 +32,8 @@ import org.oppia.android.util.data.DataProvidersInjectorProvider import org.oppia.android.util.locale.testing.LocaleTestModule import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode +import java.io.File +import java.io.PrintWriter import javax.inject.Inject import javax.inject.Singleton @@ -54,9 +57,31 @@ class ConsoleLoggerTest { @field:[Inject BackgroundTestDispatcher] lateinit var backgroundTestDispatcher: TestCoroutineDispatcher + private lateinit var logFile: File + @Before fun setUp() { setUpTestApplicationComponent() + logFile = File(context.filesDir, "oppia_app.log") + logFile.delete() + } + + @After + fun tearDown() { + logFile.delete() + } + + @Test + fun testConsoleLogger_multipleLogCalls_appendsToFile() { + consoleLogger.e(testTag, testMessage) + consoleLogger.e(testTag, "$testMessage 2") + testCoroutineDispatchers.advanceUntilIdle() + + val logContent = logFile.readText() + assertThat(logContent).contains(testMessage) + assertThat(logContent).contains("$testMessage 2") + assertThat(logContent.indexOf(testMessage)) + .isLessThan(logContent.indexOf("$testMessage 2")) } @Test @@ -76,6 +101,26 @@ class ConsoleLoggerTest { assertThat(firstErrorContext.fullErrorLog).isEqualTo(testMessage) } + @Test + fun testConsoleLogger_closeAndReopen_continuesToAppend() { + consoleLogger.e(testTag, "first $testMessage") + testCoroutineDispatchers.advanceUntilIdle() + + // Force close the PrintWriter to simulate app restart + val printWriterField = ConsoleLogger::class.java.getDeclaredField("printWriter") + printWriterField.isAccessible = true + (printWriterField.get(consoleLogger) as? PrintWriter)?.close() + printWriterField.set(consoleLogger, null) + + consoleLogger.e(testTag, "first $testMessage") + consoleLogger.e(testTag, "second $testMessage") + testCoroutineDispatchers.advanceUntilIdle() + + val logContent = logFile.readText() + assertThat(logContent).contains("first $testMessage") + assertThat(logContent).contains("second $testMessage") + } + private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) } @@ -84,9 +129,7 @@ class ConsoleLoggerTest { class TestModule { @Provides @Singleton - fun provideContext(application: Application): Context { - return application - } + fun provideContext(application: Application): Context = application @Provides @Singleton @@ -96,7 +139,7 @@ class ConsoleLoggerTest { @Provides @Singleton @EnableFileLog - fun provideEnableFileLog(): Boolean = false + fun provideEnableFileLog(): Boolean = true @Provides @Singleton @@ -113,7 +156,6 @@ class ConsoleLoggerTest { FakeOppiaClockModule::class, ] ) - interface TestApplicationComponent : DataProvidersInjector { @Component.Builder interface Builder { diff --git a/wiki/Accessibility-A11y-Guide.md b/wiki/Accessibility-A11y-Guide.md index a9e01c6c59e..d80d73f6efd 100644 --- a/wiki/Accessibility-A11y-Guide.md +++ b/wiki/Accessibility-A11y-Guide.md @@ -32,7 +32,7 @@ There are various manual and automated tests to check if app is accessible by al **[Accessibility Scanner](https://support.google.com/accessibility/android/answer/6376570?hl=en)** : Using Accessibility Scanner we can take screenshots of each and every screen in the Oppia-app manually and the Accessibility Scanner app will give the output for the individual screenshot mentioning all the errors. -[Here](https://youtu.be/LF5AgGI5H3A) is a video tutorial on how to set up and use the Accessibility Scanner. +[Here](https://youtu.be/zCIlMlbFf7I?si=Xn1L5iCJ-TJ4DRVq) is a video tutorial on how to set up and use the Accessibility Scanner. **Screen Reader**: Screen readers like **Talkback** can be used to test the app manually. Talkback app is used by blind people to navigate to different items in the screen and get audio based output. This app will not give any error like Accessibility Scanner. @@ -80,7 +80,7 @@ TalkBack is the Google **screen reader** included on Android devices. TalkBack g 5. Read all the instructions written on the screen as using Talkback requires specific steps. 6. Turn on **Use Service** -> **Allow** -[Here](https://youtu.be/xpIM9xlowjs) is a video tutorial on "How to use Talkback and what does its output mean?". +[Here](https://youtu.be/JBRV1dauxyI?si=VzrxFJSpU9r3pdqq) is a video tutorial on "How to use Talkback and what does its output mean?". ### Useful Resources * [Android A11Y Overview](https://support.google.com/accessibility/android/answer/6006564) @@ -89,9 +89,9 @@ TalkBack is the Google **screen reader** included on Android devices. TalkBack g * [Display speech output as Text: Talkback](https://developer.android.com/guide/topics/ui/accessibility/testing#optional_talkback_developer_settings) ### Developer Videos -* [How to use Accessibility Scanner? - Tutorial](https://youtu.be/LF5AgGI5H3A) +* [How to use Accessibility Scanner? - Tutorial](https://youtu.be/zCIlMlbFf7I?si=Xn1L5iCJ-TJ4DRVq) * [Presentation Slides](https://docs.google.com/presentation/d/1PM_gs3TV2LVKFv6WuF9CUQHWbK7koepAxypzxeZTFzE/edit?usp=sharing) -* [How to use Talkback and what does its output mean? - Tutorial](https://youtu.be/xpIM9xlowjs) +* [How to use Talkback and what does its output mean? - Tutorial](https://youtu.be/JBRV1dauxyI?si=VzrxFJSpU9r3pdqq) * [Presentation Slides](https://docs.google.com/presentation/d/17SeKJLKT-rUNa_Yupe97bMFSsjTNzp83jX-lZPKEtnQ/edit?usp=sharing) ## Using AccessibilityTestRule in Espresso Tests diff --git a/wiki/Interpreting-CI-Results.md b/wiki/Interpreting-CI-Results.md index 00b3c5667c7..14293e02f55 100644 --- a/wiki/Interpreting-CI-Results.md +++ b/wiki/Interpreting-CI-Results.md @@ -21,4 +21,4 @@ Example in the below check the second job has some error or failure Navigate to logs or search the keyword ‘error’ to find the error message to understand what might have caused the failure in the checks. ### Developer Video - Understanding CI check failures -Learn how to interpret and troubleshoot oppia-android CI check failures in this insightful [developer video](https://youtu.be/I2bRf6fvgJ0?si=35sAagbUFSk6bOBA). \ No newline at end of file +Learn how to interpret and troubleshoot oppia-android CI check failures in this insightful [developer video](https://youtu.be/HLzHQULZbJE?si=RLY9_8Yzv5cjYM7q). \ No newline at end of file diff --git a/wiki/Static-Analysis-Checks.md b/wiki/Static-Analysis-Checks.md index 372a9b27bc1..6cfd8b2e022 100644 --- a/wiki/Static-Analysis-Checks.md +++ b/wiki/Static-Analysis-Checks.md @@ -267,4 +267,4 @@ To fix failing tests from GitHub CI individually, follow the steps below. Note: Before running the script command in your local terminal, make sure you have Bazel installed. To learn how to set up Bazel for Oppia Android, follow these [instructions](https://github.com/oppia/oppia-android/wiki/Oppia-Bazel-Setup-Instructions). Also make sure you have oppia-android-tools installed since static checks rely on these tools to be able to perform some of the checks. To install oppia-android-tools, run `bash scripts/setup.sh` in the oppia-android directory. ### Developer Video - Understanding CI check failures -Learn how to interpret and troubleshoot oppia-android CI check failures in this insightful [developer video](https://youtu.be/I2bRf6fvgJ0?si=35sAagbUFSk6bOBA). \ No newline at end of file +Learn how to interpret and troubleshoot oppia-android CI check failures in this insightful [developer video](https://youtu.be/HLzHQULZbJE?si=RLY9_8Yzv5cjYM7q). \ No newline at end of file