MDC-102 Flutter: मटीरियल स्ट्रक्चर और लेआउट

1. परिचय

logo_components_color_2x_web_96dp.png

Material Components (एमडीसी), डेवलपर को Material Design लागू करने में मदद करते हैं. MDC को Google के इंजीनियर और UX डिज़ाइनर की टीम ने बनाया है. इसमें सुंदर और काम के कई यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट हैं. यह Android, iOS, वेब, और Flutter के लिए उपलब्ध है.material.io/develop

कोडलैब MDC-101 में, आपने लॉगिन पेज बनाने के लिए दो मटीरियल कॉम्पोनेंट इस्तेमाल किए: टेक्स्ट फ़ील्ड और इंक रिपल वाले बटन. आइए, अब नेविगेशन, स्ट्रक्चर, और डेटा जोड़कर इस बुनियाद के बारे में और जानें.

आपको क्या बनाना होगा

इस कोडलैब में, आपको Shrine नाम के ऐप्लिकेशन के लिए होम स्क्रीन बनानी होगी. यह एक ई-कॉमर्स ऐप्लिकेशन है, जो कपड़े और घर के सामान बेचता है. इसमें ये चीज़ें शामिल होंगी:

  • टॉप ऐप्लिकेशन बार
  • प्रॉडक्ट की ग्रिड वाली लिस्ट

Android

iOS

ई-कॉमर्स ऐप्लिकेशन, जिसमें सबसे ऊपर ऐप्लिकेशन बार और प्रॉडक्ट से भरा ग्रिड है

ई-कॉमर्स ऐप्लिकेशन, जिसमें सबसे ऊपर ऐप्लिकेशन बार और प्रॉडक्ट से भरा ग्रिड है

इस कोडलैब में मौजूद Material Flutter कॉम्पोनेंट और सबसिस्टम

  • टॉप ऐप्लिकेशन बार
  • ग्रिड
  • कार्ड

Flutter डेवलपमेंट के साथ अपने अनुभव के आधार पर आप क्या रेटिंग देंगे?

शुरुआती इंटरमीडिएट विशेषज्ञ

2. Flutter डेवलपमेंट एनवायरमेंट सेट अप करना

इस लैब को पूरा करने के लिए, आपके पास दो सॉफ़्टवेयर होने चाहिए— Flutter SDK टूल और एडिटर.

इनमें से किसी भी डिवाइस का इस्तेमाल करके, कोडलैब चलाया जा सकता है:

  • आपके कंप्यूटर से कनेक्ट किया गया Android या iOS डिवाइस, जो डेवलपर मोड पर सेट हो.
  • iOS सिम्युलेटर (इसके लिए, Xcode टूल इंस्टॉल करने की ज़रूरत है).
  • Android Emulator (Android Studio में सेटअप करना ज़रूरी है).
  • ब्राउज़र (डीबग करने के लिए Chrome ज़रूरी है).
  • Windows, Linux या macOS डेस्कटॉप ऐप्लिकेशन के तौर पर. आपको उस प्लैटफ़ॉर्म पर गेम बनाना होगा जहां आपको इसे डिप्लॉय करना है. इसलिए, अगर आपको Windows डेस्कटॉप ऐप्लिकेशन बनाना है, तो आपको सही बिल्ड चेन ऐक्सेस करने के लिए, Windows पर डेवलप करना होगा. ऑपरेटिंग सिस्टम से जुड़ी कुछ खास शर्तें हैं, जिनके बारे में docs.flutter.dev/desktop पर जानकारी दी गई है.

3. कोडलैब का स्टार्टर ऐप्लिकेशन डाउनलोड करना

क्या MDC-101 से आगे बढ़ना है?

अगर आपने MDC-101 पूरा कर लिया है, तो आपका कोड इस कोडलैब के लिए तैयार होना चाहिए. सीधे चरण पर जाएं: सबसे ऊपर मौजूद ऐप्लिकेशन बार जोड़ें.

क्या आपको नए सिरे से शुरुआत करनी है?

