Poly API: Récupération des ressources 3D pour vos applications Android VR et AR

Auteur: Peter Berry
Date De Création: 14 Août 2021
Date De Mise À Jour: 8 Peut 2024
Anonim
Poly API: Récupération des ressources 3D pour vos applications Android VR et AR - Applications
Poly API: Récupération des ressources 3D pour vos applications Android VR et AR - Applications

Contenu


Vous avez une idée géniale pour une application mobile de réalité virtuelle (VR) ou de réalité augmentée (AR), mais vous ne savez pas comment concrétiser votre vision?

Sauf si vous êtes un développeur Android et qu’il s’agit également d’un artiste 3D expérimenté, la création de tous les actifs nécessaires pour offrir une expérience immersive à 360 degrés peut s’avérer très ardue.

Parce que vous n’avez pas le temps, les ressources ou l’expérience nécessaires pour créer des modèles 3D, n'est pas Cela signifie que vous ne pouvez pas créer une excellente application mobile VR ou AR! Il existe une vaste gamme de ressources 3D disponibles gratuitement sur le Web, ainsi que toutes les API, les infrastructures et les bibliothèques dont vous avez besoin pour télécharger et restituer ces ressources dans vos applications Android.


Lire la suite: Vous pouvez maintenant visiter n’importe quel site Web avec Daydream VR. Même celui-là.

Dans cet article, nous allons parler de Poly, un référentiel en ligne et une API qui mettent des milliers de ressources 3D à portée de main. À la fin de cet article, vous aurez créé une application qui récupérera un actif 3D Poly au moment de l’exécution, puis le restituera à l’aide de la célèbre bibliothèque Processing for Android.

Affichage d'éléments 3D avec Poly

Si vous avez déjà utilisé le développement Unity, le référentiel Poly est similaire à Unity Asset Store, à la différence près que tout dans Poly est gratuit!

La plupart des modèles 3D de Poly sont publiés sous la licence Creative Commons. Vous êtes donc libre d’utiliser, de modifier et de remixer ces éléments, à condition de donner au créateur les crédits appropriés.


Tous les modèles 3D de Poly sont conçus pour être compatibles avec les plates-formes VR et AR de Google, telles que Daydream et ARCore, mais vous pouvez les utiliser n’importe où et à votre guise. Vous pouvez même potentiellement les utiliser avec le kit ARKit d’Apple!

Pour récupérer et afficher des ressources Poly, vous avez deux options. Tout d'abord, vous pouvez télécharger les ressources sur votre ordinateur, puis les importer dans Android Studio afin qu'elles soient livrées avec votre application et contribuer à sa taille en APK, ou vous pouvez récupérer ces ressources au moment de l'exécution à l'aide de l'API Poly.

L’API Poly multiplate-forme, basée sur REST, fournit un accès en lecture seule par programmation à l’immense collection de modèles 3D de Poly. C’est plus compliqué que de lier des actifs avec votre APK, mais il est avantageux de récupérer des actifs Poly lors de l’exécution, notamment le fait que vous gardez la taille de votre APK sous contrôle, ce qui peut affecter le nombre de personnes téléchargeant votre application.

Vous pouvez également utiliser l'API Poly pour donner plus de choix à vos utilisateurs. Par exemple, si vous développez un jeu pour mobile, vous pouvez laisser vos utilisateurs choisir parmi une gamme de modèles de personnages.

Étant donné que vous êtes libre de modifier les modèles Poly, vous pouvez même permettre à vos utilisateurs d’ajuster le caractère de leur choix, par exemple en modifiant la couleur de leurs cheveux ou de leurs yeux, ou en le combinant avec d’autres ressources Poly, telles que des armes et des armures différentes. De cette manière, l’API Poly peut vous aider à fournir une gamme impressionnante d’actifs 3D, avec de nombreuses possibilités pour personnaliser l’expérience - et tout cela pour un travail relativement réduit. Vos utilisateurs seront convaincus que vous avez passé une tonne de temps à concevoir méticuleusement tous ces modèles 3D!

Création d'un projet de modélisation 3D

Nous allons créer une application qui récupère un actif Poly particulier lors du premier lancement de l’application, puis affiche cet actif en mode plein écran, à la demande de l’utilisateur.

