نقل البيانات إلى برنامج SDK لأماكن جديدة

تنظيم صفحاتك في مجموعات يمكنك حفظ المحتوى وتصنيفه حسب إعداداتك المفضّلة.

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

إن الطريقة الوحيدة للدخول إلى الميزات وإصلاحات الأخطاء في حزمة تطوير البرامج (SDK) للأماكن لنظام التشغيل Android أعلى من الإصدار 2.6.0 تتمثل في استخدام حزمة SDK للأماكن في Android. توصي Google بالتحديث من مكتبة التوافق إلى الإصدار الجديد من الأماكن SDK لنظام التشغيل Android في أقرب وقت ممكن.

ما التغييرات التي أُجريت؟

في ما يلي النواحي الرئيسية للتغيير:

  • يتم توزيع الإصدار الجديد من الأماكن SDK لنظام التشغيل Android كمكتبة عميل ثابتة. قبل كانون الثاني (يناير) 2019، تم توفير الأماكن المخصصة لـ Android من خلال خدمات Google Play. ومنذ ذلك الحين، تم توفير مكتبة التوافق في الأماكن لتيسير النقل إلى الإصدار الجديد من الأماكن في Android.
  • هناك طرق جديدة تمامًا.
  • أصبحت أقنعة الحقول معتمدة الآن للطرق التي تعرض تفاصيل المكان. يمكنك استخدام أقنعة الحقول لتحديد أنواع بيانات الأماكن المطلوب عرضها.
  • تم تحسين رموز الحالة المُستخدَمة للإبلاغ عن الأخطاء.
  • يدعم الإكمال التلقائي الآن الرموز المميزة للجلسة.
  • لم يعد منتقي الأماكن متاحًا.

حول مكتبة توافق الأماكن

في كانون الثاني (يناير) 2019 مع طرح الإصدار 1.0 من حزمة SDK للأماكن المستقلة لنظام التشغيل Android، قدّمت Google مكتبة توافق للمساعدة في عملية الترحيل من إصدار خدمات Google Play الذي تم إيقاف العمل به من تطبيق SDK لأماكن Google لأجهزة Android (com.google.android.gms:play-services-places).

تم توفير مكتبة التوافق مؤقتًا لإعادة توجيه استدعاءات واجهة برمجة التطبيقات الموجّهة إلى إصدار خدمات Google Play إلى الإصدار المستقل الجديد وترجمتها إلى أن يتمكن مطوّرو البرامج من نقل الرموز الخاصة بهم لاستخدام الأسماء الجديدة في حزمة تطوير البرامج (SDK) المستقلة. بالنسبة إلى كل إصدار من SDK للأماكن لأجهزة Android والذي تم طرحه من الإصدار 1.0 إلى الإصدار 2.6.0، تم طرح إصدار مقابل من مكتبة التوافق في الأماكن لتوفير وظائف مكافئة.

تجميد وإيقاف مكتبة التوافق في الأماكن

سيتم إيقاف جميع إصدارات مكتبة التوافق لحزمة تطوير البرامج (SDK) لخدمة الأماكن من Android اعتبارًا من 31 آذار (مارس) 2022. الإصدار 2.6.0 هو الإصدار الأخير من مكتبة التوافق مع الأماكن. إن الطريقة الوحيدة للدخول إلى الميزات وإصلاحات الأخطاء في حزمة SDK للأماكن التي تعمل بنظام التشغيل Android أعلى من الإصدار 2.6.0 تتمثل في استخدام حزمة SDK للأماكن في Android.

توصي Google بالترحيل إلى الأماكن SDK لـ Android للوصول إلى الميزات الجديدة وإصلاحات الأخطاء الحرجة للإصدارات الأحدث من الإصدار 2.6.0. إذا كنت تستخدم مكتبة التوافق حاليًا، فاتبع الخطوات أدناه في قسم تثبيت حزمة SDK للأماكن في Android للترحيل إلى الأماكن SDK لـ Android.

