استخدام الموضوع في Compose حسب الموضوع باستخدام المادة 3

1. مقدمة

في هذا الدرس التطبيقي حول الترميز، ستتعلّم كيفية تصميم التطبيقات في Jetpack Compose باستخدام التصميم Material Design 3. ستتعرف أيضًا على الكتل البرمجية الإنشائية لمخططات الألوان وأسلوب الخط والأشكال في Material Design 3، والتي تساعدك على مظهر تطبيقك بطرق مخصصة ويسهل الوصول إليها.

بالإضافة إلى ذلك، سوف تستكشف إمكانية استخدام المواضيع الديناميكية ومستويات التوكيد المختلفة.

ما سوف تتعلمه

في هذا الدرس التطبيقي حول الترميز، ستتعرَّف على ما يلي:

  • الجوانب الرئيسية لموضوعات Material 3
  • أنظمة ألوان Material 3 وكيفية إنشاء مظاهر لتطبيقك
  • كيفية إتاحة استخدام مظاهر ديناميكية فاتحة/داكنة في تطبيقك
  • أسلوب الخط والأشكال لتخصيص تطبيقك
  • مكونات Material 3 وتخصيصها لتصميم تطبيقك

ما الذي ستقوم ببنائه

في هذا الدرس التطبيقي حول الترميز، ستُظهر تطبيق برنامج بريد إلكتروني باسم Reply. تبدأ بتطبيق غير نمط، باستخدام المظهر الأساسي، وستطبق ما تعلمته لوضع مظهر للتطبيق ودعم المظاهر الداكنة.

d15db3dc75a9d00f.png

نقطة البداية التلقائية لتطبيقنا مع المظهر الأساسي.

ستنشئ المظهر باستخدام نظام الألوان وأسلوب الخط والأشكال، ثم ستطبقه على قائمة عناوين البريد الإلكتروني وصفحة التفاصيل في تطبيقك. سيكون بإمكانك أيضًا إضافة دعم المظاهر الديناميكية إلى التطبيق. في نهاية الدرس التطبيقي حول الترميز، سيصبح بإمكانك استخدام المظاهر الديناميكية والألوان في تطبيقك.

1357cdbfaaa67721.png

نقطة نهاية درس تطبيقي حول الترميز باستخدام ألوان فاتحة ومظاهر ديناميكية فاتحة

1357cdbfaaa67721.png

نقطة نهاية درس تطبيقي حول الترميز باستخدام ألوان داكنة ومظهر ديناميكي غامق

ما ستحتاجه

2. بدء الإعداد

في هذه الخطوة، يمكنك تنزيل الرمز الكامل لتطبيق Reply الذي ستقوم بتصميمه في هذا الدرس التطبيقي حول الترميز.

الحصول على الرمز‏

يمكن العثور على رمز هذا الدرس التطبيقي في مستودع GitHub android-compose-codelabs. لاستنساخه، قم بتشغيل:

$ git clone https://github.com/googlecodelabs/android-compose-codelabs

بدلاً من ذلك، يمكنك تنزيل ملفين مضغوطين:

الاطّلاع على نموذج التطبيق

يحتوي الرمز الذي نزّلته للتو على رمز لجميع الدروس التطبيقية المتاحة في Compose. لإكمال هذا الدرس التطبيقي حول الترميز، افتح مشروع ThemingCodelab في "استوديو Android".

ننصحك بأن تبدأ بالرمز في الفرع الرئيسي وأن تتّبع الدروس التطبيقية خطوة بخطوة بالوتيرة التي تناسبك. يمكنك في أي وقت تشغيل أي من الإصدارين في "استوديو Android" من خلال تغيير فرع المشروع.

استكشاف رمز البدء

يحتوي التعليمة البرمجية الرئيسية على حزمة واجهة مستخدم، والتي تحتوي على الحزم والملفات الرئيسية التالية التي ستتفاعل معها:

  • MainActivity.kt: نشاط نقطة الدخول الذي يتم من خلاله بدء تطبيق "الرد"
  • com.example.reply.ui.theme – تحتوي هذه الحزمة على مظاهر وأسلوب الخط وأنظمة الألوان. ستضيف Material theming في هذه الحزمة.
  • com.example.reply.ui.components – يحتوي على المكوّنات المخصّصة للتطبيق، مثل عناصر القائمة وأشرطة التطبيقات وما إلى ذلك. وسيتم تطبيق مظاهر على هذه المكوّنات.
  • ReplyApp.kt – هذه هي دالة الدمج الرئيسية التي سيتم فيها بدء شجرة واجهة المستخدم. سيتم تطبيق مواضيع المستوى الأعلى في هذا الملف.

سيركّز هذا الدرس التطبيقي حول الترميز على ملفات حزمة ui.

3- تصميم المادة 3

يتيح Jetpack Compose تنفيذ التصميم المتعدد الأبعاد، وهو نظام تصميم شامل لإنشاء الواجهات الرقمية. تمّ إنشاء مكوّنات التصميم المتعدد الأبعاد (الأزرار والبطاقات ومفاتيح التبديل وما إلى ذلك) بالإضافة إلى ميزة تخصيص التصميم المتعدد الأبعاد، وهي طريقة منهجية لتخصيص التصميم المتعدد الأبعاد ليعكس العلامة التجارية لمنتجك بشكل أفضل.

يضم مظهر Material 3 الأنظمة الفرعية التالية لإضافتها إلى تطبيقك: نظام الألوان وأسلوب الخط والأشكال. عند تخصيص هذه القيم، تظهر التغييرات تلقائيًا في مكوّنات M3 المستخدَمة لإنشاء تطبيقك. لنتعمق في كل نظام فرعي ونطبقه في نموذج التطبيق.

