লাইব্রেরি মোড়ক গাইড

এই গাইডটি বর্ণনা করে কিভাবে অ্যান্ড্রয়েড এপিআই লাইব্রেরি র‍্যাপার ব্যবহার করতে হয়। লাইব্রেরি র‍্যাপার কমান্ড-লাইন টুলটি জাভা অ্যান্ড্রয়েড এপিআই-এর জন্য সি-ভাষা র‍্যাপার কোড তৈরি করে, যা আপনাকে জাভা লাইব্রেরিগুলিকে নেটিভ C/C++ অ্যান্ড্রয়েড অ্যাপে সংহত করতে সক্ষম করে। লাইব্রেরি র‍্যাপার সম্পর্কে আরো বিস্তারিত জানার জন্য, Android API-এর জন্য লাইব্রেরি র‍্যাপার দেখুন।

এই ধাপে ধাপে নির্দেশিকা প্রদর্শন করে যে কীভাবে একটি জাভা লাইব্রেরি একটি নেটিভ অ্যান্ড্রয়েড অ্যাপে সংহত করতে র‍্যাপার টুল ব্যবহার করতে হয়। উদাহরণের উদ্দেশ্যে, এই নির্দেশিকাটি androidx.core.app প্যাকেজের বিজ্ঞপ্তি লাইব্রেরিকে একীভূত করে। এই লাইব্রেরি সম্পর্কে আরও জানতে একটি বিজ্ঞপ্তি তৈরি করুন দেখুন।

পূর্বশর্ত

এই গাইডটি অনুমান করে যে আপনার একটি বিদ্যমান নেটিভ অ্যান্ড্রয়েড প্রকল্প রয়েছে। এটি গ্রেডল বিল্ড সিস্টেমও ব্যবহার করে। আপনার যদি কোনো বিদ্যমান প্রজেক্ট না থাকে, তাহলে Native C++ টেমপ্লেট ব্যবহার করে Android স্টুডিওতে একটি নতুন তৈরি করুন।

এই গাইডের উদাহরণ কোডটি নির্দেশিকা রুট my_project/ ব্যবহার করে। নেটিভ কোড my_project/app/src/main/cpp/ এ অবস্থিত, এটি অ্যান্ড্রয়েড স্টুডিও প্রকল্পের ডিফল্ট ডিরেক্টরি।

যদি আপনার কাছে ইতিমধ্যেই লাইব্রেরি র্যাপার টুল না থাকে, তাহলে আপনার পছন্দের ডিরেক্টরিতে প্যাকেজটি ডাউনলোড করুন এবং আনজিপ করুন। এই CLI টুলটির জন্য Java Runtime Environment (JRE) প্রয়োজন।

নেটিভ কোড জেনারেট করুন

একটি জাভা লাইব্রেরি সংহত করার সময়, একটি নেটিভ কোড র্যাপার তৈরি করতে র্যাপার টুল ব্যবহার করুন। প্রথম ধাপ হল মোড়ক কনফিগার করা।

র্যাপার কনফিগারেশন তৈরি করুন

আপনি নেটিভ কোড জেনারেটরের আউটপুট নিয়ন্ত্রণ করতে লাইব্রেরি র্যাপার কনফিগারেশন ফাইল তৈরি করেন। এই ফাইলের একটি বৈশিষ্ট্য আপনাকে রেপার কোড তৈরি করার ক্লাস এবং পদ্ধতিগুলি নির্দিষ্ট করতে দেয়।

যেহেতু বিজ্ঞপ্তি লাইব্রেরির জন্য মোড়ানোর জন্য অনেক পদ্ধতি নেই, আপনি তাদের সরাসরি custom_classes বিভাগে সংজ্ঞায়িত করতে পারেন। পদ্ধতিগুলি সংজ্ঞায়িত করতে আপনার প্রকল্পের যে কোনও জায়গায় একটি নতুন config.json সংস্থান তৈরি করুন৷ উদাহরণস্বরূপ, আপনি my_project/library_wrapper/config.json তৈরি করতে পারেন এবং নিম্নলিখিত নমুনা কনফিগারেশন পেস্ট করতে পারেন:

{
  "custom_classes": [
    {
      "class_name": "class java.lang.CharSequence"
    },
    {
      "class_name": "class java.lang.Object",
      "methods": [
        "java.lang.String toString()"
      ]
    },
    {
      "class_name": "class java.lang.String"
    },
    {
      "class_name": "class android.content.Context",
      "methods": [
        "java.lang.Object getSystemService(java.lang.String name)"
      ]
    },
    {
      "class_name": "class android.app.Notification"
    },
    {
      "class_name": "class android.app.NotificationManager",
      "methods": [
        "void createNotificationChannel(android.app.NotificationChannel channel)"
      ]
    },
    {
      "class_name": "class android.app.NotificationChannel",
      "methods": [
        "NotificationChannel(java.lang.String id, java.lang.CharSequence name, int importance)",
        "void setDescription(java.lang.String description)"
      ]
    },
    {
      "class_name": "class androidx.core.app.NotificationCompat"
    },
    {
      "class_name": "class androidx.core.app.NotificationCompat$Builder",
      "methods": [
        "Builder(android.content.Context context, java.lang.String channelId)",
        "androidx.core.app.NotificationCompat$Builder setContentText(java.lang.CharSequence text)",
        "androidx.core.app.NotificationCompat$Builder setContentTitle(java.lang.CharSequence title)",
        "androidx.core.app.NotificationCompat$Builder setSmallIcon(int icon)",
        "androidx.core.app.NotificationCompat$Builder setPriority(int pri)",
        "android.app.Notification build()"
      ]
    },
    {
      "class_name": "class androidx.core.app.NotificationManagerCompat",
      "methods": [
        "static androidx.core.app.NotificationManagerCompat from(android.content.Context context)",
        "void notify(int id, android.app.Notification notification)"
      ]
    }
  ]
}

পূর্ববর্তী নমুনায়, আপনি সরাসরি জাভা ক্লাস এবং পদ্ধতিগুলি ঘোষণা করেন যেগুলির জন্য নেটিভ র্যাপার কোড প্রয়োজন৷

লাইব্রেরির মোড়ক চালান

আপনার র‍্যাপার কনফিগারেশন ফাইলটি সংজ্ঞায়িত করে, আপনি নেটিভ র‍্যাপার কোড তৈরি করতে টুলটি ব্যবহার করতে প্রস্তুত৷ একটি টার্মিনাল খুলুন যেখানে আপনি লাইব্রেরি র্যাপার বের করেছেন এবং নিম্নলিখিত কমান্ডটি চালান:

java -jar lw.jar \
  -o "my_project/app/src/main/cpp/native_wrappers" \
  -c "my_project/library_wrapper/config.json"

পূর্ববর্তী নমুনায় আপনি আপনার মোড়ক কনফিগার অবস্থান নির্দিষ্ট করতে -c প্যারামিটার এবং জেনারেট করা কোড ডিরেক্টরি সংজ্ঞায়িত করতে -o প্যারামিটার ব্যবহার করেন। টুলটি চালানোর পরে, এখন আপনার নেটিভ অ্যাপ থেকে জাভা-ভিত্তিক বিজ্ঞপ্তি API কল করার জন্য প্রয়োজনীয় জেনারেটেড কোড থাকা উচিত।

নেটিভ বিজ্ঞপ্তি বাস্তবায়ন

এই বিভাগে, আপনি আপনার জেনারেট করা র্যাপার কোড ব্যবহার করে আপনার নেটিভ অ্যাপে Android বিজ্ঞপ্তি লাইব্রেরি একত্রিত করেন। প্রথম ধাপ হল আপনার প্রজেক্টের অ্যাপ-লেভেল gradle.build রিসোর্স ( my_project/app/gradle.build ) আপডেট করা।

gradle.build আপডেট করুন

  1. GNI হল একটি সাপোর্ট লাইব্রেরি যা জেনারেট করা র্যাপার কোডের জন্য প্রয়োজন। উত্পন্ন কোড ব্যবহার করে সমস্ত প্রকল্প এই লাইব্রেরি উল্লেখ করা উচিত. এই লাইব্রেরিটি উল্লেখ করতে, build.gradle এর dependencies বিভাগে নিম্নলিখিত লাইনটি যোগ করুন:

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. প্রিফ্যাব সমর্থন সক্ষম করতে, android বিভাগে নিম্নলিখিত কোড যোগ করুন:

    buildFeatures {
      prefab true
    }
    
  3. cmake কনফিগার করতে, android/defaultConfig বিভাগে নিম্নলিখিত cmake কনফিগারেশন ব্যবহার করুন:

    externalNativeBuild {
      cmake {
          arguments '-DANDROID_STL=c++_shared'
      }
    }
    