تثبيت مكتبة البرامج

يتم توزيع الإصدار الجديد من تطبيق الأماكن لـ Android كمكتبة عميل ثابتة.

استخدم Maven لإضافة تطبيق SDK لأماكن Android إلى مشروع Android Studio:

  1. إذا كنت تستخدم حاليًا مكتبة توافق الأماكن:

    1. استبدل السطر التالي في قسم dependencies:

          implementation 'com.google.android.libraries.places:places-compat:X.Y.Z'

      باستخدام هذا السطر للتبديل إلى الأماكن SDK لنظام التشغيل Android:

          implementation 'com.google.android.libraries.places:places:3.0.0'

  2. إذا كنت تستخدم حاليًا إصدار "خدمات Play" من حِزمة تطوير البرامج (SDK) الخاصة بتطبيق "الأماكن" لنظام التشغيل Android:

    1. استبدل السطر التالي في قسم dependencies:

          implementation 'com.google.android.gms:play-services-places:X.Y.Z'

      باستخدام هذا السطر للتبديل إلى الأماكن SDK لنظام التشغيل Android:

          implementation 'com.google.android.libraries.places:places:3.0.0'

  3. مزامنة مشروع Gradle.

  4. اضبط minSdkVersion لمشروع التطبيق على 16 أو أعلى.

  5. تحديث مواد العرض "المدعومة من Google":

    @drawable/powered_by_google_light // OLD
    @drawable/places_powered_by_google_light // NEW
    @drawable/powered_by_google_dark // OLD
    @drawable/places_powered_by_google_dark // NEW
    
  6. أنشئ تطبيقك. وإذا كنت ترى أي أخطاء في الإصدار بسبب التحويل إلى حزمة SDK لأماكن Google، فراجع الأقسام أدناه للحصول على معلومات حول حل هذه الأخطاء.

تهيئة عميل SDK للأماكن الجديد

ابدأ في تهيئة عميل SDK الجديد للأماكن كما هو موضح في المثال التالي:

// Add an import statement for the client library.
import com.google.android.libraries.places.api.Places;

...

// Initialize Places.
Places.initialize(getApplicationContext(), apiKey);

// Create a new Places client instance.
PlacesClient placesClient = Places.createClient(this);

رموز الحالة

تم تغيير رمز الحالة لأخطاء حدود QPS. يتم الآن عرض أخطاء حد QPS عبر PlaceStatusCodes.OVER_QUERY_LIMIT. لم يعد هناك المزيد من حدود QPD.

تمت إضافة رموز الحالة التالية:

  • REQUEST_DENIED — تم رفض الطلب. وتشمل الأسباب المحتملة ما يلي:

    • لم يتم تقديم مفتاح واجهة برمجة التطبيقات.
    • تم تقديم مفتاح واجهة برمجة تطبيقات غير صالح.
    • لم يتم تفعيل واجهة برمجة تطبيقات الأماكن في Cloud Console.
    • تم تقديم مفتاح واجهة برمجة تطبيقات مع قيود مفتاح غير صحيحة.
  • INVALID_REQUEST — الطلب غير صالح بسبب وسيطة مفقودة أو غير صالحة.

  • NOT_FOUND — لم يتم العثور على أي نتائج للطلب المعني.

طرق جديدة

يقدم الإصدار الجديد من الأماكن SDK لنظام التشغيل Android طرقًا جديدة تمامًا تم تصميمها لتحقيق التناسق. وتلتزم جميع الطرق الجديدة بما يلي:

  • لم تعد نقاط النهاية تستخدم الفعل get.
  • تتشارك عناصر الطلب والاستجابة الاسم نفسه لطريقة العميل المقابلة.
  • تحتوي كائنات الطلب الآن على أدوات إنشاء؛ حيث يتم تمرير المعلمات المطلوبة كمعلمات لـ "أداة إنشاء الطلب".
  • لم تعد ذاكرة التخزين المؤقت مستخدمة.