الأنظمة الفرعية للتصميم المتعدد الأبعاد: اللون وأسلوب الخط والأشكال.

نظام Material 3 الفرعي من الألوان وأسلوب الخط والأشكال.

4. الألوان

يعتمد نظام الألوان على مجموعة من خمسة ألوان أساسية يرتبط كل منها بلوحة ألوان مكوَّنة من 13 لونًا تستخدمها مكونات المادة 3.

خمسة ألوان أساسية لإنشاء مظهر M3

خمسة ألوان أساسية لإنشاء مظاهر M3:

يتم بعد ذلك توفير كل لون تمييز (أساسي وثانوي وثالث) بأربعة ألوان متوافقة من درجات مختلفة للإقران، وتحديد التوكيد، والتعبير المرئي.

أربعة ألوان من ألوان التمييز الأساسية والثانوية والثالثة.

أربعة ألوان من ألوان التمييز الأساسية والثانوية والثالثة:

وبالمثل، يتم تقسيم الألوان المحايدة إلى أربع درجات لونية متوافقة تستخدم للأسطح والخلفية. من المهم أيضًا التأكيد على أيقونات النص عند وضعها على أي سطح.

أربعة ألوان من درجات اللون المحايدة الأساسية.

أربعة ألوان من درجات اللون المحايدة الأساسية:

يمكنك الاطّلاع على مزيد من المعلومات عن أدوار الألوان ونظام الألوان.

جارٍ إنشاء أنظمة الألوان

على الرغم من أنّه يمكنك إنشاء رمز ColorScheme مخصّص يدويًا، يسهل غالبًا إنشاء رمز باستخدام ألوان المصدر من علامتك التجارية. تتيح لك أداة إنشاء نسق المواد إجراء ذلك، ويمكنك تصدير رموز إنشاء المظاهر اختياريًا.

يمكنك اختيار أي لون تريده، ولكن في حالة الاستخدام، ستستخدم اللون الأساسي التلقائي للردّ #825500. انقر على اللون الأساسي في قسم الألوان الأساسية الأيمن وأضِف الرمز في منتقي الألوان.

294f73fc9d2a570e.png

إضافة رمز اللون الأساسي في Material Theme Builder.

بمجرد إضافة اللون الأساسي في Material Theme Builder، من المفترض أن يظهر لك المظهر التالي وخيار التصدير في أعلى الجانب الأيسر. في هذا الدرس التطبيقي حول الترميز، يمكنك تصدير المظهر في Jetpack Compose.

يمكنك استخدام أداة Material Theme Builder، التي يمكنك تصديرها في أعلى يسار الصفحة.

يمكنك استخدام أداة Material Theme Builder، التي يتوفّر لها خيار تصدير البيانات في أعلى يسار الصفحة.

ينشئ اللون الأساسي #825500 المظهر التالي الذي ستضيفه إلى التطبيق. توفّر المادة 3 مجموعة كبيرة من أدوار الألوان للتعبير بمرونة عن حالة المكوّن وبروزه وتوكيده.

تم تصدير الألوان الفاتحة والداكنة من اللون الأساسي.

تم تصدير الألوان الفاتحة والداكنة من اللون الأساسي.

يحتوي الملف الذي تم إنشاؤه من قِبل "The Color.kt" على ألوان مظهرك وجميع الأدوار المحدّدة لكل من الألوان الفاتحة والداكنة.

Color.kt

package com.example.reply.ui.theme
import androidx.compose.ui.graphics.Color

val md_theme_light_primary = Color(0xFF825500)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
val md_theme_light_secondary = Color(0xFF6F5B40)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
val md_theme_light_tertiary = Color(0xFF51643F)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFFFBFF)
val md_theme_light_onBackground = Color(0xFF1F1B16)
val md_theme_light_surface = Color(0xFFFFFBFF)
val md_theme_light_onSurface = Color(0xFF1F1B16)
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
val md_theme_light_outline = Color(0xFF817567)
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
val md_theme_light_inverseSurface = Color(0xFF34302A)
val md_theme_light_inversePrimary = Color(0xFFFFB951)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF825500)
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
val md_theme_light_scrim = Color(0xFF000000)

val md_theme_dark_primary = Color(0xFFFFB951)
val md_theme_dark_onPrimary = Color(0xFF452B00)
val md_theme_dark_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
val md_theme_dark_secondary = Color(0xFFDDC2A1)
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
val md_theme_dark_onTertiary = Color(0xFF243515)
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF1F1B16)
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
val md_theme_dark_surface = Color(0xFF1F1B16)
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
val md_theme_dark_outline = Color(0xFF9C8F80)
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
val md_theme_dark_inversePrimary = Color(0xFF825500)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
val md_theme_dark_scrim = Color(0xFF000000)


val seed = Color(0xFF825500)

يحتوي ملف واحد (The Theme.kt) تم إنشاؤه على إعداد للألوان الفاتحة والداكنة ومظهر التطبيق. ويتضمّن أيضًا دالة إنشاء الموضوع الرئيسية، AppTheme().

Theme.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable


private val LightColors = lightColorScheme(
   primary = md_theme_light_primary,
   onPrimary = md_theme_light_onPrimary,
   primaryContainer = md_theme_light_primaryContainer,
   onPrimaryContainer = md_theme_light_onPrimaryContainer,
   secondary = md_theme_light_secondary,
   onSecondary = md_theme_light_onSecondary,
   secondaryContainer = md_theme_light_secondaryContainer,
   onSecondaryContainer = md_theme_light_onSecondaryContainer,
   tertiary = md_theme_light_tertiary,
   onTertiary = md_theme_light_onTertiary,
   tertiaryContainer = md_theme_light_tertiaryContainer,
   onTertiaryContainer = md_theme_light_onTertiaryContainer,
   error = md_theme_light_error,
   errorContainer = md_theme_light_errorContainer,
   onError = md_theme_light_onError,
   onErrorContainer = md_theme_light_onErrorContainer,
   background = md_theme_light_background,
   onBackground = md_theme_light_onBackground,
   surface = md_theme_light_surface,
   onSurface = md_theme_light_onSurface,
   surfaceVariant = md_theme_light_surfaceVariant,
   onSurfaceVariant = md_theme_light_onSurfaceVariant,
   outline = md_theme_light_outline,
   inverseOnSurface = md_theme_light_inverseOnSurface,
   inverseSurface = md_theme_light_inverseSurface,
   inversePrimary = md_theme_light_inversePrimary,
   surfaceTint = md_theme_light_surfaceTint,
   outlineVariant = md_theme_light_outlineVariant,
   scrim = md_theme_light_scrim,
)


private val DarkColors = darkColorScheme(
   primary = md_theme_dark_primary,
   onPrimary = md_theme_dark_onPrimary,
   primaryContainer = md_theme_dark_primaryContainer,
   onPrimaryContainer = md_theme_dark_onPrimaryContainer,
   secondary = md_theme_dark_secondary,
   onSecondary = md_theme_dark_onSecondary,
   secondaryContainer = md_theme_dark_secondaryContainer,
   onSecondaryContainer = md_theme_dark_onSecondaryContainer,
   tertiary = md_theme_dark_tertiary,
   onTertiary = md_theme_dark_onTertiary,
   tertiaryContainer = md_theme_dark_tertiaryContainer,
   onTertiaryContainer = md_theme_dark_onTertiaryContainer,
   error = md_theme_dark_error,
   errorContainer = md_theme_dark_errorContainer,
   onError = md_theme_dark_onError,
   onErrorContainer = md_theme_dark_onErrorContainer,
   background = md_theme_dark_background,
   onBackground = md_theme_dark_onBackground,
   surface = md_theme_dark_surface,
   onSurface = md_theme_dark_onSurface,
   surfaceVariant = md_theme_dark_surfaceVariant,
   onSurfaceVariant = md_theme_dark_onSurfaceVariant,
   outline = md_theme_dark_outline,
   inverseOnSurface = md_theme_dark_inverseOnSurface,
   inverseSurface = md_theme_dark_inverseSurface,
   inversePrimary = md_theme_dark_inversePrimary,
   surfaceTint = md_theme_dark_surfaceTint,
   outlineVariant = md_theme_dark_outlineVariant,
   scrim = md_theme_dark_scrim,
)

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

إنّ العنصر الأساسي لتنفيذها في Jetpack Compose هو عنصر MaterialTheme القابل للإنشاء.

يمكنك التفاف MaterialTheme() القابلة للإنشاء في الدالة AppTheme()، والتي تستخدم معلمتَين:

  • useDarkTheme: ترتبط هذه المَعلمة بالدالة isSystemInDarkTheme() لمراقبة إعدادات مواضيع النظام وتطبيق المظهر الفاتح أو الداكن. وإذا كنت تريد إبقاء تطبيقك بمظهر داكن أو فاتح اللون يدويًا، يمكنك ضبط قيمة منطقية إلى useDarkTheme.
  • content - المحتوى الذي سيتم تطبيق المظهر عليه

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
   val colors = if (!useDarkTheme) {
       LightColors
   } else {
       DarkColors
   }

   MaterialTheme(
       colorScheme = colors,
       content = content
   )
}

إذا حاولت تشغيل التطبيق الآن، من المفترض أن يظهر بالشكل نفسه. على الرغم من استيراد نظام الألوان الجديد بألوان مظاهر جديدة، لا يزال بإمكانك رؤية المظاهر الأساسية لأنك لم تطبق المظهر على تطبيق Compose.

تطبيق يتضمن مواضيع أساسية في حال عدم تطبيق أي مظهر.

التطبيق يتضمن مواضيع أساسية في حال عدم تطبيق أي مظهر

لتطبيق المظهر الجديد، في MainActivity.kt، عليك التفاف عنصر ReplyApp الرئيسي القابل للإنشاء مع دالة الموضوع الرئيسية AppTheme().

MainActivity.kt

setContent {
   val uiState by viewModel.uiState.collectAsStateWithLifecycle()

   AppTheme {
       ReplyApp(/*..*/)
   }
}

ويمكنك أيضًا تحديث وظائف المعاينة لرؤية المظهر الذي تم تطبيقه على معاينات التطبيق. التفاف ReplyApp القابلة للإنشاء داخل ReplyAppPreview() مع AppTheme لتطبيقها على المعاينات.

لديك مظهرا النظام الفاتح والداكن محدّدان في مَعلمات المعاينة، لذا سترى كل من المعاينة.

MainActivity.kt

@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_YES,
   name = "DefaultPreviewDark"
)
@Preview(
   uiMode = Configuration.UI_MODE_NIGHT_NO,
   name = "DefaultPreviewLight"
)
@Composable
fun ReplyAppPreview() {
   AppTheme {
       ReplyApp(
           replyHomeUIState = ReplyHomeUIState(
               emails = LocalEmailsDataProvider.allEmails
           )
       )
   }
}

في حال تشغيل التطبيق الآن، من المفترض أن تظهر لك معاينات التطبيق بألوان المظاهر التي تم استيرادها بدلاً من المظهر الأساسي.

fddf7b9cc99b1fe3.png be7a661b4553167b.png

تطبيق بمظهر أساسي (على اليسار)