स्टार्टर कोडलैब ऐप्लिकेशन डाउनलोड करना

स्टार्टर ऐप्लिकेशन, material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series डायरेक्ट्री में मौजूद है.

...या GitHub से इसका क्लोन बनाएं

GitHub से इस कोडलैब का क्लोन बनाने के लिए, ये निर्देश चलाएं:

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 102-starter_and_101-complete

प्रोजेक्ट खोलें और ऐप्लिकेशन चलाएं

  1. अपनी पसंद के एडिटर में प्रोजेक्ट खोलें.
  2. चुने गए एडिटर के लिए, शुरू करें: टेस्ट ड्राइव में "ऐप्लिकेशन चलाएं" के लिए दिए गए निर्देशों का पालन करें.

हो गया! आपको अपने डिवाइस पर एमडीसी-101 कोडलैब से श्राइन का लॉगिन पेज दिखेगा.

Android

iOS

उपयोगकर्ता नाम और पासवर्ड फ़ील्ड, रद्द करें और अगले बटन के साथ लॉगिन पेज

उपयोगकर्ता नाम और पासवर्ड फ़ील्ड, रद्द करें और अगले बटन के साथ लॉगिन पेज

अब लॉगिन स्क्रीन ठीक लग रही है, इसलिए ऐप्लिकेशन को अपने-आप कुछ प्रॉडक्ट से भर देते हैं.

4. सबसे ऊपर ऐप्लिकेशन बार जोड़ना

फ़िलहाल, "आगे बढ़ें" बटन पर क्लिक करने पर, आपको होम स्क्रीन दिखेगी. इस पर "आपने कर लिया!" लिखा होगा. बहुत बढ़िया! हालांकि, अब हमारे उपयोगकर्ता के पास कोई कार्रवाई करने या यह जानने की सुविधा नहीं है कि वे ऐप्लिकेशन में कहां हैं. उनकी मदद करने के लिए, नेविगेशन जोड़ना ज़रूरी है.

मटीरियल डिज़ाइन में नेविगेशन पैटर्न मौजूद हैं, ताकि इन्हें बेहतर तरीके से इस्तेमाल किया जा सके. सबसे ज़्यादा दिखने वाले कॉम्पोनेंट में से एक कॉम्पोनेंट, सबसे ऊपर मौजूद ऐप्लिकेशन बार होता है.

नेविगेशन की सुविधा देने और उपयोगकर्ताओं को अन्य कार्रवाइयों का तुरंत ऐक्सेस देने के लिए, सबसे ऊपर ऐप्लिकेशन बार जोड़ें.

AppBar विजेट जोड़ना

home.dart में, स्कैफ़ोल्ड में एक ऐप्लिकेशन बार जोड़ें और हाइलाइट किए गए const को हटाएं:

return const Scaffold(
  // TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
  ),

स्कैफ़ोल्ड के appBar: फ़ील्ड में AppBar जोड़ने पर, हमें बिना किसी शुल्क के एक बेहतरीन लेआउट मिलता है. इसमें, AppBar पेज में सबसे ऊपर और बॉडी उसके नीचे दिखती है.

टेक्स्ट विजेट जोड़ें

home.dart में, AppBar में कोई टाइटल जोड़ें:

