diff --git a/app/src/main/res/layout-land/coming_soon_topic_view.xml b/app/src/main/res/layout-land/coming_soon_topic_view.xml
deleted file mode 100644
index 6bac60e0d09..00000000000
--- a/app/src/main/res/layout-land/coming_soon_topic_view.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout-land/profile_chooser_fragment.xml b/app/src/main/res/layout-land/profile_chooser_fragment.xml
index 595e923267a..3d9a06b1bc4 100644
--- a/app/src/main/res/layout-land/profile_chooser_fragment.xml
+++ b/app/src/main/res/layout-land/profile_chooser_fragment.xml
@@ -53,7 +53,7 @@
android:id="@+id/profile_select_text"
style="@style/Heading1"
android:layout_marginStart="36dp"
- android:layout_marginTop="64dp"
+ android:layout_marginTop="@dimen/profile_chooser_fragment_profile_select_text_margin_top"
android:layout_marginEnd="36dp"
android:text="@string/profile_chooser_select"
android:textColor="@color/white"
@@ -65,7 +65,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
- android:layout_marginTop="12dp"
+ android:layout_marginTop="@dimen/profile_chooser_fragment_profile_recycler_view_margin_top"
android:layout_marginEnd="16dp"
android:clipToPadding="false"
android:fadingEdge="horizontal"
diff --git a/app/src/main/res/layout-land/profile_chooser_profile_view.xml b/app/src/main/res/layout-land/profile_chooser_profile_view.xml
index daad93ec975..a884798d94f 100644
--- a/app/src/main/res/layout-land/profile_chooser_profile_view.xml
+++ b/app/src/main/res/layout-land/profile_chooser_profile_view.xml
@@ -89,6 +89,7 @@
android:id="@+id/add_profile_divider_view"
android:layout_width="1dp"
android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/profile_chooser_profile_view_view_margin_top"
android:layout_gravity="bottom"
android:background="@color/oppiaProfileChooserDivider"
android:visibility="@{hasProfileEverBeenAddedValue ? View.GONE : View.VISIBLE}"
diff --git a/app/src/main/res/layout-land/promoted_story_card.xml b/app/src/main/res/layout-land/promoted_story_card.xml
deleted file mode 100755
index 4b9951015cf..00000000000
--- a/app/src/main/res/layout-land/promoted_story_card.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout-sw600dp-land/coming_soon_topic_view.xml b/app/src/main/res/layout-sw600dp-land/coming_soon_topic_view.xml
deleted file mode 100644
index 6bac60e0d09..00000000000
--- a/app/src/main/res/layout-sw600dp-land/coming_soon_topic_view.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout-sw600dp-land/promoted_story_card.xml b/app/src/main/res/layout-sw600dp-land/promoted_story_card.xml
deleted file mode 100644
index f8ff4daf685..00000000000
--- a/app/src/main/res/layout-sw600dp-land/promoted_story_card.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout-sw600dp-port/coming_soon_topic_view.xml b/app/src/main/res/layout-sw600dp-port/coming_soon_topic_view.xml
deleted file mode 100644
index ec35dab92ff..00000000000
--- a/app/src/main/res/layout-sw600dp-port/coming_soon_topic_view.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout-sw600dp-port/promoted_story_card.xml b/app/src/main/res/layout-sw600dp-port/promoted_story_card.xml
deleted file mode 100644
index 34780cdd4a4..00000000000
--- a/app/src/main/res/layout-sw600dp-port/promoted_story_card.xml
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/coming_soon_topic_view.xml b/app/src/main/res/layout/coming_soon_topic_view.xml
index cfc347ce6ed..5e1535834a7 100644
--- a/app/src/main/res/layout/coming_soon_topic_view.xml
+++ b/app/src/main/res/layout/coming_soon_topic_view.xml
@@ -13,11 +13,11 @@
android:id="@+id/topic_container"
android:layout_width="144dp"
android:layout_height="wrap_content"
- app:layoutMarginEnd="@{viewModel.endMargin}"
- app:cardCornerRadius="4dp">
+ app:cardCornerRadius="4dp"
+ app:layoutMarginEnd="@{viewModel.endMargin}">
diff --git a/app/src/main/res/layout/profile_chooser_fragment.xml b/app/src/main/res/layout/profile_chooser_fragment.xml
index 1c8f504dfbc..bba2aeb6c10 100644
--- a/app/src/main/res/layout/profile_chooser_fragment.xml
+++ b/app/src/main/res/layout/profile_chooser_fragment.xml
@@ -53,7 +53,7 @@
android:id="@+id/profile_select_text"
style="@style/Heading1"
android:layout_marginStart="36dp"
- android:layout_marginTop="64dp"
+ android:layout_marginTop="@dimen/profile_chooser_fragment_profile_select_text_margin_top"
android:layout_marginEnd="36dp"
android:text="@string/profile_chooser_select"
android:textColor="@color/white"
@@ -65,7 +65,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="32dp"
- android:layout_marginTop="36dp"
+ android:layout_marginTop="@dimen/profile_chooser_fragment_profile_recycler_view_margin_top"
android:layout_marginEnd="32dp"
android:clipToPadding="false"
android:fadingEdge="horizontal"
diff --git a/app/src/main/res/layout/profile_chooser_profile_view.xml b/app/src/main/res/layout/profile_chooser_profile_view.xml
index 3b4abd6dba5..6d5d24a109f 100644
--- a/app/src/main/res/layout/profile_chooser_profile_view.xml
+++ b/app/src/main/res/layout/profile_chooser_profile_view.xml
@@ -96,7 +96,7 @@
android:id="@+id/add_profile_divider_view"
android:layout_width="match_parent"
android:layout_height="1dp"
- android:layout_marginTop="60dp"
+ android:layout_marginTop="@dimen/profile_chooser_profile_view_view_margin_top"
android:background="@color/oppiaProfileChooserDivider"
android:visibility="@{hasProfileEverBeenAddedValue ? View.GONE : View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/main/res/layout/promoted_story_card.xml b/app/src/main/res/layout/promoted_story_card.xml
index b0d418b0917..9239643c554 100755
--- a/app/src/main/res/layout/promoted_story_card.xml
+++ b/app/src/main/res/layout/promoted_story_card.xml
@@ -12,9 +12,9 @@
+ android:textSize="@dimen/promoted_story_card_text_size" />
أعلى قائمة التنقل
@@ -65,7 +66,7 @@
الأسئلة المتكررة
الأسئلة المتكررة
التحقق من رقم التعريف الشخصي
- مقدمة عن أوبيا
+ مقدمة
الأسئلة الأكثر تكرارا
معلومات
الدروس
@@ -185,7 +186,7 @@
الحقول المعلمة بـ* مطلوبة.
صورة الملف الشخصي الحالية.
تعديل صورة الملف الشخصي
- مرحبًا بكم في أوبيا!
+ مرحبًا بكم في %s
تعلّم أي شيءٍ تريده بطريقة فعّالة وممتعة.
أضف مستخدمين إلى حسابك.
شارك الخبرة وأنشئ حتى 10 ملفات شخصية.
@@ -207,7 +208,7 @@
إغلاق
تم تغيير رقم التعريف الشخصي بنجاح
نسيت رقم التعريف الشخصي؟
- لإعادة ضبط رقم التعريف الشخصي الخاص بك، برجاء حذف تطبيق أوبيا وإعادة تثبيته مرة أخرى.\n\nبرجاء العلم أنه في حالة عدم اتصال الجهاز بالإنترنت قد تفقد تقدم المستخدم في عدة حسابات.
+ لإعادة ضبط رقم التعريف الشخصي PIN الخاص بك، إحذف تطبيق %s وأعد تثبيته مرة أخرى.\n\nضع في اعتبارك أنه إذا لم يكن الجهاز متصلاً بالإنترنت ، فقد تفقد تقدم المستخدم في حسابات متعددة.
الذهاب إلى متجر بلاي (Play Store).
إظهار/إخفاء أيقونة كلمة السر
أيقونة كلمة المرور ظاهرة.
@@ -363,6 +364,7 @@
تمييز القصص كمكتملة
تمييز المواضيع كمكتملة
عرض سجلات الأحداث (Event Logs)
+ فرض نوع الشبكة
تعديل تقدم الدرس
تمييز الفصول كمكتملة
تمييز القصص كمكتملة
@@ -375,8 +377,40 @@
تعطيل التطبيق
الكل
اكتمل التحديد
+ تم تحديد الشبكة
+ افتراضي
+ الويفي
+ شبكة الهاتف
+ لا يوجد شبكة
مكتبات برمجية (Third-party Dependencies)
إصدار %s
رخص حقوق النسخ
عارض رخصة حقوق النسخ
+ انتقل مرة أخرى إلى %s
+ قائمة تبعيات الطرف الثالث
+ قائمة تراخيص حقوق النشر
+ استئناف الدرس
+ تابع
+ ابدأ من جديد
+ صباح الخير
+ مساء الخير
+ مساء الخير،
+ كيف يمكنني إنشاء ملف تعريف(حساب) جديد؟
+ كيف يمكنني حذف ملف التعريف(حساب)؟
+ كيف يمكنني تغيير بريدي الإلكتروني / رقم هاتفي؟
+ ما هي %s؟
+ من هو المشرف؟
+ لماذا لا يتم تحميل مشغل الاستكشاف؟
+ لماذا لا يتم تشغيل الصوت الخاص بي؟
+ كيف يمكنني تنزيل موضوع؟
+ لا أجد سؤالي هنا. ماذا الان؟
+ <p>إذا كانت هذه هي المرة الأولى التي تنشئ فيها ملفًا شخصيًا وليس لديك رقم تعريف شخصي: </p> \n<p> 1. من منتقي الملف الشخصي ، اضغط على\n<strong> قم بإعداد ملفات تعريف متعددة</strong>\n</p>\n<p> 2. قم بإنشاء رقم تعريف شخصي و\n<strong>احفظ</strong>\n</p> \n<p> 3. املأ جميع البيانات للملف الشخصي.</p> \n<ol> \n<li>(اختياري) قم بتحميل صورة.</li> \n<li>إدخال اسم.</li> \n<li>(اختياري) قم بتعيين رقم تعريف شخصي مكون من 3 أرقام.</li> \n</ol> \n<p> 4. اضغط\n<strong>إنشاء</strong> . تمت إضافة هذا الملف الشخصي إلى منتقي ملف التعريف الخاص بك!\n<br/> \n<br/> إذا قمت بإنشاء ملف تعريف من قبل ولديك رقم تعريف شخصي:\n</p>\n<p> 1. من منتقي الملف الشخصي ، اضغط على\n<strong>إضافة الملف الشخصي</strong>\n</p> \n<p> 2. أدخل رقم التعريف الشخصي الخاص بك وانقر فوق\n<strong>إرسال</strong>\n</p> \n<p>3. املأ جميع الحقول للملف الشخصي.</p> \n<ol> \n<li>(اختياري) قم بتحميل صورة.</li> \n<li>إدخال اسم.</li> \n<li>(اختياري) قم بتعيين رقم تعريف شخصي مكون من 3 أرقام.</li> \n</ol> \n<p> 4. اضغط\n<strong>إنشاء</strong> . تمت إضافة هذا الملف الشخصي إلى منتقي ملف التعريف الخاص بك!\n<br/><br/> ملاحظة: فقط ال\n<u>مدير</u> قادر على إدارة الملفات الشخصية.\n</p>
+ <p>بمجرد حذف ملف التعريف:</p>\n<p><br></p> \n<p>\n<li>لا يمكن استعادة ملف التعريف.</li>\n</p>\n<p><li>سيتم حذف معلومات الملف الشخصي مثل الاسم والصور والتقدم بشكل دائم.</li></p>\n<p><br></p>\n<p>لحذف ملف تعريف (باستثناء<u>المسؤول</u></p>\n<p>1. من الصفحة الرئيسية للمسؤول ، اضغط على زر القائمة أعلى اليسار.</p>\n<p>2. اضغط على<strong>ضوابط المسؤول</strong></p>\n<p>3. اضغط على<strong>تحرير ملفات التعريف</strong></p>\n<p>4. اضغط على الملف الشخصي الذي ترغب في حذفه.</p>\n<p>5. في الجزء السفلي من الشاشة ، انقر فوق<strong>حذف الملف الشخصي</strong></p>\n<p>6. اضغط<strong>حذف</strong>لتأكيد الحذف.</p>\n<p><br></p>\n<p>ملاحظة:<u>المسؤول</u>فقط هو القادر على إدارة الملفات الشخصية.</p>
+ <p>لتغيير بريدك الإلكتروني / رقم هاتفك:</p> <p>1. من الصفحة الرئيسية للمشرف ، اضغط على زر القائمة أعلى اليسار.</p> <p>2. اضغط على <strong> عناصر تحكم المسؤول </ strong>.</p> <p>3. اضغط على <strong> تعديل الحساب </ strong>.</p> <p><br></p> <p>إذا كنت تريد تغيير بريدك الإلكتروني:</p> <p>4. أدخل بريدك الإلكتروني الجديد وانقر على <strong> حفظ </ strong>.</p> <p>5. يتم إرسال رابط التأكيد لتأكيد بريدك الإلكتروني الجديد. ستنتهي صلاحية الرابط بعد 24 ساعة ويجب النقر عليه لربطه بحسابك.</p> <p><br></p> <p>في حالة تغيير رقم هاتفك: </ p> <p> 4. أدخل رقم هاتفك الجديد وانقر على <strong> تحقق </ strong>.</p> <p>5. يتم إرسال رمز لتأكيد رقمك الجديد. ستنتهي صلاحية الرمز بعد 5 دقائق ويجب إدخاله في الشاشة الجديدة لربطه بحسابك.</p>
+ <p>%1$s \n<i>\"أو-بي-يا\"</i>(فنلندية) - \"للتعلم\"</p>\n<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>إذا لم يتم تحميل مشغل الاستكشاف</p><p><br></p><p>تحقق لمعرفة ما إذا كان التطبيق محدثًا أم لا:</p><p> <ol> <li> انتقل إلى متجر Play وتأكد من تحديث التطبيق إلى أحدث إصدار </li> </ol> </p><p><br></p><p>تحقق من اتصالك بالإنترنت:</p><p> <li> إذا كان اتصالك بالإنترنت بطيئًا ، فحاول إعادة الاتصال بشبكة Wi-Fi أو الاتصال بشبكة أخرى. </li> </p><p><br></p><p>اطلب من المشرف التحقق من أجهزتهم واتصال الإنترنت:</p><p> <li> اطلب من المشرف استكشاف الأخطاء وإصلاحها باستخدام الخطوات المذكورة أعلاه </li> </p><p><br></p><p>أخبرنا إذا كنت لا تزال تواجه مشكلات في التحميل:</p><p> <li> أبلغ عن مشكلة عن طريق الاتصال بنا على admin@oppia.org. </li> </p>
+ <p>إذا لم يتم تشغيل الصوت الخاص بك</p><p><br></p>\n<p>تحقق لمعرفة ما إذا كان التطبيق محدثًا أم لا:</p>\n<p> <li>انتقل إلى متجر Play وتأكد من تحديث التطبيق إلى أحدث إصدار</li> </p><p><br></p>\n<p>تحقق من اتصالك بالإنترنت:</p><p> <li>إذا كان اتصالك بالإنترنت بطيئًا ، فحاول إعادة الاتصال بشبكة Wi-Fi أو الاتصال بشبكة أخرى. قد يتسبب الإنترنت البطيء في تحميل الصوت بشكل غير منتظم ، مما يجعل من الصعب تشغيله.</li> </p><p><br></p>\n<p>اطلب من المسؤول التحقق من أجهزتهم واتصال الإنترنت:</p><p> \n<li>اطلب من المسؤول استكشاف الأخطاء وإصلاحها باستخدام الخطوات المذكورة أعلاه</li> </p><p><br></p>\n<p>أخبرنا إذا كنت لا تزال تواجه مشكلات في التحميل:</p><p> <li>أبلغ عن مشكلة عن طريق الاتصال بنا على admin@oppia.org.</li></p>
+ <p>لتنزيل استكشاف:</p>\n<p>1. من الصفحة الرئيسية ، انقر فوق موضوع أو استكشاف.</p>\n<p>2. من صفحة الموضوع هذه ، انقر على علامة التبويب <strong> معلومات </ strong>.</p>\n<p>3. اضغط على <strong> تنزيل الموضوع </ strong>.</p>\n<p>4. اعتمادًا على إعدادات التطبيق ، قد تحتاج إلى موافقة المسؤول أو اتصال Wifi ثابتًا لإكمال التنزيل. إذا لزم الأمر ، بمجرد استيفاء هذه المتطلبات ، يتم تنزيل الموضوع على الجهاز ويمكن استخدامه في وضع عدم الاتصال بواسطة جميع ملفات التعريف. <p>
+ <p> إذا لم تتمكن من العثور على سؤالك أو كنت ترغب في الإبلاغ عن خطأ ، فاتصل بنا على admin@oppia.org. </p>
diff --git a/app/src/main/res/values-hdpi/dimens.xml b/app/src/main/res/values-hdpi/dimens.xml
new file mode 100644
index 00000000000..bf52594b9cd
--- /dev/null
+++ b/app/src/main/res/values-hdpi/dimens.xml
@@ -0,0 +1,9 @@
+
+
+
+ 32dp
+ 18dp
+ 32dp
+ 32dp
+
+
diff --git a/app/src/main/res/values-land-hdpi/dimens.xml b/app/src/main/res/values-land-hdpi/dimens.xml
new file mode 100644
index 00000000000..686aa480cdc
--- /dev/null
+++ b/app/src/main/res/values-land-hdpi/dimens.xml
@@ -0,0 +1,9 @@
+
+
+
+ 20dp
+ 8dp
+ 32dp
+ 32dp
+
+
diff --git a/app/src/main/res/values-land-ldpi/dimens.xml b/app/src/main/res/values-land-ldpi/dimens.xml
new file mode 100644
index 00000000000..6a718617118
--- /dev/null
+++ b/app/src/main/res/values-land-ldpi/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+ 16dp
+ 8dp
+ 32dp
+ 32dp
+
diff --git a/app/src/main/res/values-land-mdpi/dimens.xml b/app/src/main/res/values-land-mdpi/dimens.xml
new file mode 100644
index 00000000000..71dd70dee47
--- /dev/null
+++ b/app/src/main/res/values-land-mdpi/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+ 64dp
+ 32dp
+ 32dp
+ 32dp
+
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
index 4b51ff326bb..cbb81f2c947 100644
--- a/app/src/main/res/values-land/dimens.xml
+++ b/app/src/main/res/values-land/dimens.xml
@@ -260,6 +260,11 @@
76dp
12dp
+
+ 64dp
+ 12dp
+ 32dp
+
72dp
72dp
@@ -297,4 +302,9 @@
40dp
96dp
+
+ 8dp
+ 8dp
+ 14sp
+ 8dp
diff --git a/app/src/main/res/values-ldpi/dimens.xml b/app/src/main/res/values-ldpi/dimens.xml
new file mode 100644
index 00000000000..0d5895656e6
--- /dev/null
+++ b/app/src/main/res/values-ldpi/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+ 32dp
+ 12dp
+ 32dp
+ 32dp
+
diff --git a/app/src/main/res/values-mdpi/dimens.xml b/app/src/main/res/values-mdpi/dimens.xml
new file mode 100644
index 00000000000..21e07668b22
--- /dev/null
+++ b/app/src/main/res/values-mdpi/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+ 64dp
+ 36dp
+ 60dp
+ 60dp
+
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 221312ff17e..33eda2d12fa 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -1,6 +1,7 @@
@@ -9,6 +10,7 @@
Opções
Meus Downloads
Ajuda
+ Reprodutor de Exploração
Ajuda
Trocar Perfil
Opções de Desenvolvedor
@@ -65,7 +67,7 @@
Perguntas Frequentes
FAQs (Perguntas Frequentes)
Verificação de PIN
- Introdução à Oppia
+ Introdução
Perguntas Frequentes (FAQs)
Info
Lições
@@ -131,6 +133,7 @@
%d GB
Correto!
Tópico: %s
+ %1$s em %2$s
- 1 Capítulo\n
- \n %d Capítulos\n
@@ -186,6 +189,9 @@
Autorize para adicionar perfis
Adicionar Perfil
Adicionar Perfil
+ Nome*
+ PIN de 3 Dígitos*
+ Confirmar PIN de 3 Dígitos*
Permitir Acesso a Download
O usuário pode baixar e excluir conteúdo sem o PIN do administrador.
Criar
@@ -201,7 +207,7 @@
Os campos marcados com * são obrigatórios.
Foto de perfil atual
Editar foto de perfil
- Bem-vindo à Oppia!
+ Bem-vindo à %s!
Aprenda o que você quiser de uma forma eficaz e divertida.
Adicione usuários à sua conta.
Compartilhe a experiência e crie até 10 perfis.
@@ -212,6 +218,7 @@
Pular
Próximo
Começar
+ Slide %d de %d
Olá, %s!
Por favor, insira o PIN do Administrador.
Por favor, insira seu PIN.
@@ -222,7 +229,7 @@
Fechar
A alteração do PIN foi bem-sucedida
Esqueceu o PIN?
- Para redefinir o seu PIN, desinstale a Oppia e reinstale-a depois.\n\nLembre-se de que, se o dispositivo não estiver online, você pode perder o progresso do usuário em várias contas.
+ Para redefinir seu PIN, desinstale %s e reinstale-o.\n\nLembre-se de que, se o dispositivo não estiver online, você pode perder o progresso do usuário em várias contas.
Ir para a Play Store
Mostrar/Esconder ícone da senha
Ícone de mostrar a senha
@@ -234,6 +241,7 @@
Cancelar
Enviar
PIN do administrador incorreto. Por favor, tente novamente.
+ Novo PIN de %1$s
Insira um Novo Pin
Meus Downloads
Downloads
@@ -269,6 +277,7 @@
Seu PIN deve ter 3 dígitos.
Seu PIN deve ter 5 dígitos.
Criar um PIN de 3 Dígitos
+ *Requerido
Botão de Voltar
Próximo
Geral
@@ -397,14 +406,35 @@
Wifi
Celular
Sem conexão
- Dependências de Terceiros
+ Dependências de terceiros
versão %s
Licença de Direitos Autorais
Visualizador de Licença de Direitos Autorais
Voltar para %s
lista de dependências de terceiros
lista de licenças de direitos autorais
- Retomar Lição
+ Retomar lição
Continuar
Recomeçar
+ Bom dia,
+ Boa tarde,
+ Boa noite,
+ Como posso criar um novo perfil?
+ Como posso deletar um perfil?
+ Como posso alterar meu e-mail/número de telefone?
+ O que é %s?
+ Quem é um administrador?
+ Por que a exploração não está carregando?
+ Por que meu áudio não está tocando?
+ Como faço o download de um tópico?
+ Não consigo encontrar minha pergunta aqui. E agora?
+ <p>Se é a sua primeira vez criando um perfil e você não tem um PIN:</p> <p> 1. No Seletor de Perfil, toque em <strong>Configurar Múltiplos Perfis</strong>. </p> <p> 2. Crie um PIN e <strong>Salvar</strong>. </p> <p> 3. Preencha todos os campos do perfil. </p> <ol> <li> (Opcional) Carregar uma foto. </li> <li> Insira um nome. </li> <li> (Opcional) Atribua um PIN de 3 dígitos. </li> </ol> <p> 4. Toque em <strong>Criar</strong>. Este perfil está adicionado ao seu Seletor de Perfil! <br/> <br/> Se você já criou um perfil antes e tem um PIN: </p> <p> 1. No Seletor de Perfil, toque em <strong>Adicionar Perfil</strong>. </p> <p> 2. Digite seu PIN e toque em <strong>Enviar</strong>. </p> <p> 3. Preencha todos os campos do perfil. </p> <ol> <li> (Opcional) Carregar uma foto. </li> <li> Insira um nome. </li> <li> (Opcional) Atribua um PIN de 3 dígitos. </li> </ol> <p> 4. Toque em <strong>Criar</strong>. Este perfil está adicionado ao seu Seletor de Perfil! <br/> <br/> Nota: Apenas o <u>Administrador</u> pode gerenciar perfis.</p>
+ <p>Depois que um perfil é deletado:</p> <p><br></p> <p> <li> O perfil não pode ser recuperado. </li> </p> <p> <li> As informações do perfil, como nome, fotos e progresso, serão excluídas permanentemente. </li> </p> <p><br></p> <p>Para deletar um perfil(excluindo o do <u>Administrador</u>):</p> <p>1. Na página inicial do administrador, toque no botão de menu no canto superior esquerdo.</p> <p>2. Toque em <strong>Controles do Administrador</strong>.</p> <p>3. Toque em <strong>Editar Perfis</strong>.</p> <p>4. Toque no perfil que deseja excluir.</p> <p>5. Na parte inferior da tela, toque em <strong>Exclusão de Perfil</strong>.</p> <p>6. Toque em <strong>Deletar</strong> para confirmar a exclusão.</p><p><br></p><p>Nota: Apenas o <u>Administrador</u> pode gerenciar perfis.</p>
+ <p>Para alterar seu e-mail/número de telefone:</p> <p>1. Na página inicial do administrador, toque no botão de menu no canto superior esquerdo.</p> <p>2. Toque em <strong>Controles do Administrador</strong>.</p> <p>3. Toque em <strong>Editar Conta</strong>.</p> <p><br></p> <p>Se você deseja alterar seu e-mail:</p> <p>4. Digite seu novo e-mail e toque em <strong>Salvar</strong>.</p> <p>5. Um link de confirmação será enviado para confirmar seu novo e-mail. O link irá expirar após 24 horas e deve ser clicado para ser associado à sua conta. </p> <p><br></p> <p>Se mudar seu número de telefone:</p> <p>4. Digite seu novo número de telefone e toque em <strong>Verificar</strong>.</p> <p>5. Um código será enviado para confirmar seu novo número. O código expira após 5 minutos e deve ser inserido na nova tela para ser associado à sua conta.</p>
+ <p>%1$s <i>\"O-pee-yah\"</i> (Finnish) - \"aprender\"</p><p><br></p><p>%1$s tem a missão de ajudar qualquer pessoa a aprender o que quiser de uma forma eficaz e agradável.</p><p><br></p><p>Ao criar um conjunto de aulas gratuitas, de alta qualidade e comprovadamente eficazes com a ajuda de educadores de todo o mundo, %1$s visa proporcionar aos alunos uma educação de qualidade - independentemente de onde estejam ou a quais recursos tradicionais tenham acesso.</p><p><br></p><p>Como estudante, você pode começar sua aventura de aprendizado navegando pelos tópicos listados na página inicial!</p>
+ <p>Um administrador é o usuário principal que gerencia perfis e configurações para cada perfil em sua conta. Provavelmente, eles são seus pais, professores ou responsáveis que criaram este perfil para você.</p><p><br></p><p>Os administradores podem gerenciar perfis, atribuir PINs e alterar outras configurações em suas contas. Dependendo do seu perfil, as permissões de administrador podem ser necessárias para determinados recursos, como download de tópicos, alteração do PIN e muito mais. </p><p><br></p><p>Para ver quem é o seu administrador, vá para o Seletor de perfil. O primeiro perfil listado e com \"Administrador\" escrito em seu nome é o Administrador. </p>
+ <p>Se a exploração não estiver carregando</p><p><br></p><p>Verifique se o aplicativo está atualizado:</p><p> <ol> <li> Acesse a Play Store e certifique-se de que o aplicativo esteja atualizado com a versão mais recente </li> </ol> </p><p><br></p><p>Verifique sua conexão com a internet:</p><p> <li> Se sua conexão com a Internet estiver lenta, tente se reconectar à rede Wi-Fi ou conectar-se a uma rede diferente. </li> </p><p><br></p><p>Peça ao administrador para verificar o dispositivo e a conexão com a Internet:</p><p> <li> Peça ao administrador para solucionar o problema usando as etapas acima </li> </p><p><br></p><p>Informe-nos se você ainda tiver problemas com o carregamento:</p><p> <li> Relate um problema entrando em contato conosco em admin@oppia.org. </li> </p>
+ <p>Se o seu áudio não estiver tocando</p><p><br></p><p>Verifique se o aplicativo está atualizado:</p><p> <li> Acesse a Play Store e certifique-se de que o aplicativo esteja atualizado com a versão mais recente </li> </p><p><br></p><p>Verifique sua conexão com a internet:</p><p> <li> Se sua conexão com a Internet estiver lenta, tente se reconectar à rede Wi-Fi ou conectar-se a uma rede diferente. A Internet lenta pode fazer com que o áudio carregue irregularmente, dificultando a reprodução. </li> </p><p><br></p><p>Peça ao administrador para verificar o dispositivo e a conexão com a Internet:</p><p> <li> Peça ao administrador para solucionar o problema usando as etapas acima</li> </p><p><br></p><p>Informe-nos se você ainda tiver problemas com o carregamento:</p><p> <li> Relate um problema entrando em contato conosco em admin@oppia.org. </li></p>
+ <p>Para baixar uma Exploração</p><p>1. Na página inicial, toque em um Tópico ou Exploração.</p><p>2. Na Página do Tópico, toque em <strong>Info</strong> aba.</p><p>3. Toque em <strong>Baixar Tópico</strong>. </p><p>4. Dependendo das configurações do aplicativo, você pode precisar da aprovação do Administrador ou de uma conexão Wifi estável para concluir o download. Se necessário, uma vez que esses requisitos sejam satisfeitos, o Tópico foi baixado para o dispositivo e pode ser usado offline por todos os perfis. <p>
+ <p>Se você não consegue encontrar sua pergunta ou gostaria de relatar um problema, entre em contato conosco em admin@oppia.org.</p>
diff --git a/app/src/main/res/values-sw600dp-land/dimens.xml b/app/src/main/res/values-sw600dp-land/dimens.xml
index f15655f2f9f..6fdbe35a1e9 100644
--- a/app/src/main/res/values-sw600dp-land/dimens.xml
+++ b/app/src/main/res/values-sw600dp-land/dimens.xml
@@ -322,4 +322,9 @@
48dp
100dp
+
+ 0dp
+ 32dp
+ 16sp
+ 4dp
diff --git a/app/src/main/res/values-sw600dp-port/dimens.xml b/app/src/main/res/values-sw600dp-port/dimens.xml
index d2f9881d062..428084dfa53 100644
--- a/app/src/main/res/values-sw600dp-port/dimens.xml
+++ b/app/src/main/res/values-sw600dp-port/dimens.xml
@@ -324,5 +324,9 @@
48dp
100dp
-
+
+ 8dp
+ 24dp
+ 16sp
+ 4dp
diff --git a/app/src/main/res/values-xxhdpi/dimens.xml b/app/src/main/res/values-xxhdpi/dimens.xml
new file mode 100644
index 00000000000..21e07668b22
--- /dev/null
+++ b/app/src/main/res/values-xxhdpi/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+ 64dp
+ 36dp
+ 60dp
+ 60dp
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 0be5019b680..94dad7a33c2 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -136,7 +136,7 @@
24dp
24dp
24dp
- 84dp
+ 60dp
24dp
24dp
24dp
@@ -444,6 +444,11 @@
32dp
12dp
+
+ 64dp
+ 36dp
+ 60dp
+
0dp
0dp
@@ -485,4 +490,10 @@
36dp
148dp
+
+ 8dp
+ 8dp
+ 280dp
+ 16sp
+ 8dp
diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt
index 7e9128034f4..3abbf827b28 100644
--- a/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt
+++ b/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt
@@ -4,6 +4,7 @@ import android.app.Application
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ActivityScenario
+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.click
@@ -26,7 +27,6 @@ import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.not
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -244,15 +244,16 @@ class TopicPracticeFragmentTest {
}
@Test
- @Ignore("Flaky test") // TODO(#3413): Test is failing unexpectedly.
fun testTopicPracticeFragment_loadFragment_selectSubtopics_clickStartButton_skillListTransferSuccessfully() { // ktlint-disable max-line-length
- launchTopicActivityIntent(internalProfileId, FRACTIONS_TOPIC_ID)
- clickPracticeTab()
- clickPracticeItem(position = 1, targetViewId = R.id.subtopic_check_box)
- scrollToPosition(position = 5)
- clickPracticeItem(position = 5, targetViewId = R.id.topic_practice_start_button)
- intended(hasComponent(QuestionPlayerActivity::class.java.name))
- intended(hasExtra(QuestionPlayerActivity.getIntentKey(), skillIdList))
+ testCoroutineDispatchers.unregisterIdlingResource()
+ launchTopicActivityIntent(internalProfileId, FRACTIONS_TOPIC_ID).use {
+ clickPracticeTab()
+ clickPracticeItem(position = 1, targetViewId = R.id.subtopic_check_box)
+ scrollToPosition(position = 5)
+ clickPracticeItem(position = 5, targetViewId = R.id.topic_practice_start_button)
+ intended(hasComponent(QuestionPlayerActivity::class.java.name))
+ intended(hasExtra(QuestionPlayerActivity.getIntentKey(), skillIdList))
+ }
}
@Test
diff --git a/scripts/assets/filename_pattern_validation_checks.textproto b/scripts/assets/filename_pattern_validation_checks.textproto
index fc6cad22b44..9307ed6f21d 100644
--- a/scripts/assets/filename_pattern_validation_checks.textproto
+++ b/scripts/assets/filename_pattern_validation_checks.textproto
@@ -1,4 +1,8 @@
filename_checks {
prohibited_filename_regex: "^((?!(app|testing)).)+/src/main/.+?Activity.kt"
- failure_message: "Activities cannot be placed outside the app or testing module"
+ failure_message: "Activities cannot be placed outside the app or testing module."
+}
+filename_checks {
+ prohibited_filename_regex: "^.+?/res/([^/]+/){2,}[^/]+$"
+ failure_message: "Only one level of subdirectories under res/ should be maintained (further subdirectories aren't supported by the project configuration)."
}
diff --git a/scripts/src/java/org/oppia/android/scripts/regex/RegexPatternValidationCheck.kt b/scripts/src/java/org/oppia/android/scripts/regex/RegexPatternValidationCheck.kt
index c6ba0af0f16..464251b2689 100644
--- a/scripts/src/java/org/oppia/android/scripts/regex/RegexPatternValidationCheck.kt
+++ b/scripts/src/java/org/oppia/android/scripts/regex/RegexPatternValidationCheck.kt
@@ -32,24 +32,25 @@ fun main(vararg args: String) {
// Check if the repo has any filename failure.
val hasFilenameCheckFailure = retrieveFilenameChecks()
- .fold(initial = false) { isFailing, filenameCheck ->
- val checkFailed = checkProhibitedFileNamePattern(
+ .fold(initial = false) { hasFailingFile, filenameCheck ->
+ val fileFails = checkProhibitedFileNamePattern(
repoRoot,
searchFiles,
filenameCheck,
)
- isFailing || checkFailed
+ return@fold hasFailingFile || fileFails
}
// Check if the repo has any file content failure.
- val hasFileContentCheckFailure = retrieveFileContentChecks()
- .fold(initial = false) { isFailing, fileContentCheck ->
- val checkFailed = checkProhibitedContent(
+ val contentChecks = retrieveFileContentChecks().map { MatchableFileContentCheck.createFrom(it) }
+ val hasFileContentCheckFailure =
+ searchFiles.fold(initial = false) { hasFailingFile, searchFile ->
+ val fileFails = checkProhibitedContent(
repoRoot,
- searchFiles,
- fileContentCheck
+ searchFile,
+ contentChecks
)
- isFailing || checkFailed
+ return@fold hasFailingFile || fileFails
}
if (hasFilenameCheckFailure || hasFileContentCheckFailure) {
@@ -138,43 +139,33 @@ private fun checkProhibitedFileNamePattern(
* Checks for a prohibited file content.
*
* @param repoRoot the root directory of the repo
- * @param searchFiles a list of all the files which needs to be checked
- * @param fileContentCheck proto object of FileContentCheck
+ * @param searchFile the file to check for prohibited content
+ * @param fileContentChecks contents to check for validity
* @return whether the file content pattern is correct or not
*/
private fun checkProhibitedContent(
repoRoot: File,
- searchFiles: List,
- fileContentCheck: FileContentCheck
+ searchFile: File,
+ fileContentChecks: Iterable
): Boolean {
- val filePathRegex = fileContentCheck.filePathRegex.toRegex()
- val prohibitedContentRegex = fileContentCheck.prohibitedContentRegex.toRegex()
-
- val matchedFiles = searchFiles.filter { file ->
- val fileRelativePath = file.toRelativeString(repoRoot)
- val isFileExactExemption = fileRelativePath in fileContentCheck.exemptedFileNameList
- val isFileMatchedExemption = fileContentCheck.exemptedFilePatternsList.any { pattern ->
- pattern.toRegex().matches(fileRelativePath)
- }
- // TODO: add tests.
- val isExempted = isFileExactExemption || isFileMatchedExemption
- return@filter if (!isExempted && filePathRegex.matches(fileRelativePath)) {
- file.useLines { lines ->
- lines.foldIndexed(initial = false) { lineIndex, isFailing, lineContent ->
- val matches = prohibitedContentRegex.containsMatchIn(lineContent)
- if (matches) {
- logProhibitedContentFailure(
- lineIndex + 1, // Increment by 1 since line numbers begin at 1 rather than 0.
- fileContentCheck.failureMessage,
- fileRelativePath
- )
- }
- isFailing || matches
+ val lines = searchFile.readLines()
+ return fileContentChecks.fold(initial = false) { hasFailingFile, fileContentCheck ->
+ val fileRelativePath = searchFile.toRelativeString(repoRoot)
+ val fileFails = if (fileContentCheck.isFileAffectedByCheck(fileRelativePath)) {
+ val affectedLines = fileContentCheck.computeAffectedLines(lines)
+ if (affectedLines.isNotEmpty()) {
+ affectedLines.forEach { lineIndex ->
+ logProhibitedContentFailure(
+ lineIndex + 1, // Increment by 1 since line numbers begin at 1 rather than 0.
+ fileContentCheck.failureMessage,
+ fileRelativePath
+ )
}
}
+ affectedLines.isNotEmpty()
} else false
+ return@fold hasFailingFile || fileFails
}
- return matchedFiles.isNotEmpty()
}
/**
@@ -213,3 +204,46 @@ private fun logProhibitedContentFailure(
val failureMessage = "$filePath:$lineNumber: $errorToShow"
println(failureMessage)
}
+
+/** A matchable version of [FileContentCheck]. */
+private data class MatchableFileContentCheck(
+ val filePathRegex: Regex,
+ val prohibitedContentRegex: Regex,
+ val failureMessage: String,
+ val exemptedFileNames: List,
+ val exemptedFilePatterns: List
+) {
+ /**
+ * Returns whether the relative file given by the specified path should be affected by this check
+ * (i.e. that it matches the inclusion pattern and is not explicitly or implicitly excluded).
+ */
+ fun isFileAffectedByCheck(relativePath: String): Boolean =
+ filePathRegex.matches(relativePath) && !isFileExempted(relativePath)
+
+ /**
+ * Returns the list of line indexes which contain prohibited content per this check (given an
+ * iterable of lines). Note that the returned indexes are based on the iteration order of the
+ * provided iterable.
+ */
+ fun computeAffectedLines(lines: Iterable): List {
+ return lines.withIndex().filter { (_, line) ->
+ prohibitedContentRegex.containsMatchIn(line)
+ }.map { (index, _) -> index }
+ }
+
+ private fun isFileExempted(relativePath: String): Boolean =
+ relativePath in exemptedFileNames || exemptedFilePatterns.any { it.matches(relativePath) }
+
+ companion object {
+ /** Returns a new [MatchableFileContentCheck] based on the specified [FileContentCheck]. */
+ fun createFrom(fileContentCheck: FileContentCheck): MatchableFileContentCheck {
+ return MatchableFileContentCheck(
+ filePathRegex = fileContentCheck.filePathRegex.toRegex(),
+ prohibitedContentRegex = fileContentCheck.prohibitedContentRegex.toRegex(),
+ failureMessage = fileContentCheck.failureMessage,
+ exemptedFileNames = fileContentCheck.exemptedFileNameList,
+ exemptedFilePatterns = fileContentCheck.exemptedFilePatternsList.map { it.toRegex() }
+ )
+ }
+ }
+}
diff --git a/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt
index e6f8c23870a..675ebe06f53 100644
--- a/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt
+++ b/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt
@@ -17,13 +17,18 @@ class RegexPatternValidationCheckTest {
private val originalOut: PrintStream = System.out
private val REGEX_CHECK_PASSED_OUTPUT_INDICATOR: String = "REGEX PATTERN CHECKS PASSED"
private val REGEX_CHECK_FAILED_OUTPUT_INDICATOR: String = "REGEX PATTERN CHECKS FAILED"
+ private val activitiesPlacementErrorMessage =
+ "Activities cannot be placed outside the app or testing module."
+ private val nestedResourceSubdirectoryErrorMessage =
+ "Only one level of subdirectories under res/ should be maintained (further subdirectories " +
+ "aren't supported by the project configuration)."
private val supportLibraryUsageErrorMessage =
"AndroidX should be used instead of the support library"
private val coroutineWorkerUsageErrorMessage =
"For stable tests, prefer using ListenableWorker with an Oppia-managed dispatcher."
private val settableFutureUsageErrorMessage =
- "SettableFuture should only be used in pre-approved locations since it's easy to potentially" +
- " mess up & lead to a hanging ListenableFuture."
+ "SettableFuture should only be used in pre-approved locations since it's easy to potentially " +
+ "mess up & lead to a hanging ListenableFuture."
private val androidGravityLeftErrorMessage =
"Use android:gravity=\"start\", instead, for proper RTL support"
private val androidGravityRightErrorMessage =
@@ -47,8 +52,8 @@ class RegexPatternValidationCheckTest {
private val androidTouchAnchorSideRightErrorMessage =
"Use motion:touchAnchorSide=\"end\", instead, for proper RTL support"
private val oppiaCantBeTranslatedErrorMessage =
- "Oppia should never used directly in a string (since it shouldn't be translated). Instead," +
- " use a parameter & insert the string retrieved from app_name."
+ "Oppia should never used directly in a string (since it shouldn't be translated). Instead, " +
+ "use a parameter & insert the string retrieved from app_name."
private val untranslatableStringsGoInSpecificFileErrorMessage =
"Untranslatable strings should go in untranslated_strings.xml, instead."
private val translatableStringsGoInMainFileErrorMessage =
@@ -104,8 +109,58 @@ class RegexPatternValidationCheckTest {
assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR)
assertThat(outContent.toString().trim()).isEqualTo(
"""
- File name/path violation: Activities cannot be placed outside the app or testing module
+ File name/path violation: $activitiesPlacementErrorMessage
- data/src/main/TestActivity.kt
+
+ $wikiReferenceNote
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testFileNamePattern_appResources_stringsFile_fileNamePatternIsCorrect() {
+ tempFolder.newFolder("testfiles", "app", "src", "main", "res", "values")
+ tempFolder.newFile("testfiles/app/src/main/res/values/strings.xml")
+
+ runScript()
+
+ assertThat(outContent.toString().trim()).isEqualTo(REGEX_CHECK_PASSED_OUTPUT_INDICATOR)
+ }
+
+ @Test
+ fun testFileNamePattern_appResources_subValuesDir_stringsFile_fileNamePatternIsNotCorrect() {
+ tempFolder.newFolder("testfiles", "app", "src", "main", "res", "values", "subdir")
+ tempFolder.newFile("testfiles/app/src/main/res/values/subdir/strings.xml")
+
+ val exception = assertThrows(Exception::class) {
+ runScript()
+ }
+
+ assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR)
+ assertThat(outContent.toString().trim()).isEqualTo(
+ """
+ File name/path violation: $nestedResourceSubdirectoryErrorMessage
+ - app/src/main/res/values/subdir/strings.xml
+
+ $wikiReferenceNote
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testFileNamePattern_domainResources_subValuesDir_stringsFile_fileNamePatternIsNotCorrect() {
+ tempFolder.newFolder("testfiles", "domain", "src", "main", "res", "drawable", "subdir")
+ tempFolder.newFile("testfiles/domain/src/main/res/drawable/subdir/example.png")
+
+ val exception = assertThrows(Exception::class) {
+ runScript()
+ }
+
+ assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR)
+ assertThat(outContent.toString().trim()).isEqualTo(
+ """
+ File name/path violation: $nestedResourceSubdirectoryErrorMessage
+ - domain/src/main/res/drawable/subdir/example.png
$wikiReferenceNote
""".trimIndent()
@@ -828,6 +883,30 @@ class RegexPatternValidationCheckTest {
assertThat(outContent.toString().trim()).isEqualTo(REGEX_CHECK_PASSED_OUTPUT_INDICATOR)
}
+ @Test
+ fun testFileContent_translatableString_inPrimaryStringsFile_fileContentIsCorrect() {
+ val prohibitedContent = "Translatable"
+ tempFolder.newFolder("testfiles", "app", "src", "main", "res", "values")
+ val stringFilePath = "app/src/main/res/values/strings.xml"
+ tempFolder.newFile("testfiles/$stringFilePath").writeText(prohibitedContent)
+
+ runScript()
+
+ assertThat(outContent.toString().trim()).isEqualTo(REGEX_CHECK_PASSED_OUTPUT_INDICATOR)
+ }
+
+ @Test
+ fun testFileContent_translatableString_inTranslatedPrimaryStringsFile_fileContentIsCorrect() {
+ val prohibitedContent = "Translatable"
+ tempFolder.newFolder("testfiles", "app", "src", "main", "res", "values-ar")
+ val stringFilePath = "app/src/main/res/values-ar/strings.xml"
+ tempFolder.newFile("testfiles/$stringFilePath").writeText(prohibitedContent)
+
+ runScript()
+
+ assertThat(outContent.toString().trim()).isEqualTo(REGEX_CHECK_PASSED_OUTPUT_INDICATOR)
+ }
+
@Test
fun testFilenameAndContent_useProhibitedFileName_useProhibitedFileContent_multipleFailures() {
tempFolder.newFolder("testfiles", "data", "src", "main")
@@ -842,10 +921,10 @@ class RegexPatternValidationCheckTest {
assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR)
assertThat(outContent.toString().trim()).isEqualTo(
"""
- File name/path violation: Activities cannot be placed outside the app or testing module
+ File name/path violation: $activitiesPlacementErrorMessage
- data/src/main/TestActivity.kt
- data/src/main/TestActivity.kt:1: AndroidX should be used instead of the support library
+ data/src/main/TestActivity.kt:1: $supportLibraryUsageErrorMessage
$wikiReferenceNote
""".trimIndent()
)
diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/CustomHtmlContentHandler.kt b/utility/src/main/java/org/oppia/android/util/parser/html/CustomHtmlContentHandler.kt
index bd115719c1a..c19f35f61f3 100644
--- a/utility/src/main/java/org/oppia/android/util/parser/html/CustomHtmlContentHandler.kt
+++ b/utility/src/main/java/org/oppia/android/util/parser/html/CustomHtmlContentHandler.kt
@@ -113,8 +113,6 @@ class CustomHtmlContentHandler private constructor(
customTagHandlers.getValue(tag).handleClosingTag(output)
customTagHandlers.getValue(tag)
.handleTag(attributes, openTagIndex, output.length, output, imageRetriever)
- customTagHandlers.getValue(tag)
- .handleContentDescription(attributes, openTagIndex, output.length, output)
}
}
}
@@ -165,21 +163,6 @@ class CustomHtmlContentHandler private constructor(
* @param output the destination [Editable] to which spans can be added
*/
fun handleClosingTag(output: Editable) {}
-
- /**
- * Called when a custom tag is encountered. This is always called after the closing tag.
- *
- * @param attributes the tag's attributes
- * @param openIndex the index in the output [Editable] at which this tag begins
- * @param closeIndex the index in the output [Editable] at which this tag ends
- * @param output the destination [Editable] to which spans can be added
- */
- fun handleContentDescription(
- attributes: Attributes,
- openIndex: Int,
- closeIndex: Int,
- output: Editable
- ) {}
}
/**
diff --git a/utility/src/main/java/org/oppia/android/util/parser/html/ImageTagHandler.kt b/utility/src/main/java/org/oppia/android/util/parser/html/ImageTagHandler.kt
index 4581c2587ad..5d55fcf4ed7 100644
--- a/utility/src/main/java/org/oppia/android/util/parser/html/ImageTagHandler.kt
+++ b/utility/src/main/java/org/oppia/android/util/parser/html/ImageTagHandler.kt
@@ -2,7 +2,6 @@ package org.oppia.android.util.parser.html
import android.text.Editable
import android.text.Spannable
-import android.text.SpannableStringBuilder
import android.text.style.ImageSpan
import org.oppia.android.util.logging.ConsoleLogger
import org.xml.sax.Attributes
@@ -10,7 +9,6 @@ import org.xml.sax.Attributes
/** The custom tag corresponding to [ImageTagHandler]. */
const val CUSTOM_IMG_TAG = "oppia-noninteractive-image"
private const val CUSTOM_IMG_FILE_PATH_ATTRIBUTE = "filepath-with-value"
-private const val CUSTOM_IMG_ALT_TEXT_ATTRIBUTE = "alt-with-value"
/**
* A custom tag handler for supporting custom Oppia images parsed with [CustomHtmlContentHandler].
@@ -45,25 +43,6 @@ class ImageTagHandler(
endIndex,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
- } else consoleLogger.e("ImageTagHandler", "Failed to parse $CUSTOM_IMG_FILE_PATH_ATTRIBUTE")
- }
-
- override fun handleContentDescription(
- attributes: Attributes,
- openIndex: Int,
- closeIndex: Int,
- output: Editable
- ) {
- val contentDescription = attributes.getJsonStringValue(CUSTOM_IMG_ALT_TEXT_ATTRIBUTE)
- if (contentDescription != null) {
- val spannableBuilder = SpannableStringBuilder(contentDescription)
- spannableBuilder.setSpan(
- contentDescription,
- /* start= */ 0,
- /* end= */ contentDescription.length,
- Spannable.SPAN_INCLUSIVE_EXCLUSIVE
- )
- output.replace(openIndex, closeIndex, spannableBuilder)
- } else consoleLogger.e("ImageTagHandler", "Failed to parse $CUSTOM_IMG_ALT_TEXT_ATTRIBUTE")
+ } else consoleLogger.e("ImageTagHandler", "Failed to parse image tag")
}
}
diff --git a/utility/src/test/java/org/oppia/android/util/parser/html/ImageTagHandlerTest.kt b/utility/src/test/java/org/oppia/android/util/parser/html/ImageTagHandlerTest.kt
index 9dd8e10aeb1..352fad75028 100644
--- a/utility/src/test/java/org/oppia/android/util/parser/html/ImageTagHandlerTest.kt
+++ b/utility/src/test/java/org/oppia/android/util/parser/html/ImageTagHandlerTest.kt
@@ -50,10 +50,6 @@ private const val IMAGE_TAG_WITHOUT_FILEPATH_MARKUP =
""
-private const val IMAGE_TAG_WITHOUT_ALT_VALUE_MARKUP =
- ""
-
/** Tests for [ImageTagHandler]. */
@RunWith(AndroidJUnit4::class)
@LooperMode(LooperMode.Mode.PAUSED)
@@ -113,7 +109,7 @@ class ImageTagHandlerTest {
fun testParseHtml_withImageCardMarkup_hasNoReadableText() {
val parsedHtml =
CustomHtmlContentHandler.fromHtml(
- html = IMAGE_TAG_WITHOUT_ALT_VALUE_MARKUP,
+ html = IMAGE_TAG_MARKUP_1,
imageRetriever = mockImageRetriever,
customTagHandlers = tagHandlersWithImageTagSupport
)
@@ -124,22 +120,6 @@ class ImageTagHandlerTest {
assertThat(parsedHtmlStr.first().isObjectReplacementCharacter()).isTrue()
}
- @Test
- fun testParseHtml_withImageCardMarkup_missingAltValue_hasReadableText() {
- val parsedHtml =
- CustomHtmlContentHandler.fromHtml(
- html = IMAGE_TAG_MARKUP_1,
- imageRetriever = mockImageRetriever,
- customTagHandlers = tagHandlersWithImageTagSupport
- )
-
- // The image only adds a control character, so there aren't any human-readable characters.
- val parsedHtmlStr = parsedHtml.toString()
- val parsedContentDescription = "alt text 1"
- assertThat(parsedHtmlStr).hasLength(parsedContentDescription.length)
- assertThat(parsedHtmlStr.first().isObjectReplacementCharacter()).isFalse()
- }
-
@Test
fun testParseHtml_withImageCardMarkup_missingFilename_doesNotIncludeImageSpan() {
val parsedHtml =