Pour nous aider à récupérer cet actif, j'utiliserai Fuel, une bibliothèque de réseau HTTP pour Kotlin et Android. Commencez par créer un nouveau projet avec les paramètres de votre choix, puis, lorsque vous y êtes invité, choisissez «Inclure le support Kotlin».

Tous les appels que vous effectuez vers l'API Poly doivent inclure une clé API, qui sert à identifier votre application et à appliquer des limites d'utilisation. Lors du développement et des tests, vous utiliserez souvent une clé d'API non restreinte, mais si vous envisagez de publier cette application, vous devez utiliser une clé d'API restreinte Android.

Pour créer une clé restreinte, vous devez connaître le certificat de signature SHA-1 de votre projet. Par conséquent, obtenons maintenant ces informations:

  • Sélectionnez l’onglet «Gradle» d’Android Studio (où le curseur est positionné dans la capture d’écran suivante). Cela ouvre un panneau «Projets Gradle».

  • Dans le panneau "Projets Gradle", double-cliquez pour développer la "racine" de votre projet, puis sélectionnez "Tâches> Android> Rapport de signature". Ceci ouvre un nouveau panneau au bas de la fenêtre d'Android Studio.
  • Sélectionnez le bouton «Basculer entre les exécutions de tâches / mode texte» (où le curseur est positionné dans la capture d'écran suivante).

Le panneau «Exécuter» va maintenant se mettre à jour pour afficher de nombreuses informations sur votre projet, y compris son empreinte digitale SHA-1.

Créer un compte Google Cloud Platform

Pour acquérir la clé API nécessaire, vous devez disposer d'un compte Google Cloud Platform (GPC).

Si vous n'avez pas de compte, vous pouvez vous inscrire pour un essai gratuit de 12 mois en vous rendant sur la page Try Cloud Platform for free et en suivant les instructions. Notez qu'une carte de crédit ou une carte de débit est requise, mais d'après la page Foire aux questions, elle sert uniquement à vérifier votre identité et «vous ne serez ni débité ni facturé lors de votre essai gratuit».

Obtenez votre clé API Poly

Une fois que vous êtes tous inscrits, vous pouvez activer l’API Poly et créer votre clé:

  • Rendez-vous sur la console GCP.
  • Sélectionnez l'icône en ligne dans le coin supérieur gauche, puis choisissez «API et services> Tableau de bord».
  • Sélectionnez «Activer les API et les services».
  • Dans le menu de gauche, choisissez «Autre».
  • Sélectionnez la carte “Poly API”.
  • Cliquez sur le bouton "Activer".
  • Après quelques instants, vous serez redirigé vers un nouvel écran. Ouvrez le menu latéral et choisissez «API et services> Informations d'identification».

  • Dans la fenêtre contextuelle suivante, sélectionnez «Restreindre la clé».
  • Donnez à votre clé un nom distinctif.
  • Sous «Restrictions d'application», sélectionnez «Applications Android».
  • Sélectionnez "Ajouter le nom du package et l'empreinte digitale."
  • Copiez / collez l’empreinte SHA-1 de votre projet dans le champ «Empreinte de certificat de signature».
  • Entrez le nom du package de votre projet (il apparaît dans votre manifeste et en haut de chaque fichier de classe).
  • Cliquez sur "Enregistrer".

Vous allez maintenant accéder à l'écran «Credentials» de votre projet, qui contient la liste de toutes vos clés d'API, y compris la clé d'API polyvalente que vous venez de créer.

Dépendances du projet: extensions de carburant, P3D et Kotlin

Afin de récupérer et d’afficher des ressources Poly, nous avons besoin de l’aide de bibliothèques supplémentaires:

  • Carburant. À l’heure actuelle, Poly n’a pas de boîte à outils Android officielle, vous devez donc utiliser l’API directement à l’aide de son interface REST. Pour simplifier ce processus, j’utiliserai la bibliothèque de réseaux Fuel HTTP.
  • Traitement pour Android. J'utiliserai le moteur de rendu P3D de cette bibliothèque pour afficher l'actif Poly.

Ouvrez le fichier build.gradle de votre projet et ajoutez ces deux bibliothèques en tant que dépendances du projet:

dépendances {implementation fileTree (include:, dir: libs) implementation "org.jetbrains.kotlin: kotlin-stdlib-jre7: $ kotlin_version" implémentation com.android.support:appcompat-v7:27.1.1 // Ajoute la bibliothèque de carburant / / implementation com.github.kittinunf.fuel: fuel-android: 1.13.0 // Ajoutez le moteur Processing for Android // implémentation org.p5android: processing-core: 4.0.1}

Pour que notre code soit plus concis, j'utiliserai également les extensions Android Kotlin. Nous allons donc ajouter ce plugin pendant que le fichier build.gradle est ouvert:

appliquer le plugin: kotlin-android-extensions

Enfin, étant donné que nous récupérons l'actif sur Internet, notre application a besoin de l'autorisation Internet. Ouvrez votre manifeste et ajoutez ce qui suit:

Ajouter votre clé API

Chaque fois que notre application demande un actif à Poly, elle doit inclure une clé API valide. J'utilise un texte de substitution, mais vous doit remplacez cet espace réservé par votre propre clé API si l'application doit fonctionner.

J'ajoute également une coche pour que l'application affiche un avertissement si vous oubliez de remplacer le texte «INSERT-YOUR-API-KEY»:

import android.os.Bundle import android.support.v7.app.AppCompatActivity, classe MainActivity: AppCompatActivity () {objet compagnon {const val APIKey = "INSERT-YOUR-API-KEY"} remplacez fun onCreate (saveInstanceState: Bundle?) { super.onCreate (savedInstanceState) setContentView (R.layout.activity_main) // Si la clé de l'API commence par «INSERT» ... // if (APIKey.startsWith ("INSERT")) {// affiche le toast suivant… .// Toast.makeText (this, "Vous n'avez pas mis à jour votre clé d'API", Toast.LENGTH_SHORT) .show ()} else {... ... ...

Récupérer l'actif

Vous pouvez choisir n’importe quel actif sur le site Google Poly, mais j’utiliserai ce modèle de la planète Terre.

Vous récupérez un actif à l'aide de son ID, qui apparaît à la fin du slug de l'URL (mis en évidence dans la capture d'écran précédente). Nous combinons cet ID d'actif avec l'hôte Poly API, qui est «https://poly.googleapis.com/v1».

import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.github.kittinunf.fuel.android.extension.responseJson import com.github.kittinunf.fuel .httpDownload import com.github.kittinunf.fuel.httpGet import kotlinx.android.synthetic.main.activity_main. * import java.io.File classe MainActivity: AppCompatActivity () {objet compagnon {const val APIKey = "INSERT-YOUR-API -KEY "val assetURL =" https://poly.googleapis.com/v1/assets/94XG1XUy10q "} substitue l'amusement onCreate (savedInstanceState: Bundle?) {Super.onCréer (savedInstanceState) setContentView (R.layout.activity_main) if ( APIKey.startsWith ("INSERT")) {Toast.makeText (this, "Vous n'avez pas mis à jour votre clé API", Toast.LENGTH_SHORT) .show ()} else {

Ensuite, nous devons envoyer une requête GET à l'URL de l'actif, à l'aide de la méthode httpGet (). Je précise également que le type de réponse doit être JSON:

import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.github.kittinunf.fuel.android.extension.responseJson import com.github.kittinunf.fuel .httpDownload import com.github.kittinunf.fuel.httpGet import kotlinx.android.synthetic.main.activity_main. * import java.io.File classe MainActivity: AppCompatActivity () {objet compagnon {const val APIKey = "INSERT-YOUR-API -KEY "val assetURL =" https://poly.googleapis.com/v1/assets/94XG1XUy10q "} substitue l'amusement onCreate (savedInstanceState: Bundle?) {Super.onCréer (savedInstanceState) setContentView (R.layout.activity_main) if ( APIKey.startsWith ("INSERT")) {Toast.makeText (this, "Vous n'avez pas mis à jour votre clé API", Toast.LENGTH_SHORT) .show ()} else {// Appelez le serveur, puis transmettez les données à l'aide du Méthode “listOf” // assetURL.httpGet (listOf ("clé" de APIKey)). ResponseJson {demande, réponse, résultat -> // Fait quelque chose avec la réponse // result.fold ({val as set = it.obj ()

L'actif peut avoir plusieurs formats, tels que OBJ, GLTF et FBX. Nous devons déterminer que l'actif est au format OBJ.

Dans cette étape, je récupère également le nom et l’URL de tous les fichiers que nous devons télécharger,
y compris le fichier principal de l’actif («racine»), ainsi que tous les fichiers de matériau et de texture associés («ressources»).

Si notre application ne parvient pas à récupérer l’actif correctement, elle affiche un toast pour en informer l’utilisateur.

import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.github.kittinunf.fuel.android.extension.responseJson import com.github.kittinunf.fuel .httpDownload import com.github.kittinunf.fuel.httpGet import kotlinx.android.synthetic.main.activity_main. * import java.io.File classe MainActivity: AppCompatActivity () {objet compagnon {const val APIKey = "INSERT-YOUR-API -KEY "val assetURL =" https://poly.googleapis.com/v1/assets/94XG1XUy10q "} substitue l'amusement onCreate (savedInstanceState: Bundle?) {Super.onCréer (savedInstanceState) setContentView (R.layout.activity_main) if ( APIKey.startsWith ("INSERT")) {Toast.makeText (this, "Vous n'avez pas mis à jour votre clé d'API", Toast.LENGTH_SHORT) .show ()} else {// Envoyez une demande GET à l'URL de l'actif // assetURL. httpGet (listOf ("key" to APIKey)). responseJson {demande, réponse, résultat -> // Fait quelque chose avec la réponse // resultat.fold ({val asset = it.obj () var objectURL: String? = null var materialLibraryName: String? = null var materialLibraryURL: String? = null // Vérifiez le format de l'actif à l'aide du tableau «formats» // val assetFormats = asset.getJSONArray ("formats") // Parcourez tous les formats // pour (i dans 0 jusqu'à assetFormats.length ()) { val currentFormat = assetFormats.getJSONObject (i) // Utilisez formatType pour identifier le type de format de cette ressource. Si le format est OBJ… .// if (currentFormat.getString ("formatType") == "OBJ") {//... puis récupérez le fichier 'racine' de cette ressource, c'est-à-dire le fichier OBJ // objectURL = currentFormat. getJSONObject ("root") .getString ("url") // Récupère toutes les dépendances du fichier racine // materialLibraryName = currentFormat.getJSONArray ("resources") .getJSONObject (0) .getString ("relativePath") materialLibraryURL = currentFormat.getJString ("resources") .getJSONObject (0) .getString ("url") break}} objectURL !!. httpDownload (). destination {_, _ -> Fichier (filesDir, "globeAsset.obj")} .response {_ , _, result -> result.fold ({}, {// Si vous ne pouvez pas localiser ou télécharger le fichier OBJ, affichez alors une erreur // Toast.makeText (this, "Impossible de télécharger la ressource", Toast.LENGTH_SHORT ) .show ()})} materialLibraryURL !!. httpDownload (). destination {_, _ -> Fichier (filesDir, materialLibraryName)} .response {_, _, result -> result.fold ({}, {Toast. makeText (this, "Impossible de télécharger la ressource", Toast.LENGTH_SHORT) .show ()})}}, { Toast.makeText (this, "Impossible de télécharger la ressource", Toast.LENGTH_SHORT) .show ()})}}}

À ce stade, si vous installez le projet sur votre smartphone ou votre tablette Android, ou sur un périphérique virtuel Android (AVD), l'actif sera téléchargé avec succès, mais l'application ne l'affiche pas. Réparons cela maintenant!

Création d'un deuxième écran: Ajout de navigation

Nous allons afficher l'actif en mode plein écran. Nous allons donc mettre à jour notre fichier main_activity.xml afin d'inclure un bouton qui, lorsque vous appuyez dessus, lancera l'activité en plein écran.

Ajoutons maintenant l’onClickListener à la fin du fichier MainActivity.kt:

import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.widget.Toast import com.github.kittinunf.fuel.android.extension.responseJson import com.github.kittinunf.fuel .httpDownload import com.github.kittinunf.fuel.httpGet import kotlinx.android.synthetic.main.activity_main. * import java.io.File classe MainActivity: AppCompatActivity () {objet compagnon {const val APIKey = "INSERT-YOUR-API -KEY "val assetURL =" https://poly.googleapis.com/v1/assets/94XG1XUy10q "} substitue l'amusement onCreate (savedInstanceState: Bundle?) {Super.onCréer (savedInstanceState) setContentView (R.layout.activity_main) if ( APIKey.startsWith ("INSERT")) {Toast.makeText (this, "Vous n'avez pas mis à jour votre clé API", Toast.LENGTH_SHORT) .show ()} else {assetURL.httpGet (listOf ("clé" de la clé APIKey)). responseJson {requête, réponse, résultat -> result.fold ({val asset = it.obj () var objectURL: String? = null var materialLibraryName: String? = null var materialLibraryURL: Str ing? = null val assetFormats = asset.getJSONArray ("formats") pour (i dans 0 jusqu'à assetFormats.length ()) {val currentFormat = assetFormats.getJSONObject (i) if (currentFormat.getString ("formatType") == "OBJ" ) {objectURL = currentFormat.getJSONObject ("root") .getString ("url") materialLibraryName = currentFormat.getJSONArray ("resources") .getJSONObject (0) .getString ("relativePath") materialLibraryURL = currentFormat.getJSON ) .getJSONObject (0) .getString ("url") pause}} objectURL !!. httpDownload (). destination {_, _ -> Fichier (filesDir, "globeAsset.obj")} .response {_, _, résultat -> resultat.fold ({}, {Toast.makeText (this, "Impossible de télécharger la ressource", Toast.LENGTH_SHORT) .show ()}) materialLibraryURL !!. httpDownload (). destination {_, _ -> Fichier (filesDir, materialLibraryName)} .response {_, _, result -> result.fold ({}, {Toast.makeText (this, "Impossible de télécharger la ressource", Toast.LENGTH_SHORT) .show ()})}}, {Toast.makeText (this, "Impossible de télécharger la ressource", Toast.LENGTH_SHORT) .sh ow ()})} // Implémenter un bouton // displayButton.setOnClickListener {val intent = Intent (this, SecondActivity :: class.java) startActivity (intent); }}}

Construire une toile 3D

Créons maintenant l’activité dans laquelle nous afficherons notre actif en mode plein écran:

  • Tout en maintenant la touche Contrôle enfoncée, cliquez sur le fichier MainActivity.kt de votre projet, puis sélectionnez «Nouveau> Fichier / classe Kotlin».
  • Ouvrez le menu déroulant "Genre" et sélectionnez "Classe".
  • Donnez à cette classe le nom «SecondActivity», puis cliquez sur «OK».

Pour dessiner un objet 3D, nous avons besoin d'une toile 3D! Je vais utiliser le rendu P3D de la bibliothèque Processing for Android, ce qui signifie étendre la classe PApplet, remplacer la méthode settings (), puis transmettre P3D en tant qu’argument à la méthode fullScreen (). Nous devons également créer une propriété qui représente l'actif Poly en tant qu'objet PShape.

displayAsset () {val canvas3D = object: PApplet () {var polyAsset: PShape? = null remplace les paramètres amusants () {fullScreen (PConstants.P3D)}

Ensuite, nous devons initialiser l'objet PShape en redéfinissant la méthode setup (), en appelant la méthode loadShape (), puis en passant le chemin absolu du fichier .obj:

remplacez fun setup () {polyAsset = loadShape (File (filesDir, "globeAsset.obj"). absolutePath)}

Dessiner sur la toile de P3D

Pour dessiner sur ce canevas 3D, nous devons redéfinir la méthode draw ():

Remplacez fun draw () {background (0) shape (polyAsset)}}

Par défaut, de nombreux actifs extraits de l'API Poly sont plus petits. Par conséquent, si vous exécutez ce code maintenant, vous risquez même de ne pas voir l'actif, selon la configuration de votre écran. Lors de la création de scènes 3D, vous créerez généralement une caméra personnalisée afin que l'utilisateur puisse explorer la scène et visualiser vos ressources 3D à 360 degrés. Toutefois, cet article n’entre pas dans le cadre de cet article. Par conséquent, je vais modifier manuellement la taille et la position de l’actif afin de s’assurer de son bon confort à l’écran.

Vous pouvez augmenter la taille de l’actif en transmettant une valeur négative à la méthode scale ():

échelle (-10f)

Vous pouvez ajuster la position de l’actif dans l’espace 3D virtuel à l’aide de la méthode translate () et des coordonnées suivantes:

  • X. Positionne l'actif sur l'axe horizontal.
  • Y. Positionne l'actif sur l'axe vertical.
  • Z. C'est l'axe «profondeur / hauteur» qui transforme un objet 2D en objet 3D. Les valeurs positives créent l'impression que l'objet s'approche de vous, tandis que les valeurs négatives donnent l'impression que l'objet s'éloigne de vous.

Notez que les transformations sont cumulatives, donc tout ce qui se passe après que la fonction accumule l'effet.

J'utilise les éléments suivants:

traduire (-50f, -100f, 10f)

Voici le code complété:

redéfinit fun draw draw () {background (0) scale (-10f) translate (-50f, -100f) // Dessine l'actif en appelant la méthode shape () // shape (polyAsset)}}

Ensuite, nous devons créer le fichier de mise en page correspondant, où nous ajouterons le canevas 3D en tant que widget FrameLayout:

  • Cliquez sur le dossier "res> layout" de votre projet en maintenant la touche Contrôle enfoncée.
  • Sélectionnez «Fichier de ressources de mise en page».
  • Nommez ce fichier «activity_second», puis cliquez sur «OK».

Maintenant que nous avons notre FrameLayout «asset_view», nous devons en informer notre SecondActivity! Revenez dans le fichier SecondActivity.kt, créez une nouvelle instance de PFragment et pointez-la dans la direction de notre widget «asset_view»:

import android.os.Bundle import android.support.v7.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_second. * import processing.android.PFragment import processing.core.PApplet import processing.core.PConstants import processing processing.core .PShape import java.io.File class SecondActivity: AppCompatActivity () {remplace le plaisir onCreate (savedInstanceState: Bundle?) {Super.onCreate (savedInstanceState) setContentView (R.layout.activity_second) displayAsset ()} private fun displayAsset () {val canvas3D = objet: PApplet () {var polyAsset: PShape? = null remplace les paramètres amusants () {fullScreen (PConstants.P3D)} remplace les paramètres amusants setup () {polyAsset = loadShape (File (filesDir, "globeAsset.obj"). absolutePath)} remplacera fun draw () {background (0) scale (-10f) traduire (-50f, -100f) forme (polyAsset)}} // Ajouter le // valeur suivante assetView = PFragment (canvas3D) assetView.setView (asset_view, this)}}

La dernière étape consiste à ajouter SecondActivity à votre manifeste:

// Ajoute le suivant //

Tester votre projet

Nous sommes maintenant prêts à tester le projet terminé! Installez-le sur votre appareil Android ou AVD et assurez-vous d'avoir une connexion Internet active. Dès que l'application sera lancée, elle téléchargera l'actif. Vous pourrez ensuite l'afficher en appuyant sur le bouton «Afficher l’actif».

Vous pouvez télécharger ce projet complet depuis GitHub.

Emballer

Dans cet article, nous avons expliqué comment utiliser l'API Poly pour récupérer un actif 3D au moment de l'exécution et comment afficher cet actif à l'aide de la bibliothèque Processing for Android. Pensez-vous que l’API Poly a le potentiel de rendre le développement de la RV et de l’AR accessible à davantage de personnes? Faites-nous savoir dans les commentaires ci-dessous!

en relation

  • Google apportera des applications AR à «des centaines de millions» d'appareils Android en 2018
  • Google vous enseignera gratuitement l'IA et l'apprentissage automatique
  • 15 meilleurs jeux de VR pour Google Cardboard
  • 10 meilleures applications de VR pour Google Cardboard
  • Qu'est-ce que Google Fuchsia? Est-ce le nouvel Android?
  • Qu'est-ce que Google Duplex? - fonctionnalités, date de sortie et plus
  • Comment créer une application de réalité virtuelle pour Android en seulement 7 minutes
  • Casques VR mobiles - Quelles sont vos meilleures options?

C’et le moment de choiir le plan de la emaine. Cette emaine, nou omme ravi de vou parler du plan de kicktart illimité de print.Pa de baux flexible ou de contrat à long terme aujourd'hui....

Il et temp de conulter le meilleur téléphone et de planifier de offre. Le premier choix de cette emaine pourrait valoir juqu’à 650 $ i vou ouhaitez changer de fournieur....

Partager