تطبيق بمظهر الألوان الذي تم استيراده (يمين).

674cec6cc12db6a0.png

معاينات التطبيقات الفاتحة والداكنة مع مظاهر الألوان التي تم استيرادها

تتوافق المادة 3 مع نظام الألوان الفاتحة والداكنة. أنك جمعت التطبيق بالمظهر الذي تم استيراده فقط؛ تستخدم مكونات المادة 3 أدوار الألوان التلقائية.

لنتعرَّف على معلومات عن أدوار الألوان واستخدامها قبل البدء في إضافتها إلى التطبيق.

أدوار الألوان وإمكانية الوصول

يمكن استخدام كل دور لوني في مجموعة متنوعة من الأماكن اعتمادًا على حالة المكوِّن وبروزه وتركيزه.

1f184a05ea57aa84.png

أدوار الألوان للألوان الأساسية والثانوية والثلاثية.

الأساسي هو اللون الأساسي الذي يُستخدم للمكوّنات الرئيسية مثل الأزرار البارزة والحالات النشطة.

يتم استخدام لون المفتاح الثانوي للمكوّنات الأقل بروزًا في واجهة المستخدم، مثل شرائح الفلاتر.

ويتم استخدام اللون الرئيسي الثالث لتوفير لمسات متباينة، واستخدام ألوان محايدة للخلفية والأسطح في التطبيق.

يوفّر نظام ألوان المواد قيمًا لدرجات اللون وقياسات قياسية يمكن استخدامها لتلبية نسب التباين التي يمكن الوصول إليها. يمكنك استخدام القيم الأساسية في أعلى الحاوية الأساسية، وفي الحاوية الأساسية فوق الحاوية الأساسية، ونفسها مع غيرها من ألوان التمييز والألوان المحايدة لتوفير تباين يمكن للمستخدم الوصول إليه.

لمزيد من المعلومات، يُرجى الاطّلاع على أدوار الألوان وإمكانية الوصول.

ارتفاعات الدرجات اللونية والظل

تمثل المادة 3 المسقط الرأسي باستخدام تراكبات ألوان الدرجات اللونية بشكل أساسي. تعد هذه طريقة جديدة للتمييز بين الحاويات والأسطح من بعضها البعض، مع زيادة ارتفاع درجات اللون، تستخدم درجات لونية أكثر بروزًا، بالإضافة إلى الظلال.

ارتفاع الدرجة اللونية مع ارتفاع الظل ارتفاع درجة اللون في المستوى 2 والذي يأخذ اللون من خانة اللون الأساسية.

تم أيضًا تغيير تراكبات الارتفاع في المظاهر الداكنة إلى تراكبات ألوان الدرجات اللونية في Material Design 3. يأتي لون التراكب من فتحة اللون الأساسية.

يشمل سطح M3، الجزء الخلفي الذي يمكن تكوينه خلف معظم مكونات M3، دعمًا لارتفاع الدرجات اللونية والظل:

Surface(
   modifier = modifier,
   tonalElevation = {..}
   shadowElevation = {..}
) {
   Column(content = content)
}

إضافة الألوان إلى التطبيق

في حال تشغيل التطبيق، يمكنك رؤية الألوان التي تم تصديرها في التطبيق حيث تظهر المكوّنات بألوان تلقائية. الآن بعد أن عرفنا أدوار الألوان واستخدامها، لنجعل التطبيق مظهرًا لأدوار الألوان الصحيحة.

be7a661b4553167b.png

تطبيق بمظهر الألوان ومكوّنات تتولى أدوار الألوان التلقائية

ألوان السطح

في الشاشة الرئيسية، ستبدأ بالتفاف التطبيق الرئيسي القابل للإنشاء في Surface() لتوفير القاعدة لوضع محتوى التطبيق فوقه. افتح MainActivity.kt ويلتفّ عنصر ReplyApp() القابل للإنشاء باستخدام Surface.

ستقدم أيضًا ارتفاعًا تدرّجيًا يبلغ 5.dp لمنح السطح لونًا لونيًا للخانة الأساسية، ما يساعد في توفير التباين مقابل عنصر القائمة وشريط البحث أعلاه. يكون ارتفاع درجة اللون والظل للسطح تلقائيًا 0.dp.

MainActivity.kt

AppTheme {
   Surface(tonalElevation = 5.dp) {
       ReplyApp(
           replyHomeUIState = uiState,
          // other parameters
         )
   }
}

إذا قمت بتشغيل تطبيقك الآن ورأيت صفحة "القائمة" و"التفاصيل"، يُفترض أن ترى سطح الدرجات اللونية مطبقًا على التطبيق بأكمله.

be7a661b4553167b.png e70d762495173610.png

خلفية التطبيق بدون سطح ولون اللون (على اليسار)

خلفية التطبيق مع تطبيق السطح واللون اللوني (على اليمين)

ألوان شريط التطبيق

ليس لشريط البحث المخصص في الأعلى خلفية واضحة مثل طلبات التصميم. ويتم تلقائيًا ضبطه على سطح القاعدة التلقائي. يمكنك توفير خلفية الفصل بين الصور بشكل واضح.

5779fc399d8a8187.png

شريط بحث مخصّص بدون خلفية (اليسار)

شريط بحث مخصّص مع خلفية (إلى اليمين)

ستتمكن الآن من تعديل تطبيق ui/components/ReplyAppBars.kt، الذي يحتوي على شريط التطبيقات. ستضيف MaterialTheme.colorScheme.background إلى Modifier الخاصة بـ Row القابلة للتعديل.

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

يُفترض أن يظهر الآن فصل واضح بين سطح الدرجات اللونية وشريط التطبيق بلون الخلفية.