يقدم هذا القسم الطرق الجديدة، ويوضح لك كيفية عملها.

جلب مكان بواسطة رقم التعريف

استخدِم fetchPlace() للحصول على تفاصيل عن مكان معيّن. fetchPlace() تعمل بشكل مماثل لوظيفة getPlaceById().

اتبع الخطوات التالية لجلب مكان:

  1. استدعاء fetchPlace()، مع تمرير كائن FetchPlaceRequest يحدد رقم تعريف المكان وقائمة الحقول التي تحدد بيانات المكان المراد عرضها.

    // Define a Place ID.
    String placeId = "INSERT_PLACE_ID_HERE";
    
    // Specify the fields to return.
    List<Place.Field> placeFields = Arrays.asList(Place.Field.ID, Place.Field.NAME);
    
    // Construct a request object, passing the place ID and fields array.
    FetchPlaceRequest request = FetchPlaceRequest.builder(placeId, placeFields)
            .build();
    
    
  2. اتصل بـ addOnSuccessListener() للتعامل مع FetchPlaceResponse. يتم عرض نتيجة Place واحدة.

    // Add a listener to handle the response.
    placesClient.fetchPlace(request).addOnSuccessListener((response) -> {
      Place place = response.getPlace();
      Log.i(TAG, "Place found: " + place.getName());
    }).addOnFailureListener((exception) -> {
        if (exception instanceof ApiException) {
            ApiException apiException = (ApiException) exception;
            int statusCode = apiException.getStatusCode();
            // Handle error with given status code.
            Log.e(TAG, "Place not found: " + exception.getMessage());
        }
    });
    

جلب صورة مكان

استخدم fetchPhoto() للحصول على صورة مكان. يعرض تطبيق fetchPhoto() صورًا لمكان. تم تبسيط نمط طلب الصورة. يمكنك الآن طلب PhotoMetadata مباشرةً من الكائن Place، ولم يعد الطلب المنفصل ضروريًا. يمكن أن يصل الحد الأقصى للعرض إلى ارتفاع 1600 بكسل للصور. fetchPhoto() مشابهة لوظيفة getPhoto().

اتبع الخطوات التالية لجلب صور الأماكن:

  1. يمكنك إعداد مكالمة مع fetchPlace(). احرص على تضمين الحقل PHOTO_METADATAS في طلبك:

    List<Place.Field> fields = Arrays.asList(Place.Field.PHOTO_METADATAS);
    
  2. الحصول على كائن مكان (هذا المثال يستخدم fetchPlace()، ولكن يمكنك أيضًا استخدام findCurrentPlace()):

    FetchPlaceRequest placeRequest = FetchPlaceRequest.builder(placeId, fields).build();
    
  3. أضف OnSuccessListener للحصول على البيانات الوصفية للصور من النتيجة Place في FetchPlaceResponse، ثم استخدم البيانات الوصفية للصور الناتجة للحصول على صورة نقطية ونص إحالة:

    placesClient.fetchPlace(placeRequest).addOnSuccessListener((response) -> {
        Place place = response.getPlace();
    
        // Get the photo metadata.
        PhotoMetadata photoMetadata = place.getPhotoMetadatas().get(0);
    
        // Get the attribution text.
        String attributions = photoMetadata.getAttributions();
    
        // Create a FetchPhotoRequest.
        FetchPhotoRequest photoRequest = FetchPhotoRequest.builder(photoMetadata)
                .setMaxWidth(500) // Optional.
                .setMaxHeight(300) // Optional.
                .build();
        placesClient.fetchPhoto(photoRequest).addOnSuccessListener((fetchPhotoResponse) -> {
            Bitmap bitmap = fetchPhotoResponse.getBitmap();
            imageView.setImageBitmap(bitmap);
        }).addOnFailureListener((exception) -> {
            if (exception instanceof ApiException) {
                ApiException apiException = (ApiException) exception;
                int statusCode = apiException.getStatusCode();
                // Handle error with given status code.
                Log.e(TAG, "Place not found: " + exception.getMessage());
            }
        });
    });
    