আপনার সম্পূর্ণ করা build.gradle কনফিগারেশন নিম্নলিখিত অনুরূপ হওয়া উচিত:

android {
    ...

    buildFeatures {
        prefab true
    }

    defaultConfig {
        ...

        externalNativeBuild {
            cmake {
                arguments '-DANDROID_STL=c++_shared'
            }
        }
    }
}

dependencies {
    ...
    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    ...
}

CMakeLists সংশোধন করুন

  1. ফাইলের উপরের স্তরে নিম্নলিখিত লাইন যোগ করে আপনার প্রকল্পের CMakeLists.txt ( my_project/app/src/main/cpp/CMakeLists.txt ) এ GNI লাইব্রেরি যোগ করুন:

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. target_link_libraries বিভাগে নিম্নলিখিত লাইন যোগ করুন:

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. ফাইলের শীর্ষ স্তরে নিম্নলিখিত লাইন যোগ করে জেনারেট কোডে একটি রেফারেন্স যোগ করুন:

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. ফাইলের শেষের কাছে এই লাইনগুলি যোগ করুন:

    include_directories(./native_wrappers/c)
    include_directories(./native_wrappers/cpp)
    

আপনার আপডেট করা CMakeLists.txt সংস্থান নিম্নলিখিত নমুনার অনুরূপ হওয়া উচিত:

cmake_minimum_required(VERSION 3.18.1)

project("my_project")

file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")

add_library(
        my_project
        SHARED
        native-lib.cpp
        ${native_wrappers}
        )

find_library(
        log-lib
        log)

find_package(com.google.android.gms.gni.c REQUIRED CONFIG)

target_link_libraries(
        my_project
        PUBLIC com.google.android.gms.gni.c::gni_shared
        ${log-lib})

include_directories(./native_wrappers/c)
include_directories(./native_wrappers/cpp)