b1b374b801dadc06.png

شريط البحث الذي يظهر لون الخلفية فوق سطح درجات اللون

ألوان زر الإجراءات العائمة

70ceac87233fe466.png

زر الإجراء الرئيسي (FAB) كبير بدون تطبيق أي مواضيع (لليسار).

زر الإجراء الرئيسي (FAB) كبير الحجم بمظهر مع لون ثالث (يمين).

على الشاشة الرئيسية، يمكنك تحسين مظهر زر الإجراء الرئيسي (FAB) بحيث يظهر كزر لعبارة الحث على اتّخاذ إجراء. لتنفيذ ذلك، يتم استخدام لون تمييز ثالث لها.

في ملف ReplyListContent.kt، عدِّل containerColor للعنصر الرئيسي (FAB) إلى tertiaryContainer ولون المحتوى إلى onTertiaryContainer للحفاظ على إمكانية الوصول وتباين الألوان.

ReplyListContent.kt

ReplyInboxScreen(/*..*/) {
// Email list content
  LargeFloatingActionButton(
    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
    contentColor = MaterialTheme.colorScheme.onTertiaryContainer
  ){
   /*..*/   
  }
}

يمكنك تشغيل التطبيق للاطّلاع على مظهر زر الإجراء الرئيسي (FAB). أنت تستخدم LargeFloatingActionButton في هذا الدرس التطبيقي حول الترميز.

ألوان البطاقة

تستخدم قائمة عناوين البريد الإلكتروني على الشاشة الرئيسية مكون بطاقة. تكون هذه الطريقة بطاقة معبأة تستخدم لون خيار السطح للون الحاوية لتوفير فصل واضح بين لون السطح ولون البطاقة. يوفر Compose أيضًا إمكانية تنفيذ ElevatedCard وOutlinedCard.

يمكنك تمييز بعض العناصر المهمة بشكل أكبر من خلال توفير درجات ألوان ثانوية. يمكنك تعديل ui/components/ReplyEmailListItem.kt من خلال تعديل لون حاوية البطاقة باستخدام CardDefaults.cardColors() للرسائل الإلكترونية المهمة:

ReplyEmailListItem.kt

Card(
   modifier =  modifier
       .padding(horizontal = 16.dp, vertical = 4.dp)
       .semantics { selected = isSelected }
       .clickable { navigateToDetail(email.id) },
   colors = CardDefaults.cardColors(
       containerColor = if (email.isImportant)
           MaterialTheme.colorScheme.secondaryContainer
       else MaterialTheme.colorScheme.surfaceVariant
   )
){
  /*..*/   
}

5818200be0b01583.png 9367d40023db371d.png

ميِّز عنصر القائمة باستخدام لون الحاوية الثانوي على سطح درجات اللون.

لون عنصر قائمة التفاصيل

الآن، لقد انتهيت من تصميم شاشتك الرئيسية. يمكنك إلقاء نظرة على صفحة التفاصيل من خلال النقر على أي من عناصر قائمة عناوين البريد الإلكتروني.

7a9ea7cf3e91e9c7.png 79b3874aeca4cd1.png

صفحة التفاصيل التلقائية بدون عنصر قائمة حسب الموضوع (على اليسار)

عنصر قائمة التفاصيل مع تطبيق مظهر الخلفية (على اليمين).

لا يتم تطبيق أي لون على عنصر القائمة، لذلك يتم استخدام لون السطح التلقائي. سيتم تطبيق لون الخلفية على عنصر القائمة لإنشاء فصل وإضافة مساحة متروكة لتوفير مسافات حول الخلفية.

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(MaterialTheme.colorScheme.background)
           .padding(20.dp)
    ) {
      // List item content
    }
}

يمكنك أن ترى أنه، فقط من خلال توفير الخلفية، يكون لديك فصل واضح بين سطح الدرجات اللونية وعنصر القائمة.

لديك الآن كل من الصفحة الرئيسية وصفحة التفاصيل باستخدام الأدوار واستخدام الألوان الصحيحة . لنرَ كيف يمكن لتطبيقك الاستفادة من الألوان الديناميكية لتوفير تجربة أكثر تخصيصًا وتماسكًا.

5- إضافة ألوان ديناميكية في التطبيق

اللون الديناميكي هو الجزء الأساسي من المادة 3، والذي تستمد فيه الخوارزمية ألوانًا مخصّصة من خلفية المستخدم لتطبيقها على التطبيقات وواجهة مستخدم النظام.

تجعل المواضيع الديناميكية تطبيقاتك أكثر تخصيصًا. ويوفّر أيضًا للمستخدمين تجربة متّسقة وسلسة مع مظهر النظام.

تتوفّر الألوان الديناميكية على نظام التشغيل Android 12 والإصدارات الأحدث. إذا كان اللون الديناميكي متاحًا، يمكنك إعداد ألوان ديناميكية باستخدام dynamicDarkColorScheme() أو dynamicLightColorScheme(). وفي حال عدم تفعيلها، عليك العودة إلى استخدام ColorScheme إضاءة أو داكنة بشكل تلقائي.

استبدِل رمز الدالة AppTheme في ملف Theme.kt بالرمز التالي:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
   val context = LocalContext.current
   val colors = when {
       (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) -> {
           if (useDarkTheme) dynamicDarkColorScheme(context)
           else dynamicLightColorScheme(context)
       }
       useDarkTheme -> DarkColors
       else -> LightColors
   }
   
      MaterialTheme(
       colorScheme = colors,
       content = content
     )
}

fecc63b4c6034236.png

مظهر ديناميكي مأخوذ من خلفية Android 13.