// TODO: Add app bar (102)
  appBar: AppBar(
    // TODO: Add buttons and title (102)
    title: const Text('SHRINE'),
    // TODO: Add trailing buttons (102)

अपना प्रोजेक्ट सेव करें.

Android

iOS

ऐसा ऐप्लिकेशन बार जिसके टाइटल के तौर पर श्राइन है

टाइटल के तौर पर Shrine वाला ऐप्लिकेशन बार

कई ऐप्लिकेशन बार में, टाइटल के बगल में एक बटन होता है. अपने ऐप्लिकेशन में मेन्यू आइकॉन जोड़ें.

पहले आइकॉन का आइकॉन जोड़ें

home.dart में ही रहते हुए, AppBar के leading: फ़ील्ड के लिए आइकॉनबटन सेट करें. (शुरुआत से आखिर तक के क्रम को दोहराने के लिए, इसे title: फ़ील्ड से पहले रखें):

    // TODO: Add buttons and title (102)
    leading: IconButton(
      icon: const Icon(
        Icons.menu,
        semanticLabel: 'menu',
      ),
      onPressed: () {
        print('Menu button');
      },
    ),

अपना प्रोजेक्ट सेव करें.

Android

iOS

ऐप्लिकेशन बार, जिसमें श्राइन का टाइटल है और हैमबर्गर मेन्यू आइकॉन

ऐप्लिकेशन बार, जिसमें शीर्षक के तौर पर 'श्राइन' और हैमबर्गर मेन्यू आइकॉन है

मेन्यू आइकॉन (जिसे "हैमबर्गर" भी कहा जाता है) ठीक वहीं दिखता है जहां आपको उम्मीद होती है.

टाइटल के आखिर में बटन भी जोड़े जा सकते हैं. Flutter में, इन्हें "कार्रवाइयां" कहा जाता है.

कार्रवाइयां जोड़ें

दो और आइकॉन बटन जोड़े जा सकते हैं.

उन्हें टाइटल के बाद, AppBar इंस्टेंस में जोड़ें:

// TODO: Add trailing buttons (102)
actions: <Widget>[
  IconButton(
    icon: const Icon(
      Icons.search,
      semanticLabel: 'search',
    ),
    onPressed: () {
      print('Search button');
    },
  ),
  IconButton(
    icon: const Icon(
      Icons.tune,
      semanticLabel: 'filter',
    ),
    onPressed: () {
      print('Filter button');
    },
  ),
],

अपना प्रोजेक्ट सेव करें. आपकी होम स्क्रीन इस तरह दिखेगी:

Android

iOS

ऐप्लिकेशन बार, जिसमें Shrine के तौर पर टाइटल और हैमबर्गर मेन्यू आइकॉन है. साथ ही, ट्रेलिंग सर्च और पसंद के मुताबिक बनाने के आइकॉन भी हैं

ऐप्लिकेशन बार, जिसमें टाइटल के तौर पर Shrine और हैमबर्गर मेन्यू आइकॉन है. साथ ही, ट्रेलिंग सर्च और पसंद के मुताबिक बनाने के आइकॉन भी हैं

अब ऐप्लिकेशन में लीडिंग बटन, टाइटल, और दाईं ओर दो कार्रवाइयाँ मौजूद हैं. ऐप्लिकेशन बार में, ऊंचाई भी दिखती है. इसके लिए, हल्की छाया का इस्तेमाल किया जाता है, ताकि यह पता चल सके कि यह कॉन्टेंट से अलग लेयर पर है.

5. ग्रिड में कार्ड जोड़ना

अब हमारे ऐप्लिकेशन का स्ट्रक्चर तैयार है. अब कॉन्टेंट को कार्ड में डालकर व्यवस्थित करते हैं.

GridView जोड़ना

सबसे ऊपर मौजूद ऐप्लिकेशन बार के नीचे, एक कार्ड जोड़कर शुरुआत करते हैं. सिर्फ़ कार्ड विजेट के पास खुद को दिखाने के लिए इतनी जानकारी नहीं है कि हम उसे कहां देख सकते हैं. इसलिए, हम इसे GridView विजेट में इनकैप्सुलेट करना चाहेंगे.

स्caffol की बॉडी में मौजूद Center को GridView से बदलें:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  // TODO: Build a grid of cards (102)
  children: <Widget>[Card()],
),

चलिए, इस कोड को अनपैक करते हैं. GridView count() कंस्ट्रक्टर को शुरू करता है, क्योंकि इसमें जितने आइटम दिखते हैं उन्हें गिना जा सकता है, न कि अनलिमिटेड. हालांकि, इसके लेआउट को तय करने के लिए, ज़्यादा जानकारी की ज़रूरत है.

crossAxisCount: बताता है कि कितने आइटम हैं. हमें दो कॉलम चाहिए.