বিজ্ঞপ্তির যুক্তি প্রয়োগ করুন

  1. সোর্স ফাইলটি খুলুন বা তৈরি করুন যেখানে আপনি বিজ্ঞপ্তির ক্ষমতা প্রয়োগ করতে চান। এই ফাইলটিতে, হেডার ফাইল gni.h অন্তর্ভুক্ত করুন এবং একটি নতুন ShowNativeNotification() ফাংশন সংজ্ঞায়িত করুন:

    #include "gni/gni.h"
    
    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
      // Get the JavaVM from the JNIEnv.
      JavaVM *java_vm;
      env->GetJavaVM(&java_vm);
    
      // Initialize the GNI runtime. This function needs to be called before any
      // call to the generated code.
      GniCore_init(java_vm, main_activity);
    }
    
  2. বিজ্ঞপ্তি-নির্দিষ্ট ধ্রুবক মান সংজ্ঞায়িত করুন, এবং বিজ্ঞপ্তি হ্যান্ডলার ফাংশন CharSequenceFromCString() এবং CreateNotification() :

    const int32_t IMPORTANCE_HIGH = 4;  // NotificationManager.IMPORTANCE_HIGH
    const int32_t PRIORITY_MAX = 2;  // NotificationCompat.PRIORITY_MAX
    const int32_t NOTIFICATION_ID = 123;  // User defined notification id.
    
    // Convert a C string into CharSequence.
    CharSequence *CharSequenceFromCString(const char *text) {
       String *string = String_fromCString(text);
       // Cast String to CharSequence. In Java, a String implements CharSequence.
       CharSequence *result = GNI_CAST(CharSequence, String, string);
       // Casting creates a new object, so it needs to be destroyed as normal.
       String_destroy(string);
       return result;
    }
    
    // Create a notification.
    Notification *
    CreateNotification(Context *context, String *channel_id,
                       const char *title, const char *content,
                       int32_t icon_id) {
       // Convert C strings to CharSequence.
       CharSequence *title_chars = CharSequenceFromCString(title);
       CharSequence *content_chars = CharSequenceFromCString(content);
    
       // Create a NotificationCompat.Builder and set all required properties.
       NotificationCompat_Builder *notification_builder =
           NotificationCompat_Builder_construct(context, channel_id);
       NotificationCompat_Builder_setContentTitle(notification_builder,
                                                  title_chars);
       NotificationCompat_Builder_setContentText(notification_builder,
                                                 content_chars);
       NotificationCompat_Builder_setSmallIcon(notification_builder, icon_id);
       NotificationCompat_Builder_setPriority(notification_builder,
                                              PRIORITY_MAX);
    
       // Build a notification.
       Notification *notification =
           NotificationCompat_Builder_build(notification_builder);
    
       // Clean up allocated objects.
       NotificationCompat_Builder_destroy(notification_builder);
       CharSequence_destroy(title_chars);
       CharSequence_destroy(content_chars);
    
       return notification;
    }
    

    সি++

    const int32_t IMPORTANCE_HIGH = 4;  // NotificationManager.IMPORTANCE_HIGH
    const int32_t PRIORITY_MAX = 2;  // NotificationCompat.PRIORITY_MAX
    const int32_t NOTIFICATION_ID = 123;  // User defined notification id.
    
    // Convert a C string into CharSequence.
    CharSequence *CharSequenceFromCString(const char *text) {
       String *string = String_fromCString(text);
       // Cast String to CharSequence. In Java, a String implements CharSequence.
       CharSequence *result = new CharSequence(string->GetImpl());
       // Casting creates a new object, so it needs to be destroyed as normal.
       String::destroy(string);
       return result;
    }
    
    // Create a notification.
    Notification&
    CreateNotification(Context *context, String *channel_id, const char *title,
                       const char *content, int32_t icon_id) {
       // Convert C strings to CharSequence.
       CharSequence *title_chars = CharSequenceFromCString(title);
       CharSequence *content_chars = CharSequenceFromCString(content);
    
       // Create a NotificationCompat.Builder and set all required properties.
    
       NotificationCompat::Builder *notification_builder = new NotificationCompat::Builder(*context, *channel_id);
       notification_builder->setContentTitle(*title_chars);
       notification_builder->setContentText(*content_chars);
       notification_builder->setSmallIcon(icon_id);
       notification_builder->setPriority(PRIORITY_MAX);
    
       // Build a notification.
       Notification& notification = notification_builder->build();
    
       // Clean up allocated objects.
       NotificationCompat::Builder::destroy(notification_builder);
       CharSequence::destroy(title_chars);
       CharSequence::destroy(content_chars);
    
       return notification;
    }
    

    বিজ্ঞপ্তি লাইব্রেরির কিছু ফাংশন String এর পরিবর্তে CharSequence নেয়। CharSequenceFromCString() ফাংশন এই বস্তুর মধ্যে রূপান্তর সক্ষম করে। CreateNotification() ফাংশনটি একটি বিজ্ঞপ্তি তৈরি করতে Java NotificationCompat.Builder এর মোড়ানো সংস্করণ ব্যবহার করে।

  3. নিম্নলিখিত ফাংশনে পেস্ট করে একটি বিজ্ঞপ্তি চ্যানেল তৈরি করতে যুক্তি যোগ করুন, CreateNotificationChannel() :

    void CreateNotificationChannel(Context *context, String *channel_id) {
       CharSequence *channel_name = CharSequenceFromCString("channel name");
       String *channel_description = String_fromCString("channel description");
       String *system_service_name = String_fromCString("notification");
       NotificationChannel *channel =
           NotificationChannel_construct(channel_id, channel_name,
                                         IMPORTANCE_HIGH);
       NotificationChannel_setDescription(channel, channel_description);
    
       Object *notification_manager_as_object =
           Context_getSystemService(context, system_service_name);
       NotificationManager *notification_manager =
           GNI_CAST(NotificationManager, Object,
                    notification_manager_as_object);
    
       NotificationManager_createNotificationChannel(notification_manager,
                                                     channel);
    
       CharSequence_destroy(channel_name);
       String_destroy(channel_description);
       String_destroy(system_service_name);
       NotificationChannel_destroy(channel);
       Object_destroy(notification_manager_as_object);
       NotificationManager_destroy(notification_manager);
    }
    

    সি++

    void CreateNotificationChannel(Context *context, String *channel_id) {
       CharSequence *channel_name = CharSequenceFromCString("channel name");
       String *channel_description = String_fromCString("channel description");
       String *system_service_name = String_fromCString("notification");
       NotificationChannel *channel =
           new NotificationChannel(*channel_id, *channel_name, IMPORTANCE_HIGH);
       channel->setDescription(*channel_description);
    
       Object& notification_manager_as_object =
           context->getSystemService(*system_service_name);
       NotificationManager *notification_manager =
           new NotificationManager(notification_manager_as_object.GetImpl());
    
       notification_manager->createNotificationChannel(*channel);
    
       CharSequence::destroy(channel_name);
       String::destroy(channel_description);
       String::destroy(system_service_name);
       NotificationChannel::destroy(channel);
       Object::destroy(&notification_manager_as_object);
       NotificationManager::destroy(notification_manager);
    }
    
  4. CreateNotificationChannel() কল করতে আপনার আগে তৈরি করা ShowNativeNotification() ফাংশন আপডেট করুন। ShowNativeNotification() এর শেষে নিম্নলিখিত কোড যোগ করুন:

    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
     // ...
    
     // Create a Context object by wrapping an existing JNI reference.
     Context *context = Context_wrapJniReference(main_activity);
    
     // Create a String object.
     String *channel_id = String_fromCString("new_messages");
    
     // Create a notification channel.
     CreateNotificationChannel(context, channel_id);
    
     // Create a notification with a given title, content, and icon.
     Notification *notification =
         CreateNotification(context, channel_id, "My Native Notification",
                            "Hello!", icon_id);
    
     // Create a notification manager and use it to show the notification.
     NotificationManagerCompat *notification_manager =
         NotificationManagerCompat_from(context);
     NotificationManagerCompat_notify(notification_manager, NOTIFICATION_ID,
                                      notification);
    
     // Destroy all objects.
     Context_destroy(context);
     String_destroy(channel_id);
     Notification_destroy(notification);
     NotificationManagerCompat_destroy(notification_manager);
    }
    

    সি++

    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
       // Get the JavaVM from the JNIEnv.
       JavaVM *java_vm;
       env->GetJavaVM(&java_vm);
    
       // Initialize the GNI runtime. This function needs to be called before any
       // call to the generated code.
       GniCore::Init(java_vm, main_activity);
    
       // Create a Context object by wrapping an existing JNI reference.
       Context *context = new Context(main_activity);
    
       // Create a String object.
       String *channel_id = String_fromCString("new_messages");
    
       // Create a notification channel.
       CreateNotificationChannel(context, channel_id);
    
       // Create a notification with a given title, content, and icon.
       Notification& notification =
           CreateNotification(context, channel_id, "My Native Notification",
                              "Hello!", icon_id);
    
       // Create a notification manager and use it to show the notification.
       NotificationManagerCompat& notification_manager =
           NotificationManagerCompat::from(*context);
       notification_manager.notify(NOTIFICATION_ID, notification);
    
       // Destroy all objects.
       Context::destroy(context);
       String::destroy(channel_id);
       Notification::destroy(&notification);
       NotificationManagerCompat::destroy(&notification_manager);
    }   
  5. আপনার যুক্তি সংজ্ঞায়িত করে, আপনার প্রকল্পের একটি উপযুক্ত স্থানে ShowNativeNotification() কল করে একটি বিজ্ঞপ্তি ট্রিগার করুন।