عند تشغيل التطبيق الآن، من المفترض أن يتم تطبيق المواضيع الديناميكية باستخدام الخلفية التلقائية لنظام التشغيل Android 13.

وقد تحتاج أيضًا إلى تغيير نمط شريط الحالة ديناميكيًا اعتمادًا على نظام الألوان المستخدم لتصميم تطبيقك.

1095e2b2c1ffdc14.png

تم تطبيق لون التطبيق بدون شريط الحالة (السماعة اليسرى).

تم تطبيق لون شريط الحالة على التطبيق (إلى اليمين).

لتعديل لون شريط الحالة بناءً على اللون الأساسي لمظهرك، أضِف لون شريط الحالة بعد اختيار الألوان في عنصر AppTheme القابل للإنشاء:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean =  isSystemInDarkTheme(),
   content: @Composable () -> Unit
) {
 
 // color scheme selection code

 // Add primary status bar color from chosen color scheme.
 val view = LocalView.current
 if (!view.isInEditMode) {
    SideEffect {
        val window = (view.context as Activity).window
        window.statusBarColor = colors.primary.toArgb()
        WindowCompat
            .getInsetsController(window, view)
            .isAppearanceLightStatusBars = useDarkTheme
    }
 }
   
  MaterialTheme(
    colorScheme = colors,
     content = content
   )
}

عند تشغيل التطبيق، من المفترض أن يظهر شريط الحالة بألوانه الأساسية. يمكنك أيضًا تجربة المظهرَين الديناميكي الفاتح والداكن من خلال تغيير المظهر الداكن للنظام.

69093b5bce31fd43.png

تم تطبيق المظهرَين الفاتح والداكن (اليسار) والداكن (اليمين) مع الخلفية التلقائية في Android 13.

حتى الآن، تم تطبيق ألوان على تطبيقك ساهمت في تحسين شكل التطبيق. ومع ذلك، يمكنك ملاحظة أن كل النص في التطبيق بنفس الحجم، لذا يمكنك الآن إضافة أسلوب الخط إلى التطبيق.

6- فن الطباعة

يحدد التصميم المتعدد الأبعاد 3 مقياس كتابة. وقد تم تبسيط التسمية والتجميع لـ: العرض، والعنوان، والعنوان، والنص الأساسي، والتصنيف، مع استخدام أحجام كبيرة ومتوسطة وصغيرة لكل منها.

999a161dcd9b0ec4.png

مقياس نوع المادة 3.

تعريف أسلوب الخط

توفِّر ميزة Compose لفئة M3 Typography، بالإضافة إلى فئات TextStyle وfont-related الحالية، نموذجًا لمقياس النوع Material 3.

تقدم الدالة الإنشائية لأسلوب الخط إعدادات افتراضية لكل نمط حتى تتمكن من حذف أي معلمات لا تريد تخصيصها. لمزيد من المعلومات، يمكنك الاطّلاع على أنماط أسلوب الخط وقيمها التلقائية.

ستستخدم خمسة أنماط لأسلوب الخط في تطبيقك: headlineSmall وtitleLarge وbodyLarge وbodyMedium وlabelMedium. ستغطي هذه الأنماط كلاً من الشاشة الرئيسية وشاشة التفاصيل.

شاشة تعرض استخدام أسلوب الخط للعنوان والتصنيف ونمط النص الأساسي.

شاشة تعرض استخدام أسلوب الخط للعنوان والتصنيف ونمط النص الأساسي.

بعد ذلك، انتقِل إلى حزمة ui/theme وافتح Type.kt. أضِف الرمز التالي لتقديم التنفيذ الخاص بك لبعض أنماط النص بدلاً من القيم التلقائية:

Type.kt

val typography = Typography(
   headlineSmall = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 24.sp,
       lineHeight = 32.sp,
       letterSpacing = 0.sp
   ),
   titleLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 18.sp,
       lineHeight = 28.sp,
       letterSpacing = 0.sp
   ),
   bodyLarge = TextStyle(
       fontWeight = FontWeight.Normal,
       fontSize = 16.sp,
       lineHeight = 24.sp,
       letterSpacing = 0.15.sp
   ),
   bodyMedium = TextStyle(
       fontWeight = FontWeight.Medium,
       fontSize = 14.sp,
       lineHeight = 20.sp,
       letterSpacing = 0.25.sp
   ),
   labelMedium = TextStyle(
       fontWeight = FontWeight.SemiBold,
       fontSize = 12.sp,
       lineHeight = 16.sp,
       letterSpacing = 0.5.sp
   )
)

تم تحديد أسلوب الخط الآن. لإضافتها إلى المظهر، مرِّره إلى عنصر MaterialTheme() القابل للإنشاء داخل AppTheme:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       content = content
   )
}

العمل على أسلوب الخط

تمامًا مثل الألوان، ستصل إلى نمط الطباعة للمظهر الحالي باستخدام MaterialTheme.typography. يمنحك هذا مثيل أسلوب الخط لاستخدام أسلوب الخط المحدّد في Type.k.

Text(
   text = "Hello M3 theming",
   style = MaterialTheme.typography.titleLarge
)

Text(
   text = "you are learning typography",
   style = MaterialTheme.typography.bodyMedium
)

من المحتمل ألا يحتاج منتجك إلى جميع الأنماط الافتراضية الخمسة عشر من مقياس النوع "تصميم متعدد الأبعاد". في هذا الدرس التطبيقي حول الترميز، يتم اختيار خمسة أحجام بينما يتم حذف الباقي.