padding: फ़ील्ड, GridView के चारों तरफ़ स्पेस देता है. बेशक, आपको पेज के पीछे या सबसे नीचे की ओर पैडिंग नहीं दिख सकती. इसकी वजह यह है कि उनके बगल में कोई GridView चिल्ड्रन मौजूद नहीं है.

childAspectRatio: फ़ील्ड, आसपेक्ट रेशियो (चौड़ाई/ऊंचाई) के आधार पर आइटम के साइज़ की पहचान करता है.

डिफ़ॉल्ट रूप से, GridView ऐसी टाइल बनाता है जो एक जैसे साइज़ की होती हैं.

हमारे पास एक कार्ड है, लेकिन वह खाली है. अपने कार्ड में चाइल्ड विजेट जोड़ें.

कॉन्टेंट का लेआउट बनाना

कार्ड में इमेज, टाइटल, और सेकंडरी टेक्स्ट के लिए क्षेत्र होने चाहिए.

GridView के बच्चों को अपडेट करें:

// TODO: Build a grid of cards (102)
children: <Widget>[
  Card(
    clipBehavior: Clip.antiAlias,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        AspectRatio(
          aspectRatio: 18.0 / 11.0,
          child: Image.asset('assets/diamond.png'),
        ),
        Padding(
          padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text('Title'),
              const SizedBox(height: 8.0),
              Text('Secondary Text'),
            ],
          ),
        ),
      ],
    ),
  )
],

यह कोड, चाइल्ड विजेट को वर्टिकल तौर पर लेआउट करने के लिए इस्तेमाल किया जाने वाला कॉलम विजेट जोड़ता है.

crossAxisAlignment: field से CrossAxisAlignment.start का पता चलता है, जिसका मतलब है कि "टेक्स्ट को सबसे आगे के किनारे पर अलाइन करें."

AspectRatio विजेट की मदद से, यह तय किया जाता है कि इमेज को कौनसा आकार मिलेगा. इससे कोई फ़र्क़ नहीं पड़ता कि किस तरह की इमेज दी गई है.

पैडिंग की मदद से, टेक्स्ट को साइड से थोड़ा अंदर लाया जा सकता है.

दो टेक्स्ट विजेट, वर्टिकल तौर पर स्टैक किए गए हैं. इनके बीच 8 पॉइंट का खाली स्पेस (SizedBox) है. हम उन्हें पैडिंग के अंदर रखने के लिए एक और कॉलम बनाते हैं.

अपना प्रोजेक्ट सेव करें.

Android

iOS

इमेज, शीर्षक, और दूसरे टेक्स्ट वाला एक आइटम

इमेज, टाइटल, और सेकंडरी टेक्स्ट वाला एक आइटम

इस झलक में, आपको कार्ड के किनारे से इनसेट दिखेगा. इसके कोने गोल हैं और एक परछाई (जो कार्ड की ऊंचाई दिखाती है) है. पूरी आकृति को Material में "कंटेनर" कहा जाता है. (कंटेनर नाम की वास्तविक विजेट क्लास से भ्रम पैदा नहीं होना चाहिए.)

आम तौर पर, कार्ड को अन्य कार्ड के साथ कलेक्शन में दिखाया जाता है. आइए, उन्हें ग्रिड में कलेक्शन के तौर पर दिखाएं.

6. कार्ड का कलेक्शन बनाना

जब किसी स्क्रीन में एक से ज़्यादा कार्ड मौजूद होते हैं, तो उन्हें एक या उससे ज़्यादा कलेक्शन में एक साथ रखा जाता है. किसी कलेक्शन में मौजूद कार्ड एक ही प्लैनर पर होते हैं. इसका मतलब है कि कार्ड एक ही लेवल पर होते हैं. ऐसा तब तक होता है, जब तक कार्ड को उठाया या खींचा-धकेला नहीं जाता. हालांकि, हम यहां ऐसा नहीं करेंगे.

कार्ड को कलेक्शन में जोड़ना