العثور على مكان من موقع المستخدم

استخدِم findCurrentPlace() للعثور على الموقع الجغرافي الحالي لجهاز المستخدم. تعرض findCurrentPlace() قائمة بـ PlaceLikelihood تشير إلى الأماكن التي يُرجح وجود جهاز المستخدم فيها. findCurrentPlace() تعمل بشكل مماثل لـ getCurrentPlace().

اتبع الخطوات التالية للحصول على الموقع الحالي لجهاز المستخدم:

  1. تأكَّد من أنّ تطبيقك يطلب إذنَي ACCESS_FINE_LOCATION وACCESS_WIFI_STATE. يجب على المستخدم منح إذن للوصول إلى موقع الجهاز الحالي. اطلع على طلب أذونات التطبيقات للحصول على التفاصيل.

  2. أنشئ FindCurrentPlaceRequest، بما في ذلك قائمة بأنواع بيانات الأماكن المطلوب عرضها.

      // Use fields to define the data types to return.
      List<Place.Field> placeFields = Arrays.asList(Place.Field.NAME);
    
      // Use the builder to create a FindCurrentPlaceRequest.
      FindCurrentPlaceRequest request =
              FindCurrentPlaceRequest.builder(placeFields).build();
    
  3. يمكنك الاتصال بـ findCurrentPlace ومعالجة الاستجابة، مع التحقق أولاً من أن المستخدم قد منح الإذن باستخدام الموقع الجغرافي لجهازه.

      // Call findCurrentPlace and handle the response (first check that the user has granted permission).
      if (ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
          placesClient.findCurrentPlace(request).addOnSuccessListener(((response) -> {
              for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {
                  Log.i(TAG, String.format("Place '%s' has likelihood: %f",
                          placeLikelihood.getPlace().getName(),
                          placeLikelihood.getLikelihood()));
                  textView.append(String.format("Place '%s' has likelihood: %f\n",
                          placeLikelihood.getPlace().getName(),
                          placeLikelihood.getLikelihood()));
              }
          })).addOnFailureListener((exception) -> {
              if (exception instanceof ApiException) {
                  ApiException apiException = (ApiException) exception;
                  Log.e(TAG, "Place not found: " + apiException.getStatusCode());
              }
          });
      } else {
          // A local method to request required permissions;
          // See https://developer.android.com/training/permissions/requesting
          getLocationPermission();
      }
    

العثور على توقعات الإكمال التلقائي

استخدِم findAutocompletePredictions() لعرض توقعات الأماكن استجابةً لطلبات بحث المستخدمين. findAutocompletePredictions() تعمل بشكل مماثل لـ getAutocompletePredictions().

يوضح المثال التالي الاتصال بالرقم findAutocompletePredictions():

// Create a new token for the autocomplete session. Pass this to FindAutocompletePredictionsRequest,
// and once again when the user makes a selection (for example when calling fetchPlace()).
AutocompleteSessionToken token = AutocompleteSessionToken.newInstance();
// Create a RectangularBounds object.
RectangularBounds bounds = RectangularBounds.newInstance(
  new LatLng(-33.880490, 151.184363),
  new LatLng(-33.858754, 151.229596));
// Use the builder to create a FindAutocompletePredictionsRequest.
FindAutocompletePredictionsRequest request = FindAutocompletePredictionsRequest.builder()
// Call either setLocationBias() OR setLocationRestriction().
   .setLocationBias(bounds)
   //.setLocationRestriction(bounds)
   .setCountry("au")
   .setTypesFilter(Arrays.asList(PlaceTypes.ADDRESS))
   .setSessionToken(token)
   .setQuery(query)
   .build();