بما أنّك لم تطبّق أسلوب الخط على العناصر القابلة للإنشاء Text(، سيتم تلقائيًا الرجوع إلى Typography.bodyLarge.

أسلوب الخط في القائمة الرئيسية

بعد ذلك، طبِّق أسلوب الخط على الدالة ReplyEmailListItem في ui/components/ReplyEmailListItem.kt للتمييز بين العناوين والتصنيفات:

ReplyEmailListItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.titleLarge,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   overflow = TextOverflow.Ellipsis
)

90645c0765167bb7.png 6c4af2f412c18bfb.png

تم تطبيق نمط الخط على الشاشة الرئيسية (اليسرى).

الشاشة الرئيسية مع تطبيق أسلوب الخط (اليمين)

أسلوب الخط في قائمة التفاصيل

وبالمثل، ستضيف أسلوب الخط في شاشة التفاصيل من خلال تعديل كل النصوص القابلة للإنشاء من ReplyEmailThreadItem في ui/components/ReplyEmailThreadItem.kt:

ReplyEmailThreadItem.kt

Text(
   text = email.sender.firstName,
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = stringResource(id = R.string.twenty_mins_ago),
   style = MaterialTheme.typography.labelMedium
)

Text(
   text = email.subject,
   style = MaterialTheme.typography.bodyMedium,
   modifier = Modifier.padding(top = 12.dp, bottom = 8.dp),
)

Text(
   text = email.body,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

543ac09e43d8761.png 3412771e95a45f36.png

تم تطبيق شاشة التفاصيل بدون أسلوب الخط (على اليسار).

شاشة التفاصيل مع تطبيق أسلوب الخط (اليمين)

تخصيص أسلوب الخط

من خلال ميزة "الكتابة"، من السهل جدًا تخصيص نمط النص أو توفير خط مخصّص. يمكنك تعديل TextStyle لتخصيص نوع الخط ومجموعة الخطوط وتباعد الأحرف وما إلى ذلك.

سيتم تغيير نمط النص في ملف theme/Type.kt، وسيتم تطبيقه على كل المكوّنات التي تستخدمه.

عدِّل fontWeight إلى SemiBold ومن lineHeight إلى 32.sp للسمة titleLarge، حيث يتم استخدام هذا الإعداد للموضوع في عنصر القائمة. سيركز بشكل أكبر على الموضوع ويوفر فواصل واضحة.

Type.kt

...
titleLarge = TextStyle(
   fontWeight = FontWeight.SemiBold,
   fontSize = 18.sp,
   lineHeight = 32.sp,
   letterSpacing = 0.0.sp
),
...

f8d2212819eb0b61.png

تطبيق أسلوب الخط المخصّص على نص الموضوع:

7. الأشكال

يمكن عرض الأسطح المادية بأشكال مختلفة. أشكال الانتباه المباشر، وتحديد المكونات، وتوصيل الحالة، والتعبير عن العلامة التجارية.

تحديد الأشكال

توفِّر ميزة Compose لفئة Shapes مَعلمات موسّعة لتنفيذ أشكال M3 الجديدة. يشبه مقياس الشكل M3، الذي يشبه مقياس الكتابة، نطاقًا معبرًا من الأشكال في واجهة المستخدم.

هناك أحجام مختلفة للأشكال في مقياس الشكل:

  • صغير جدًا
  • صغير
  • متوسطة
  • كبير
  • كبير جدًا

بشكل افتراضي، يكون لكل شكل قيمة افتراضية يمكن تجاوزها. بالنسبة إلى تطبيقك، ستستخدم الشكل المتوسط لتعديل عنصر القائمة، ولكن يمكنك الإعلان عن أشكال أخرى أيضًا. أنشئ ملفًا جديدًا باسم Shape.kt في حزمة ui/theme وأضف الرمز للأشكال:

Shape.kt

package com.example.reply.ui.theme

import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp

val shapes = Shapes(
   extraSmall = RoundedCornerShape(4.dp),
   small = RoundedCornerShape(8.dp),
   medium = RoundedCornerShape(16.dp),
   large = RoundedCornerShape(24.dp),
   extraLarge = RoundedCornerShape(32.dp)
)

الآن بعد أن حددت shapes، مرره إلى M3 MaterialTheme كما فعلت مع الألوان وأسلوب الخط:

Theme.kt

@Composable
fun AppTheme(
   useDarkTheme: Boolean = isSystemInDarkTheme(),
   content: @Composable() () -> Unit
) {
  // dynamic theming content

   MaterialTheme(
       colorScheme = colors,
       typography = typography,
       shapes = shapes
       content = content
   )
}

التعامل مع الأشكال

تمامًا كما هو الحال مع اللون وأسلوب الخط، يمكنك تطبيق الأشكال على مكونات Material باستخدام MaterialTheme.shape، والذي يمنحك مثيل Shape للوصول إلى أشكال Material.

العديد من مكونات Material لديها بالفعل أشكال افتراضية مطبقة عليها، ولكن يمكنك توفير الأشكال الخاصة بك وتطبيقها على المكونات عبر الفتحات المتاحة.

Card(shape = MaterialTheme.shapes.medium) { /* card content */ }
FloatingActionButton(shape = MaterialTheme.shapes.large) { /* fab content */}

قيم الأشكال التلقائية لجميع مكونات Material 3.تعيين مكونات Material باستخدام أنواع مختلفة من الأشكال.

يمكنك الاطلاع على تعيين الأشكال لجميع المكونات في مستندات الشكل.

هناك شكلان آخران متاحون للاستخدام: RectangleShape وCircleShape، وهما جزءان من ميزة Compose. لا يحتوي شكل المستطيل على نصف قطر حدي، بينما يُظهر شكل الدائرة حواف مدورة بالكامل.

يمكنك أيضًا تطبيق الأشكال على المكوّنات باستخدام Modifiers التي تتخذ أشكالاً، مثل Modifier.clip وModifier.background وModifier.border.

شكل شريط التطبيق

نريد أن يكون لشريط التطبيق خلفية مستديرة بزاوية:

f873392abe535494.png

يستخدم TopAppBar عنصر Row مع لون خلفية. لعرض الخلفية المستديرة، حدِّد شكل الخلفية من خلال تمرير CircleShape إلى أداة تعديل الخلفية:

ReplyAppBars.kt

@Composable
fun ReplySearchBar(modifier: Modifier = Modifier) {
   Row(
       modifier = modifier
           .fillMaxWidth()
           .padding(16.dp)
           .background(
               MaterialTheme.colorScheme.background,
               CircleShape
           ),
       verticalAlignment = Alignment.CenterVertically
   ) {
       // Search bar content
   }
}

f873392abe535494.png

شكل عنصر قائمة التفاصيل

أنت تستخدم على الشاشة الرئيسية بطاقة تستخدم Shape.Medium بشكل تلقائي. ومع ذلك، بالنسبة لصفحة التفاصيل لدينا، استخدمت عمودًا بلون الخلفية بدلاً من ذلك. للحصول على مظهر موحّد للقائمة، ضع شكلاً متوسطًا عليها.

3412771e95a45f36.png 80ee881c41a98c2a.png

عمود عنصر قائمة التفاصيل بدون شكل في عنصر القائمة (الأيسر) وشكل متوسط في القائمة (اليمين).

ReplyEmailThreadItem.kt

@Composable
fun ReplyEmailThreadItem(
   email: Email,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
           .fillMaxWidth()
           .padding(8.dp)
           .background(
               MaterialTheme.colorScheme.background,
               MaterialTheme.shapes.medium
           )
           .padding(16.dp)

   ) {
      // List item content
      
   }
}