फ़िलहाल, हमारा कार्ड GridView के children: फ़ील्ड के इनलाइन में बनाया गया है. इसमें नेस्ट किया गया बहुत सारा कोड है, जिसे पढ़ना मुश्किल हो सकता है. चलिए, इसे ऐसे फ़ंक्शन के तौर पर एक्सट्रैक्ट करते हैं जो जितने चाहें उतने खाली कार्ड जनरेट करके, कार्ड की सूची दिखाता है.

build() फ़ंक्शन के ऊपर एक नया निजी फ़ंक्शन बनाएं (ध्यान रखें कि अंडरस्कोर से शुरू होने वाले फ़ंक्शन निजी एपीआई होते हैं):

// TODO: Make a collection of cards (102)
List<Card> _buildGridCards(int count) {
  List<Card> cards = List.generate(
    count,
    (int index) {
      return Card(
        clipBehavior: Clip.antiAlias,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            AspectRatio(
              aspectRatio: 18.0 / 11.0,
              child: Image.asset('assets/diamond.png'),
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: const <Widget>[
                  Text('Title'),
                  SizedBox(height: 8.0),
                  Text('Secondary Text'),
                ],
              ),
            ),
          ],
        ),
      );
    },
  );
  return cards;
}

जनरेट किए गए कार्ड, GridView के children फ़ील्ड को असाइन करें. GridView में शामिल सभी चीज़ों को इस नए कोड से बदलना याद रखें:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(10) // Replace
),

अपना प्रोजेक्ट सेव करें.

Android

iOS

इमेज, टाइटल, और सेकंडरी टेक्स्ट वाले आइटम का ग्रिड

आइटम का ग्रिड, जिसमें इमेज, टाइटल, और सेकंडरी टेक्स्ट शामिल हैं

कार्ड मौजूद हैं, लेकिन उनमें अभी कुछ नहीं दिख रहा है. प्रॉडक्ट डेटा जोड़ें.

प्रॉडक्ट डेटा जोड़ना

ऐप्लिकेशन में कुछ प्रॉडक्ट की इमेज, नाम, और कीमतें मौजूद हैं. आइए, इसे कार्ड में पहले से मौजूद विजेट में जोड़ें

इसके बाद, home.dart में एक नया पैकेज और डेटा मॉडल के लिए दी गई कुछ फ़ाइलें इंपोर्ट करें:

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

import 'model/product.dart';
import 'model/products_repository.dart';

आखिर में, प्रॉडक्ट की जानकारी फ़ेच करने के लिए _buildGridCards() बदलें और कार्ड में उस डेटा का इस्तेमाल करें:

// TODO: Make a collection of cards (102)