অ্যাপটি চালান

কম্পাইল করুন এবং কোডটি চালান যা ShowNativeNotification() কল করে। একটি সাধারণ বিজ্ঞপ্তি আপনার পরীক্ষার ডিভাইসের স্ক্রিনের শীর্ষে উপস্থিত হওয়া উচিত।

JARs থেকে wrappers তৈরি করুন

পূর্ববর্তী উদাহরণে, আপনি জাভা ক্লাস এবং পদ্ধতিগুলিকে ম্যানুয়ালি সংজ্ঞায়িত করেছেন যাতে একটি মোড়ক কনফিগার ফাইলে নেটিভ কোডের প্রয়োজন হয়। পরিস্থিতিগুলির জন্য যেখানে আপনাকে একটি API এর বড় অংশগুলি অ্যাক্সেস করতে হবে, এটি র্যাপার টুলে এক বা একাধিক লাইব্রেরি JAR প্রদান করার জন্য আরও দক্ষ। মোড়ক তারপর JAR-এ পাওয়া সমস্ত পাবলিক চিহ্নের জন্য মোড়ক তৈরি করে।

নিম্নলিখিত উদাহরণটি একটি লাইব্রেরি JAR প্রদান করে সম্পূর্ণ বিজ্ঞপ্তি APIকে মোড়ানো হয়।