يؤدي تشغيل تطبيقك الآن إلى عرض عنصر قائمة شاشة تفصيلي على شكل medium.

8. التوكيد

ومن خلال ميزة التوكيد في واجهة المستخدم، يمكنك إبراز بعض المحتوى بدلاً من المحتوى الآخر، مثلاً عندما تريد التمييز بين العنوان والترجمة. التوكيد في M3 يستخدم أشكالاً مختلفة من الألوان ومجموعاتها على الألوان. لديك طريقتان لإضافة التوكيد:

  1. استخدام السطح وتباين السطح والخلفية إلى جانب الألوان على السطح والمتغيرات على السطح من نظام الألوان M3 الموسَّع

على سبيل المثال، يمكن استخدام السطح مع المتغير السطحي، ويمكن استخدام متغير السطح مع الشكل السطحي لتوفير مستويات مختلفة من التوكيد.

ويمكن أيضًا استخدام خيارات السطح مع ألوان التمييز لتوفير تركيز أقل من الألوان البارزة، ولكن مع إمكانية الوصول إليها واتّباع نسبة التباين.

أدوار ألوان خيارات سطح السطح والخلفية والسطح

أدوار ألوان خيارات سطح السطح والخلفية والسطح

  1. استخدام سُمك خط مختلفة للنص كما رأيت في قسم أسلوب الخط، يمكنك توفير ترجيحات مخصصة لمقياس الكتابة لتوفير توكيد مختلف.

بعد ذلك، يجب تعديل ReplyEmailListItem.kt لعرض فرق التوكيد باستخدام خيار السطح. حسب الإعدادات التلقائية، سيظهر محتوى البطاقة بلون المحتوى التلقائي استنادًا إلى الخلفية.

سيتم تعديل لون نص الوقت ولون النص الأساسي القابل للإنشاء إلى onSurfaceVariant. يؤدي ذلك إلى تقليل مستوى التوكيد مقارنةً بـ onContainerColors، والذي يتم تطبيقه تلقائيًا على نص الموضوع والعنوان القابل للإنشاء.

2c9b7f2bd016edb8.png 6850ff391f21e4ba.png

الوقت والنص الأساسي مع التركيز نفسه على الموضوع والعنوان (على اليسار).

الوقت والجسم مع انخفاض التركيز على الموضوع والعنوان (على اليمين).

ReplyEmailListItem.kt

Text(
   text = email.createdAt,
   style = MaterialTheme.typography.labelMedium,
   color = MaterialTheme.colorScheme.onSurfaceVariant
)

Text(
   text = email.body,
   maxLines = 2,
   style = MaterialTheme.typography.bodyLarge,
   color = MaterialTheme.colorScheme.onSurfaceVariant
   overflow = TextOverflow.Ellipsis
)

بالنسبة إلى بطاقة البريد الإلكتروني المهمة التي تظهر في الخلفية secondaryContainer، يكون كل ألوان النص هو onSecondaryContainer تلقائيًا. بالنسبة إلى الرسائل الإلكترونية الأخرى، تكون الخلفية surfaceVariant,، وبالتالي يكون اللون التلقائي لكل النص onSurfaceVariant.

9. تهانينا

تهانينا! لقد أكملت هذا الدرس التطبيقي حول الترميز بنجاح. لقد نفذت مظاهر Material باستخدام Compose باستخدام الألوان وأسلوب الخط والأشكال إلى جانب الألوان الديناميكية لموضوع تطبيقك وتوفير تجربة مخصصة.

2d8fcabf15ac5202.png 5a4d31db0185dca6.png ce009e4ce560834d.png

يتم تطبيق نهاية نتائج البحث باستخدام ألوان ديناميكية ومظهر ألوان.

الخطوات التالية

يمكنك الاطّلاع على الدروس التطبيقية الأخرى حول الترميز في مسار الإنشاء:

محتوى إضافي للقراءة

نماذج التطبيقات

المستندات المرجعية