// Replace this entire method
List<Card> _buildGridCards(BuildContext context) {
  List<Product> products = ProductsRepository.loadProducts(Category.all);

  if (products.isEmpty) {
    return const <Card>[];
  }

  final ThemeData theme = Theme.of(context);
  final NumberFormat formatter = NumberFormat.simpleCurrency(
      locale: Localizations.localeOf(context).toString());

  return products.map((product) {
    return Card(
      clipBehavior: Clip.antiAlias,
      // TODO: Adjust card heights (103)
      child: Column(
        // TODO: Center items on the card (103)
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          AspectRatio(
            aspectRatio: 18 / 11,
            child: Image.asset(
              product.assetName,
              package: product.assetPackage,
             // TODO: Adjust the box size (102)
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
              child: Column(
               // TODO: Align labels to the bottom and center (103)
               crossAxisAlignment: CrossAxisAlignment.start,
                // TODO: Change innermost Column (103)
                children: <Widget>[
                 // TODO: Handle overflowing labels (103)
                 Text(
                    product.name,
                    style: theme.textTheme.titleLarge,
                    maxLines: 1,
                  ),
                  const SizedBox(height: 8.0),
                  Text(
                    formatter.format(product.price),
                    style: theme.textTheme.titleSmall,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }).toList();
}

ध्यान दें: फ़िलहाल, कंपाइल और रन नहीं किया जाएगा. हमारे पास एक और बदलाव है.

साथ ही, कंपाइल करने से पहले, build() फ़ंक्शन को बदलकर BuildContext को _buildGridCards() में पास करें:

// TODO: Add a grid view (102)
body: GridView.count(
  crossAxisCount: 2,
  padding: const EdgeInsets.all(16.0),
  childAspectRatio: 8.0 / 9.0,
  children: _buildGridCards(context) // Changed code
),

ऐप्लिकेशन को रीस्टार्ट करें.

Android

iOS

इमेज, प्रॉडक्ट के टाइटल, और कीमत के साथ आइटम का ग्रिड

प्रॉडक्ट की इमेज, टाइटल, और कीमत के साथ आइटम का ग्रिड

यह देखा जा सकता है कि हम आपके कार्ड के बीच में वर्टिकल स्पेस नहीं जोड़ते. ऐसा इसलिए, क्योंकि डिफ़ॉल्ट रूप से उनके ऊपर और नीचे 4 पॉइंट मार्जिन होते हैं.

अपना प्रोजेक्ट सेव करें.

प्रॉडक्ट डेटा दिखता है, लेकिन इमेज के आस-पास ज़्यादा जगह होती है. इस मामले में, इमेज को डिफ़ॉल्ट रूप से .scaleDown के BoxFit के साथ ड्रॉ किया जाता है. चलिए उसे .fitWidth में बदलते हैं, ताकि वे थोड़ा ज़ूम इन करें और खाली सफ़ेद जगह हटा दें.

इमेज में BoxFit.fitWidth वैल्यू के साथ fit: फ़ील्ड जोड़ें:

  // TODO: Adjust the box size (102)
  fit: BoxFit.fitWidth,

Android

iOS

आइटम का ग्रिड दिख रहा है. इसमें काटी गई इमेज, प्रॉडक्ट का टाइटल, और कीमत शामिल है

हमारे प्रॉडक्ट अब ऐप्लिकेशन में सही तरीके से दिख रहे हैं!

7. बधाई हो!

हमारे ऐप्लिकेशन में एक बुनियादी फ़्लो है, जो लोगों को लॉगिन स्क्रीन से होम स्क्रीन पर ले जाता है, जहां प्रॉडक्ट देखे जा सकते हैं. कोड की कुछ लाइनों में ही, हमने सबसे ऊपर मौजूद ऐप्लिकेशन बार (टाइटल और तीन बटन के साथ) और कार्ड (अपने ऐप्लिकेशन का कॉन्टेंट पेश करने के लिए) जोड़ा है. हमारी होम स्क्रीन बुनियादी संरचना और कार्रवाई करने लायक कॉन्टेंट के साथ अब आसान और फ़ंक्शनल हो गई है.

अगले चरण

अब हमने टॉप ऐप्लिकेशन बार, कार्ड, टेक्स्ट फ़ील्ड, और बटन के साथ, Material Flutter लाइब्रेरी के चार मुख्य कॉम्पोनेंट का इस्तेमाल किया है! Material कॉम्पोनेंट विजेट कैटलॉग पर जाकर, ज़्यादा जानकारी देखी जा सकती है.

हालांकि, यह पूरी तरह से काम कर रहा है, लेकिन हमारा ऐप्लिकेशन फ़िलहाल किसी खास ब्रैंड या नज़रिए के बारे में नहीं बताता. MDC-103: मटीरियल डिज़ाइन थीमिंग के साथ रंग, आकार, ऊंचाई, और टाइप में, हम चमकीले और मॉर्डन ब्रैंड दिखाने के लिए इन कॉम्पोनेंट की स्टाइल को पसंद के मुताबिक बनाएंगे.

मुझे इस कोडलैब को पूरा करने में ज़्यादा समय और मेहनत नहीं लगी

पूरी तरह सहमत सहमत न तो सहमत, न ही असहमत असहमति है पूरी तरह असहमति है

मुझे आने वाले समय में Material Components का इस्तेमाल जारी रखना है

पूरी तरह सहमत सहमत कोई बात नहीं असहमत पूरी तरह असहमत