placesClient.findAutocompletePredictions(request).addOnSuccessListener((response) -> {
   for (AutocompletePrediction prediction : response.getAutocompletePredictions()) {
       Log.i(TAG, prediction.getPlaceId());
       Log.i(TAG, prediction.getPrimaryText(null).toString());
   }
}).addOnFailureListener((exception) -> {
   if (exception instanceof ApiException) {
       ApiException apiException = (ApiException) exception;
       Log.e(TAG, "Place not found: " + apiException.getStatusCode());
   }
});

الرموز المميزة للجلسة

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

أقنعة الحقول

في الطرق التي تعرض تفاصيل المكان، يجب تحديد أنواع بيانات الأماكن المطلوب عرضها مع كل طلب. ويساعد ذلك في ضمان طلب البيانات (التي تدفع رسومًا) التي ستستخدمها فقط.

لتحديد أنواع البيانات المطلوب عرضها، مرِّر مصفوفة من Place.Field في FetchPlaceRequest، كما هو موضّح في المثال التالي:

// Include address, ID, and phone number.
List<Place.Field> placeFields = Arrays.asList(Place.Field.ADDRESS,
                                              Place.Field.ID,
                                              Place.Field.PHONE_NUMBER);

يمكنك استخدام واحد أو أكثر من الحقول التالية:

  • Place.Field.ADDRESS
  • Place.Field.ID
  • Place.Field.LAT_LNG
  • Place.Field.NAME
  • Place.Field.OPENING_HOURS
  • Place.Field.PHONE_NUMBER
  • Place.Field.PHOTO_METADATAS
  • Place.Field.PLUS_CODE
  • Place.Field.PRICE_LEVEL
  • Place.Field.RATING
  • Place.Field.TYPES
  • Place.Field.USER_RATINGS_TOTAL
  • Place.Field.VIEWPORT
  • Place.Field.WEBSITE_URI

اطلع على مزيد من المعلومات حول رموز تخزين تعريفية لبيانات الأماكن.

تحديثات منتقي الأماكن والإكمال التلقائي

يوضح هذا القسم التغييرات التي طرأت على أدوات الأماكن (منتقي الأماكن والإكمال التلقائي).

الإكمال الآلي الآلي

تم إجراء التغييرات التالية على ميزة الإكمال التلقائي:

  • تمت إعادة تسمية PlaceAutocomplete إلى Autocomplete.
    • تمت إعادة تسمية PlaceAutocomplete.getPlace إلى Autocomplete.getPlaceFromIntent.
    • تمت إعادة تسمية PlaceAutocomplete.getStatus إلى Autocomplete.getStatusFromIntent.
  • تمت إعادة تسمية PlaceAutocomplete.RESULT_ERROR إلى AutocompleteActivity.RESULT_ERROR (لم يتم تغيير معالجة الخطأ لجزء الإكمال التلقائي).

منتقي الأماكن

تم إيقاف منتقي الأماكن في 29 كانون الثاني (يناير) 2019. تم إيقافه في 29 تموز (يوليو) 2019 ولم يعد متوفرًا. وسيؤدي الاستخدام المستمر إلى ظهور رسالة خطأ. لا تتوافق حزمة تطوير البرامج (SDK) الجديدة مع منتقي الأماكن.

أدوات الإكمال التلقائي

تم تحديث أدوات الإكمال التلقائي:

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

توضح الأقسام التالية كيفية إضافة أداة إكمال تلقائي إلى مشروعك.

تضمين AutocompleteFragment

