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

1. परिचय

logo_components_color_2x_web_96dp.png

मटीरियल कॉम्पोनेंट (एमडीसी) की मदद से, डेवलपर मटीरियल डिज़ाइन लागू कर सकते हैं. MDC को Google के इंजीनियरों और यूज़र एक्सपीरियंस (यूएक्स) डिज़ाइनर की टीम ने बनाया है. इसमें कई शानदार और काम के यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट शामिल हैं. यह 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. अपने चुने गए एडिटर के लिए, शुरू करें: बिना शुल्क आज़माएं में दिए गए "ऐप्लिकेशन चलाएं" निर्देशों का पालन करें.

हो गया! आपको अपने डिवाइस पर, MDC-101 कोडलैब का Shrine लॉगिन पेज दिखेगा.

Android

iOS

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

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

अब जब लॉगिन स्क्रीन अच्छी दिख रही है, तो आइए ऐप्लिकेशन में कुछ प्रॉडक्ट जोड़ें.

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

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

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

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

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

home.dart में, Scaffold में AppBar जोड़ें और हाइलाइट किए गए const को हटाएं:

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

Scaffold के 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 को टाइटल के तौर पर इस्तेमाल करने वाला ऐप्लिकेशन बार

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

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

Add a leading IconButton

home.dart में रहते हुए, AppBar के leading: फ़ील्ड के लिए IconButton सेट करें. (इसे title: फ़ील्ड से पहले रखें, ताकि यह लीडिंग-टू-ट्रेलिंग ऑर्डर की तरह दिखे):

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

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

Android

iOS

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

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

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

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

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

इसमें दो और IconButton जोड़े जा सकते हैं.

टाइटल के बाद, उन्हें 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 विजेट में शामिल करेंगे.

Scaffold के मुख्य हिस्से में मौजूद 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 विजेट यह तय करता है कि इमेज का आकार कैसा होगा. इससे कोई फ़र्क़ नहीं पड़ता कि किस तरह की इमेज दी गई है.

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

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

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

Android

iOS

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

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

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

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

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

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

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

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

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

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

इमेज में fit: फ़ील्ड जोड़ें और उसकी वैल्यू BoxFit.fitWidth पर सेट करें:

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

Android

iOS

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

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

7. बधाई हो!

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

अगले चरण

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

यह पूरी तरह से काम कर रहा है. हालांकि, हमारा ऐप्लिकेशन अब तक किसी खास ब्रैंड या नज़रिए को नहीं दिखाता है. हम MDC-103: Material Design Theming with Color, Shape, Elevation and Type में, इन कॉम्पोनेंट की स्टाइल को पसंद के मुताबिक बनाएंगे, ताकि एक शानदार और मॉडर्न ब्रैंड को दिखाया जा सके.

मैंने इस कोडलैब को कम समय और कम मेहनत में पूरा कर लिया

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

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

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