প্রয়োজনীয় JAR পান

বিজ্ঞপ্তি API হল androidx.core প্যাকেজের একটি অংশ, যা Google Maven সংগ্রহস্থল থেকে পাওয়া যায়। লাইব্রেরি aar ফাইলটি ডাউনলোড করুন এবং আপনার পছন্দের একটি ডিরেক্টরিতে এটি আনপ্যাক করুন। classes.jar ফাইলটি সন্ধান করুন।

classes.jar ফাইলে আমাদের প্রয়োজনীয় নোটিফিকেশন লাইব্রেরির বাইরে অনেক ক্লাস রয়েছে। আপনি যদি শুধু classes.jar দিয়ে লাইব্রেরির মোড়ক প্রদান করেন, তাহলে টুলটি JAR-এর প্রতিটি ক্লাসের জন্য নেটিভ কোড তৈরি করে, যা আমাদের প্রকল্পের জন্য অদক্ষ এবং অপ্রয়োজনীয়। এটি সমাধান করার জন্য, JAR-এর বিজ্ঞপ্তি ক্লাসে কোড জেনারেশন সীমাবদ্ধ করতে র্যাপার কনফিগারেশনে একটি ফিল্টার ফাইল প্রদান করুন।

একটি অনুমোদিত ফিল্টার সংজ্ঞায়িত করুন

ফিল্টার ফাইলগুলি হল প্লেইন টেক্সট ফাইল যা আপনি আপনার লাইব্রেরি র্যাপার কনফিগারেশনে প্রদান করেন। তারা আপনাকে লাইব্রেরির মোড়কে দেওয়া JAR ফাইলগুলি থেকে কোন ক্লাসগুলি অন্তর্ভুক্ত করতে হবে (বা বাদ দিতে হবে) তা নির্ধারণ করার অনুমতি দেয়।

আপনার প্রকল্পে, allowed-symbols.txt শিরোনামের একটি ফাইল তৈরি করুন এবং নিম্নলিখিত লাইনে পেস্ট করুন:

androidx.core.app.NotificationCompat*

অনুমতি ফিল্টার হিসাবে ব্যবহার করা হলে, পূর্ববর্তী কোডটি নির্দিষ্ট করে যে শুধুমাত্র যে চিহ্নগুলির নাম androidx.core.app.NotificationCompat দিয়ে শুরু হয় সেগুলিকে মোড়ানো হয়৷

লাইব্রেরির মোড়ক চালান

JAR ডিরেক্টরিতে একটি টার্মিনাল খুলুন এবং নিম্নলিখিত কমান্ডটি চালান:

java -jar lw.jar \
 -i classes.jar \
 -o "./generated-jar" \
 -c "./config.json" \
 -fa allowed-symbols.txt \
 --skip_deprecated_symbols

পূর্ববর্তী নমুনা কমান্ড আপনার ফিল্টার করা ক্লাসের জন্য generated-jar/ ডিরেক্টরিতে মোড়ক কোড তৈরি করে।

সমর্থন

আপনি যদি লাইব্রেরির মোড়কের সাথে কোনও সমস্যা খুঁজে পান তবে দয়া করে আমাদের জানান।

বাগ ব্রাউজ করুন একটি বাগ ফাইল করুন
প্রকৌশল
ডকুমেন্টেশন