لإضافة جزء إكمال تلقائي، اتّبع الخطوات التالية:

  1. أضف جزءًا إلى تنسيق XML لأنشطتك، كما هو موضح في المثال التالي.

    <fragment
      android:id="@+id/autocomplete_fragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:name=
    "com.google.android.libraries.places.widget.AutocompleteSupportFragment"
      />
    
  2. لإضافة أداة الإكمال التلقائي إلى النشاط، اتبع الخطوات التالية:

    • عليك إعداد Places، مع اجتياز سياق التطبيق ومفتاح واجهة برمجة التطبيقات.
    • ابدأ تشغيل AutocompleteSupportFragment.
    • اتصل بـ setPlaceFields() لتوضيح أنواع بيانات الأماكن التي تريد الحصول عليها.
    • يمكنك إضافة PlaceSelectionListener لتنفيذ شيء ما مع النتيجة، بالإضافة إلى التعامل مع أي أخطاء قد تحدث.

    يوضح المثال التالي إضافة أداة إكمال تلقائي إلى نشاط:

    /**
     * Initialize Places. For simplicity, the API key is hard-coded. In a production
     * environment we recommend using a secure mechanism to manage API keys.
     */
    if (!Places.isInitialized()) {
        Places.initialize(getApplicationContext(), "YOUR_API_KEY");
    }
    
    // Initialize the AutocompleteSupportFragment.
    AutocompleteSupportFragment autocompleteFragment = (AutocompleteSupportFragment)
            getSupportFragmentManager().findFragmentById(R.id.autocomplete_fragment);
    
    autocompleteFragment.setPlaceFields(Arrays.asList(Place.Field.ID, Place.Field.NAME));
    
    autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
        @Override
        public void onPlaceSelected(Place place) {
            // TODO: Get info about the selected place.
            Log.i(TAG, "Place: " + place.getName() + ", " + place.getId());
        }
    
        @Override
        public void onError(Status status) {
            // TODO: Handle the error.
            Log.i(TAG, "An error occurred: " + status);
        }
    });
    

استخدام نية تشغيل نشاط الإكمال التلقائي

  1. إعداد Places وتمرير سياق التطبيق ومفتاح واجهة برمجة التطبيقات
  2. استخدِم Autocomplete.IntentBuilder لإنشاء هدف، مع اجتياز وضع PlaceAutocomplete المطلوب (ملء الشاشة أو التراكب). يجب أن ينشد الهدف startActivityForResult، مع إدخال رمز طلب يحدّد النية بالشراء.
  3. يمكنك إلغاء معاودة الاتصال بـ onActivityResult لتلقّي المكان المحدَّد.

يوضح المثال التالي كيفية استخدام نية تشغيل الإكمال التلقائي، ثم معالجة النتيجة:

    /**
     * Initialize Places. For simplicity, the API key is hard-coded. In a production
     * environment we recommend using a secure mechanism to manage API keys.
     */
    if (!Places.isInitialized()) {
        Places.initialize(getApplicationContext(), "YOUR_API_KEY");
    }

    ...

    // Set the fields to specify which types of place data to return.
    List<Place.Field> fields = Arrays.asList(Place.Field.ID, Place.Field.NAME);

    // Start the autocomplete intent.
    Intent intent = new Autocomplete.IntentBuilder(
            AutocompleteActivityMode.FULLSCREEN, fields)
            .build(this);
    startActivityForResult(intent, AUTOCOMPLETE_REQUEST_CODE);

    ...

    /**
     * Override the activity's onActivityResult(), check the request code, and
     * do something with the returned place data (in this example its place name and place ID).
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == AUTOCOMPLETE_REQUEST_CODE) {
            if (resultCode == RESULT_OK) {
                Place place = Autocomplete.getPlaceFromIntent(data);
                Log.i(TAG, "Place: " + place.getName() + ", " + place.getId());
            } else if (resultCode == AutocompleteActivity.RESULT_ERROR) {
                // TODO: Handle the error.
                Status status = Autocomplete.getStatusFromIntent(data);
                Log.i(TAG, status.getStatusMessage());
            } else if (resultCode == RESULT_CANCELED) {
                // The user canceled the operation.
            }
        }
    }

لم يعد منتقي الأماكن متوفرًا

تم إيقاف منتقي الأماكن في 29 كانون الثاني (يناير) 2019. تم إيقافه في 29 تموز (يوليو) 2019 ولم يعد متوفرًا. وسيؤدي الاستخدام المستمر إلى ظهور رسالة خطأ. لا تتوافق حزمة تطوير البرامج (SDK) الجديدة مع منتقي الأماكن.