mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-07 06:12:31 -06:00
chore: remove ios and android package from monorepo (#6533)
This commit is contained in:
@@ -264,7 +264,6 @@
|
||||
"multiple_languages": "Mehrsprachigkeit",
|
||||
"name": "Name",
|
||||
"new": "Neu",
|
||||
"new_survey": "Neue Umfrage",
|
||||
"new_version_available": "Formbricks {version} ist da. Jetzt aktualisieren!",
|
||||
"next": "Weiter",
|
||||
"no_background_image_found": "Kein Hintergrundbild gefunden.",
|
||||
@@ -342,7 +341,6 @@
|
||||
"save": "Speichern",
|
||||
"save_changes": "Änderungen speichern",
|
||||
"saving": "Speichern",
|
||||
"scheduled": "Geplant",
|
||||
"search": "Suchen",
|
||||
"security": "Sicherheit",
|
||||
"segment": "Segment",
|
||||
@@ -382,7 +380,6 @@
|
||||
"survey_live": "Umfrage live",
|
||||
"survey_not_found": "Umfrage nicht gefunden",
|
||||
"survey_paused": "Umfrage pausiert.",
|
||||
"survey_scheduled": "Umfrage geplant.",
|
||||
"survey_type": "Umfragetyp",
|
||||
"surveys": "Umfragen",
|
||||
"switch_to": "Wechseln zu {environment}",
|
||||
@@ -763,47 +760,21 @@
|
||||
"unable_to_delete_api_key": "API-Schlüssel kann nicht gelöscht werden"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "Dies ist die URL deines Formbricks Backends.",
|
||||
"app_connection": "App-Verbindung",
|
||||
"app_connection_description": "Verbinde deine App mit Formbricks.",
|
||||
"cache_update_delay_description": "Wenn du Aktualisierungen an Umfragen, Kontakten, Aktionen oder anderen Daten vornimmst, kann es bis zu 5 Minuten dauern, bis diese Änderungen in deiner lokalen App, die das Formbricks SDK verwendet, angezeigt werden. Diese Verzögerung ist auf eine Einschränkung unseres aktuellen Caching-Systems zurückzuführen. Wir arbeiten aktiv an einer Überarbeitung des Cache und werden in Formbricks 4.0 eine Lösung veröffentlichen.",
|
||||
"cache_update_delay_title": "Änderungen werden aufgrund von Caching nach 5 Minuten angezeigt",
|
||||
"check_out_the_docs": "Schau dir die Docs an.",
|
||||
"dive_into_the_docs": "Tauch in die Docs ein.",
|
||||
"does_your_widget_work": "Funktioniert dein Widget?",
|
||||
"environment_id": "Deine Umgebungs-ID",
|
||||
"environment_id_description": "Diese ID identifiziert eindeutig diese Formbricks Umgebung.",
|
||||
"environment_id_description_with_environment_id": "Wird verwendet, um die richtige Umgebung zu identifizieren: {environmentId} ist deine.",
|
||||
"formbricks_sdk": "Formbricks SDK",
|
||||
"formbricks_sdk_connected": "Formbricks SDK ist verbunden",
|
||||
"formbricks_sdk_not_connected": "Formbricks SDK ist noch nicht verbunden.",
|
||||
"formbricks_sdk_not_connected_description": "Verbinde deine Website oder App mit Formbricks",
|
||||
"have_a_problem": "Hast Du ein Problem?",
|
||||
"how_to_setup": "Wie einrichten",
|
||||
"how_to_setup_description": "Befolge diese Schritte, um das Formbricks Widget in deiner App einzurichten.",
|
||||
"identifying_your_users": "deine Nutzer identifizieren",
|
||||
"if_you_are_planning_to": "Wenn Du planst zu",
|
||||
"insert_this_code_into_the": "Füge diesen Code in die",
|
||||
"need_a_more_detailed_setup_guide_for": "Brauche eine detailliertere Anleitung für",
|
||||
"not_working": "Klappt nicht?",
|
||||
"open_an_issue_on_github": "Eine Issue auf GitHub öffnen",
|
||||
"open_the_browser_console_to_see_the_logs": "Öffne die Browser Konsole, um die Logs zu sehen.",
|
||||
"receiving_data": "Daten werden empfangen \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "Erneut prüfen",
|
||||
"scroll_to_the_top": "Scroll nach oben!",
|
||||
"setup_alert_description": "Befolge dieses Schritt-für-Schritt-Tutorial, um deine App oder Website in weniger als 5 Minuten zu verbinden.",
|
||||
"setup_alert_title": "Wie man verbindet",
|
||||
"step_1": "Schritt 1: Installiere mit pnpm, npm oder yarn",
|
||||
"step_2": "Schritt 2: Widget initialisieren",
|
||||
"step_2_description": "Importiere Formbricks und initialisiere das Widget in deiner Komponente (z.B. App.tsx):",
|
||||
"step_3": "Schritt 3: Debug-Modus",
|
||||
"switch_on_the_debug_mode_by_appending": "Schalte den Debug-Modus ein, indem Du anhängst",
|
||||
"tag_of_your_app": "Tag deiner App",
|
||||
"to_the_url_where_you_load_the": "URL, wo Du die lädst",
|
||||
"want_to_learn_how_to_add_user_attributes": "Willst Du lernen, wie man Attribute hinzufügt?",
|
||||
"you_are_done": "Du bist fertig \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "du kannst die Benutzer-ID festlegen mit",
|
||||
"your_app_now_communicates_with_formbricks": "Deine App kommuniziert jetzt mit Formbricks - sie sendet Ereignisse und lädt Umfragen automatisch!"
|
||||
"setup_alert_title": "Wie man verbindet"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "Dies ist dein einziges Projekt, es kann nicht gelöscht werden. Erstelle zuerst ein neues Projekt.",
|
||||
@@ -1256,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "Umfrage automatisch schließen nach",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "Schließe die Umfrage automatisch nach einer bestimmten Anzahl von Antworten.",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Schließe die Umfrage automatisch, wenn der Benutzer nach einer bestimmten Anzahl von Sekunden nicht antwortet.",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "Schließt die Umfrage automatisch zu Beginn des Tages (UTC).",
|
||||
"automatically_mark_the_survey_as_complete_after": "Umfrage automatisch als abgeschlossen markieren nach",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "Umfrage automatisch zu Beginn des Tages (UTC) freigeben.",
|
||||
"back_button_label": "Zurück\"- Button ",
|
||||
"background_styling": "Hintergründe",
|
||||
"brand_color": "Markenfarbe",
|
||||
@@ -1306,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "Aktionen auswählen, die die Umfrage auslösen.",
|
||||
"choose_where_to_run_the_survey": "Wähle, wo die Umfrage durchgeführt werden soll.",
|
||||
"city": "Stadt",
|
||||
"close_survey_on_date": "Umfrage am Datum schließen",
|
||||
"close_survey_on_response_limit": "Umfrage bei Erreichen des Antwortlimits schließen",
|
||||
"color": "Farbe",
|
||||
"column_used_in_logic_error": "Diese Spalte wird in der Logik der Frage {questionIndex} verwendet. Bitte entferne sie zuerst aus der Logik.",
|
||||
@@ -1514,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "Weiterleitung anlegen",
|
||||
"redirect_to_url": "Zu URL weiterleiten",
|
||||
"redirect_to_url_not_available_on_free_plan": "Umleitung zu URL ist im kostenlosen Plan nicht verfügbar",
|
||||
"release_survey_on_date": "Umfrage an Datum veröffentlichen",
|
||||
"remove_description": "Beschreibung entfernen",
|
||||
"remove_translations": "Übersetzungen entfernen",
|
||||
"require_answer": "Antwort erforderlich",
|
||||
@@ -1858,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "Umfrage erfolgreich gelöscht",
|
||||
"survey_duplicated_successfully": "Umfrage erfolgreich dupliziert",
|
||||
"survey_duplication_error": "Duplizieren der Umfrage fehlgeschlagen",
|
||||
"survey_status_tooltip": "Um den Umfragestatus zu aktualisieren, aktualisiere den Zeitplan in den Umfrageoptionen.",
|
||||
"templates": {
|
||||
"all_channels": "Alle Kanäle",
|
||||
"all_industries": "Alle Branchen",
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
"multiple_languages": "Multiple languages",
|
||||
"name": "Name",
|
||||
"new": "New",
|
||||
"new_survey": "New Survey",
|
||||
"new_version_available": "Formbricks {version} is here. Upgrade now!",
|
||||
"next": "Next",
|
||||
"no_background_image_found": "No background image found.",
|
||||
@@ -342,7 +341,6 @@
|
||||
"save": "Save",
|
||||
"save_changes": "Save changes",
|
||||
"saving": "Saving",
|
||||
"scheduled": "Scheduled",
|
||||
"search": "Search",
|
||||
"security": "Security",
|
||||
"segment": "Segment",
|
||||
@@ -382,7 +380,6 @@
|
||||
"survey_live": "Survey live",
|
||||
"survey_not_found": "Survey not found",
|
||||
"survey_paused": "Survey paused.",
|
||||
"survey_scheduled": "Survey scheduled.",
|
||||
"survey_type": "Survey Type",
|
||||
"surveys": "Surveys",
|
||||
"switch_to": "Switch to {environment}",
|
||||
@@ -763,47 +760,21 @@
|
||||
"unable_to_delete_api_key": "Unable to delete API Key"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "This is the URL of your Formbricks backend.",
|
||||
"app_connection": "App Connection",
|
||||
"app_connection_description": "Connect your app to Formbricks.",
|
||||
"cache_update_delay_description": "When you make updates to surveys, contacts, actions, or other data, it can take up to 5 minutes for those changes to appear in your local app running the Formbricks SDK. This delay is due to a limitation in our current caching system. We’re actively reworking the cache and will release a fix in Formbricks 4.0.",
|
||||
"cache_update_delay_title": "Changes will be reflected after 5 minutes due to caching",
|
||||
"check_out_the_docs": "Check out the docs.",
|
||||
"dive_into_the_docs": "Dive into the docs.",
|
||||
"does_your_widget_work": "Does your widget work?",
|
||||
"environment_id": "Your Environment ID",
|
||||
"environment_id_description": "This id uniquely identifies this Formbricks environment.",
|
||||
"environment_id_description_with_environment_id": "Used to identify the correct environment: {environmentId} is yours.",
|
||||
"formbricks_sdk": "Formbricks SDK",
|
||||
"formbricks_sdk_connected": "Formbricks SDK is connected",
|
||||
"formbricks_sdk_not_connected": "Formbricks SDK is not yet connected.",
|
||||
"formbricks_sdk_not_connected_description": "Connect your website or app with Formbricks",
|
||||
"have_a_problem": "Have a problem?",
|
||||
"how_to_setup": "How to setup",
|
||||
"how_to_setup_description": "Follow these steps to setup the Formbricks widget within your app.",
|
||||
"identifying_your_users": "identifying your users",
|
||||
"if_you_are_planning_to": "If you are planning to",
|
||||
"insert_this_code_into_the": "Insert this code into the",
|
||||
"need_a_more_detailed_setup_guide_for": "Need a more detailed setup guide for",
|
||||
"not_working": "Not working?",
|
||||
"open_an_issue_on_github": "Open an issue on GitHub",
|
||||
"open_the_browser_console_to_see_the_logs": "Open the browser console to see the logs.",
|
||||
"receiving_data": "Receiving data \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "Re-check",
|
||||
"scroll_to_the_top": "Scroll to the top!",
|
||||
"setup_alert_description": "Follow this step-by-step tutorial to connect your app or website in under 5 minutes.",
|
||||
"setup_alert_title": "How to connect",
|
||||
"step_1": "Step 1: Install with pnpm, npm or yarn",
|
||||
"step_2": "Step 2: Initialize widget",
|
||||
"step_2_description": "Import Formbricks and initialize the widget in your Component (e.g. App.tsx):",
|
||||
"step_3": "Step 3: Debug mode",
|
||||
"switch_on_the_debug_mode_by_appending": "Switch on the debug mode by appending",
|
||||
"tag_of_your_app": "tag of your app",
|
||||
"to_the_url_where_you_load_the": "to the URL where you load the",
|
||||
"want_to_learn_how_to_add_user_attributes": "Want to learn how to add user attributes, custom events and more?",
|
||||
"you_are_done": "You're done \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "you can set the user id with",
|
||||
"your_app_now_communicates_with_formbricks": "Your app now communicates with Formbricks - sending events, and loading surveys automatically!"
|
||||
"setup_alert_title": "How to connect"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "This is your only project, it cannot be deleted. Create a new project first.",
|
||||
@@ -1256,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "Automatically close survey after",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "Automatically close the survey after a certain number of responses.",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Automatically close the survey if the user does not respond after certain number of seconds.",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "Automatically closes the survey at the beginning of the day (UTC).",
|
||||
"automatically_mark_the_survey_as_complete_after": "Automatically mark the survey as complete after",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "Automatically release the survey at the beginning of the day (UTC).",
|
||||
"back_button_label": "\"Back\" Button Label",
|
||||
"background_styling": "Background Styling",
|
||||
"brand_color": "Brand color",
|
||||
@@ -1306,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "Choose the actions which trigger the survey.",
|
||||
"choose_where_to_run_the_survey": "Choose where to run the survey.",
|
||||
"city": "City",
|
||||
"close_survey_on_date": "Close survey on date",
|
||||
"close_survey_on_response_limit": "Close survey on response limit",
|
||||
"color": "Color",
|
||||
"column_used_in_logic_error": "This column is used in logic of question {questionIndex}. Please remove it from logic first.",
|
||||
@@ -1514,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "Redirect thank you card",
|
||||
"redirect_to_url": "Redirect to Url",
|
||||
"redirect_to_url_not_available_on_free_plan": "Redirect To Url is not available on free plan",
|
||||
"release_survey_on_date": "Release survey on date",
|
||||
"remove_description": "Remove description",
|
||||
"remove_translations": "Remove translations",
|
||||
"require_answer": "Require Answer",
|
||||
@@ -1858,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "Survey deleted successfully!",
|
||||
"survey_duplicated_successfully": "Survey duplicated successfully.",
|
||||
"survey_duplication_error": "Failed to duplicate the survey.",
|
||||
"survey_status_tooltip": "To update the survey status, update the schedule and close setting in the survey response options.",
|
||||
"templates": {
|
||||
"all_channels": "All channels",
|
||||
"all_industries": "All industries",
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
"multiple_languages": "Plusieurs langues",
|
||||
"name": "Nom",
|
||||
"new": "Nouveau",
|
||||
"new_survey": "Nouveau Sondage",
|
||||
"new_version_available": "Formbricks {version} est là. Mettez à jour maintenant !",
|
||||
"next": "Suivant",
|
||||
"no_background_image_found": "Aucune image de fond trouvée.",
|
||||
@@ -342,7 +341,6 @@
|
||||
"save": "Enregistrer",
|
||||
"save_changes": "Enregistrer les modifications",
|
||||
"saving": "Sauvegarder",
|
||||
"scheduled": "Programmé",
|
||||
"search": "Recherche",
|
||||
"security": "Sécurité",
|
||||
"segment": "Segmenter",
|
||||
@@ -382,7 +380,6 @@
|
||||
"survey_live": "Sondage en direct",
|
||||
"survey_not_found": "Sondage non trouvé",
|
||||
"survey_paused": "Sondage en pause.",
|
||||
"survey_scheduled": "Sondage programmé.",
|
||||
"survey_type": "Type de sondage",
|
||||
"surveys": "Enquêtes",
|
||||
"switch_to": "Passer à {environment}",
|
||||
@@ -763,47 +760,21 @@
|
||||
"unable_to_delete_api_key": "Impossible de supprimer la clé API"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "Ceci est l'URL de votre backend Formbricks.",
|
||||
"app_connection": "Connexion d'application",
|
||||
"app_connection_description": "Connectez votre application à Formbricks.",
|
||||
"cache_update_delay_description": "Lorsque vous effectuez des mises à jour sur les sondages, contacts, actions ou autres données, cela peut prendre jusqu'à 5 minutes pour que ces modifications apparaissent dans votre application locale exécutant le SDK Formbricks. Ce délai est dû à une limitation de notre système de mise en cache actuel. Nous retravaillons activement le cache et publierons une correction dans Formbricks 4.0.",
|
||||
"cache_update_delay_title": "Les modifications seront reflétées après 5 minutes en raison de la mise en cache",
|
||||
"check_out_the_docs": "Consultez la documentation.",
|
||||
"dive_into_the_docs": "Plongez dans la documentation.",
|
||||
"does_your_widget_work": "Votre widget fonctionne-t-il ?",
|
||||
"environment_id": "Votre identifiant d'environnement",
|
||||
"environment_id_description": "Cet identifiant identifie de manière unique cet environnement Formbricks.",
|
||||
"environment_id_description_with_environment_id": "Utilisé pour identifier l'environnement correct : {environmentId} est le vôtre.",
|
||||
"formbricks_sdk": "SDK Formbricks",
|
||||
"formbricks_sdk_connected": "Le SDK Formbricks est connecté",
|
||||
"formbricks_sdk_not_connected": "Le SDK Formbricks n'est pas encore connecté.",
|
||||
"formbricks_sdk_not_connected_description": "Connectez votre site web ou votre application à Formbricks.",
|
||||
"have_a_problem": "Vous avez un problème ?",
|
||||
"how_to_setup": "Comment configurer",
|
||||
"how_to_setup_description": "Suivez ces étapes pour configurer le widget Formbricks dans votre application.",
|
||||
"identifying_your_users": "identifier vos utilisateurs",
|
||||
"if_you_are_planning_to": "Si vous prévoyez de",
|
||||
"insert_this_code_into_the": "Insérez ce code dans le",
|
||||
"need_a_more_detailed_setup_guide_for": "Besoin d'un guide d'installation plus détaillé pour",
|
||||
"not_working": "Ça ne fonctionne pas ?",
|
||||
"open_an_issue_on_github": "Ouvrir un problème sur GitHub",
|
||||
"open_the_browser_console_to_see_the_logs": "Ouvrez la console du navigateur pour voir les journaux.",
|
||||
"receiving_data": "Réception des données \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "Re-vérifier",
|
||||
"scroll_to_the_top": "Faites défiler vers le haut !",
|
||||
"setup_alert_description": "Suivez ce tutoriel étape par étape pour connecter votre application ou site web en moins de 5 minutes.",
|
||||
"setup_alert_title": "Comment connecter",
|
||||
"step_1": "Étape 1 : Installer avec pnpm, npm ou yarn",
|
||||
"step_2": "Étape 2 : Initialiser le widget",
|
||||
"step_2_description": "Importez Formbricks et initialisez le widget dans votre composant (par exemple, App.tsx) :",
|
||||
"step_3": "Étape 3 : Mode débogage",
|
||||
"switch_on_the_debug_mode_by_appending": "Activez le mode débogage en ajoutant",
|
||||
"tag_of_your_app": "étiquette de votre application",
|
||||
"to_the_url_where_you_load_the": "vers l'URL où vous chargez le",
|
||||
"want_to_learn_how_to_add_user_attributes": "Vous voulez apprendre à ajouter des attributs utilisateur, des événements personnalisés et plus encore ?",
|
||||
"you_are_done": "Vous avez terminé \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "vous pouvez définir l'ID utilisateur avec",
|
||||
"your_app_now_communicates_with_formbricks": "Votre application communique désormais avec Formbricks - envoyant des événements et chargeant des enquêtes automatiquement !"
|
||||
"setup_alert_title": "Comment connecter"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "Ceci est votre seul projet, il ne peut pas être supprimé. Créez d'abord un nouveau projet.",
|
||||
@@ -1256,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "Fermer automatiquement l'enquête après",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "Fermer automatiquement l'enquête après un certain nombre de réponses.",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Fermer automatiquement l'enquête si l'utilisateur ne répond pas après un certain nombre de secondes.",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "Ferme automatiquement l'enquête au début de la journée (UTC).",
|
||||
"automatically_mark_the_survey_as_complete_after": "Marquer automatiquement l'enquête comme terminée après",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "Libérer automatiquement l'enquête au début de la journée (UTC).",
|
||||
"back_button_label": "Label du bouton \"Retour''",
|
||||
"background_styling": "Style de fond",
|
||||
"brand_color": "Couleur de marque",
|
||||
@@ -1306,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "Choisissez les actions qui déclenchent l'enquête.",
|
||||
"choose_where_to_run_the_survey": "Choisissez où réaliser l'enquête.",
|
||||
"city": "Ville",
|
||||
"close_survey_on_date": "Clôturer l'enquête à la date",
|
||||
"close_survey_on_response_limit": "Fermer l'enquête sur la limite de réponse",
|
||||
"color": "Couleur",
|
||||
"column_used_in_logic_error": "Cette colonne est utilisée dans la logique de la question {questionIndex}. Veuillez d'abord la supprimer de la logique.",
|
||||
@@ -1514,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "Carte de remerciement de redirection",
|
||||
"redirect_to_url": "Rediriger vers l'URL",
|
||||
"redirect_to_url_not_available_on_free_plan": "La redirection vers l'URL n'est pas disponible sur le plan gratuit.",
|
||||
"release_survey_on_date": "Publier l'enquête à la date",
|
||||
"remove_description": "Supprimer la description",
|
||||
"remove_translations": "Supprimer les traductions",
|
||||
"require_answer": "Réponse requise",
|
||||
@@ -1858,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "Enquête supprimée avec succès !",
|
||||
"survey_duplicated_successfully": "Enquête dupliquée avec succès.",
|
||||
"survey_duplication_error": "Échec de la duplication de l'enquête.",
|
||||
"survey_status_tooltip": "Pour mettre à jour le statut de l'enquête, mettez à jour le calendrier et fermez les paramètres dans les options de réponse à l'enquête.",
|
||||
"templates": {
|
||||
"all_channels": "Tous les canaux",
|
||||
"all_industries": "Tous les secteurs",
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
"multiple_languages": "多言語",
|
||||
"name": "名前",
|
||||
"new": "新規",
|
||||
"new_survey": "新規フォーム",
|
||||
"new_version_available": "Formbricks {version} が利用可能です。今すぐアップグレード!",
|
||||
"next": "次へ",
|
||||
"no_background_image_found": "背景画像が見つかりません。",
|
||||
@@ -342,7 +341,6 @@
|
||||
"save": "保存",
|
||||
"save_changes": "変更を保存",
|
||||
"saving": "保存中",
|
||||
"scheduled": "スケジュール済み",
|
||||
"search": "検索",
|
||||
"security": "セキュリティ",
|
||||
"segment": "セグメント",
|
||||
@@ -382,7 +380,6 @@
|
||||
"survey_live": "フォーム公開中",
|
||||
"survey_not_found": "フォームが見つかりません",
|
||||
"survey_paused": "フォームは一時停止中です。",
|
||||
"survey_scheduled": "フォームはスケジュール済みです。",
|
||||
"survey_type": "フォームの種類",
|
||||
"surveys": "フォーム",
|
||||
"switch_to": "{environment}に切り替え",
|
||||
@@ -763,47 +760,21 @@
|
||||
"unable_to_delete_api_key": "APIキーを削除できませんでした"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "これはFormbricksバックエンドのURLです。",
|
||||
"app_connection": "アプリ接続",
|
||||
"app_connection_description": "あなたのアプリをFormbricksに接続します。",
|
||||
"cache_update_delay_description": "フォーム・連絡先・アクションなどを更新してから、Formbricks SDK を実行中のローカルアプリに反映されるまで最大5分かかる場合があります。これは現在のキャッシュ方式の制限によるものです。私たちはキャッシュを改修中で、Formbricks 4.0 で修正を提供予定です。",
|
||||
"cache_update_delay_title": "キャッシュのため変更の反映に最大5分かかります",
|
||||
"check_out_the_docs": "ドキュメントを見る",
|
||||
"dive_into_the_docs": "ドキュメントを詳しく読む",
|
||||
"does_your_widget_work": "ウィジェットは動作していますか?",
|
||||
"environment_id": "あなたのEnvironmentId",
|
||||
"environment_id_description": "このIDはこのFormbricks環境を一意に識別します。",
|
||||
"environment_id_description_with_environment_id": "正しい環境を識別するために使用します: {environmentId} があなたのIDです。",
|
||||
"formbricks_sdk": "Formbricks SDK",
|
||||
"formbricks_sdk_connected": "Formbricks SDK は接続されています",
|
||||
"formbricks_sdk_not_connected": "Formbricks SDK はまだ接続されていません。",
|
||||
"formbricks_sdk_not_connected_description": "あなたのウェブサイトまたはアプリをFormbricksに接続してください",
|
||||
"have_a_problem": "問題がありますか?",
|
||||
"how_to_setup": "セットアップ方法",
|
||||
"how_to_setup_description": "アプリ内でFormbricksウィジェットを設定する手順に従ってください。",
|
||||
"identifying_your_users": "ユーザーの識別",
|
||||
"if_you_are_planning_to": "〜を計画している場合は",
|
||||
"insert_this_code_into_the": "次のコードを挿入してください",
|
||||
"need_a_more_detailed_setup_guide_for": "より詳細なセットアップガイドが必要ですか",
|
||||
"not_working": "うまく動作しませんか?",
|
||||
"open_an_issue_on_github": "GitHubでIssueを作成",
|
||||
"open_the_browser_console_to_see_the_logs": "ブラウザのコンソールを開いてログを確認してください。",
|
||||
"receiving_data": "データ受信中 \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "再チェック",
|
||||
"scroll_to_the_top": "ページ上部に戻る",
|
||||
"setup_alert_description": "5 分以内でアプリまたはウェブサイト を 接続する手順をステップバイステップ の チュートリアルに従ってください。",
|
||||
"setup_alert_title": "接続方法",
|
||||
"step_1": "ステップ1: pnpm / npm / yarnでインストール",
|
||||
"step_2": "ステップ2: ウィジェットの初期化",
|
||||
"step_2_description": "Formbricksをインポートし、コンポーネント(例: App.tsx)でウィジェットを初期化します。",
|
||||
"step_3": "ステップ3: デバッグモード",
|
||||
"switch_on_the_debug_mode_by_appending": "URLの末尾に以下を付与してデバッグモードを有効化",
|
||||
"tag_of_your_app": "あなたのアプリのタグ",
|
||||
"to_the_url_where_you_load_the": "読み込みを行うURLに",
|
||||
"want_to_learn_how_to_add_user_attributes": "ユーザー属性、カスタムイベント等の追加方法を学びますか?",
|
||||
"you_are_done": "完了です \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "ユーザーIDは次のように設定できます",
|
||||
"your_app_now_communicates_with_formbricks": "あなたのアプリはFormbricksと通信し、イベント送信やフォームの自動読込を行います!"
|
||||
"setup_alert_title": "接続方法"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "これは唯一のプロジェクトのため削除できません。まず新しいプロジェクトを作成してください。",
|
||||
@@ -1256,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "フォームを自動的に閉じる",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "一定の回答数に達した後にフォームを自動的に閉じます。",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "ユーザーが一定秒数応答しない場合、フォームを自動的に閉じます。",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "日(UTC)の開始時にフォームを自動的に閉じます。",
|
||||
"automatically_mark_the_survey_as_complete_after": "フォームを自動的に完了としてマークする",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "日(UTC)の開始時にフォームを自動的にリリースします。",
|
||||
"back_button_label": "「戻る」ボタンのラベル",
|
||||
"background_styling": "背景のスタイル",
|
||||
"brand_color": "ブランドカラー",
|
||||
@@ -1306,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "フォームをトリガーするアクションを選択してください。",
|
||||
"choose_where_to_run_the_survey": "フォームを実行する場所を選択してください。",
|
||||
"city": "市区町村",
|
||||
"close_survey_on_date": "日付でフォームを閉じる",
|
||||
"close_survey_on_response_limit": "回答数の上限でフォームを閉じる",
|
||||
"color": "色",
|
||||
"column_used_in_logic_error": "この列は質問 {questionIndex} のロジックで使用されています。まず、ロジックから削除してください。",
|
||||
@@ -1514,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "サンクスクカードをリダイレクト",
|
||||
"redirect_to_url": "URLにリダイレクト",
|
||||
"redirect_to_url_not_available_on_free_plan": "URLへのリダイレクトは無料プランでは利用できません",
|
||||
"release_survey_on_date": "日付でフォームをリリース",
|
||||
"remove_description": "説明を削除",
|
||||
"remove_translations": "翻訳を削除",
|
||||
"require_answer": "回答を必須にする",
|
||||
@@ -1858,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "フォームを正常に削除しました!",
|
||||
"survey_duplicated_successfully": "フォームを正常に複製しました。",
|
||||
"survey_duplication_error": "フォームの複製に失敗しました。",
|
||||
"survey_status_tooltip": "フォームのステータスを更新するには、フォームの回答オプションでスケジュールとクローズ設定を更新してください。",
|
||||
"templates": {
|
||||
"all_channels": "すべてのチャネル",
|
||||
"all_industries": "すべての業界",
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
"multiple_languages": "Vários idiomas",
|
||||
"name": "Nome",
|
||||
"new": "Novo",
|
||||
"new_survey": "Nova Pesquisa",
|
||||
"new_version_available": "Formbricks {version} chegou. Atualize agora!",
|
||||
"next": "Próximo",
|
||||
"no_background_image_found": "Imagem de fundo não encontrada.",
|
||||
@@ -342,7 +341,6 @@
|
||||
"save": "Salvar",
|
||||
"save_changes": "Salvar alterações",
|
||||
"saving": "Salvando",
|
||||
"scheduled": "agendado",
|
||||
"search": "Buscar",
|
||||
"security": "Segurança",
|
||||
"segment": "segmento",
|
||||
@@ -382,7 +380,6 @@
|
||||
"survey_live": "Pesquisa ao vivo",
|
||||
"survey_not_found": "Pesquisa não encontrada",
|
||||
"survey_paused": "Pesquisa pausada.",
|
||||
"survey_scheduled": "Pesquisa agendada.",
|
||||
"survey_type": "Tipo de Pesquisa",
|
||||
"surveys": "Pesquisas",
|
||||
"switch_to": "Mudar para {environment}",
|
||||
@@ -763,47 +760,21 @@
|
||||
"unable_to_delete_api_key": "Não foi possível deletar a Chave API"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "Essa é a URL do seu backend do Formbricks.",
|
||||
"app_connection": "Conexão do App",
|
||||
"app_connection_description": "Conecte seu app ao Formbricks.",
|
||||
"cache_update_delay_description": "Quando você faz atualizações em pesquisas, contatos, ações ou outros dados, pode levar até 5 minutos para que essas mudanças apareçam no seu app local rodando o SDK do Formbricks. Esse atraso é devido a uma limitação no nosso sistema de cache atual. Estamos ativamente retrabalhando o cache e planejamos lançar uma correção no Formbricks 4.0.",
|
||||
"cache_update_delay_title": "As mudanças serão refletidas após 5 minutos devido ao cache",
|
||||
"check_out_the_docs": "Confere a documentação.",
|
||||
"dive_into_the_docs": "Mergulha na documentação.",
|
||||
"does_your_widget_work": "Seu widget funciona?",
|
||||
"environment_id": "Seu Id do Ambiente",
|
||||
"environment_id_description": "Este ID identifica exclusivamente este ambiente do Formbricks.",
|
||||
"environment_id_description_with_environment_id": "Usado para identificar o ambiente correto: {environmentId} é o seu.",
|
||||
"formbricks_sdk": "SDK do Formbricks",
|
||||
"formbricks_sdk_connected": "O SDK do Formbricks está conectado",
|
||||
"formbricks_sdk_not_connected": "O SDK do Formbricks ainda não está conectado.",
|
||||
"formbricks_sdk_not_connected_description": "Conecte seu site ou app com o Formbricks",
|
||||
"have_a_problem": "Tá com problema?",
|
||||
"how_to_setup": "Como configurar",
|
||||
"how_to_setup_description": "Siga esses passos para configurar o widget do Formbricks no seu app.",
|
||||
"identifying_your_users": "identificando seus usuários",
|
||||
"if_you_are_planning_to": "Se você está planejando",
|
||||
"insert_this_code_into_the": "Insere esse código no",
|
||||
"need_a_more_detailed_setup_guide_for": "Preciso de um guia de configuração mais detalhado para",
|
||||
"not_working": "Não tá funcionando?",
|
||||
"open_an_issue_on_github": "Abre uma issue no GitHub",
|
||||
"open_the_browser_console_to_see_the_logs": "Abre o console do navegador pra ver os logs.",
|
||||
"receiving_data": "Recebendo dados \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "Verificar novamente",
|
||||
"scroll_to_the_top": "Rola pra cima!",
|
||||
"setup_alert_description": "Siga este tutorial passo a passo para conectar seu app ou site em menos de 5 minutos.",
|
||||
"setup_alert_title": "Como conectar",
|
||||
"step_1": "Passo 1: Instale com pnpm, npm ou yarn",
|
||||
"step_2": "Passo 2: Iniciar widget",
|
||||
"step_2_description": "Importe o Formbricks e inicialize o widget no seu Componente (por exemplo, App.tsx):",
|
||||
"step_3": "Passo 3: Modo de depuração",
|
||||
"switch_on_the_debug_mode_by_appending": "Ative o modo de depuração adicionando",
|
||||
"tag_of_your_app": "etiqueta do seu app",
|
||||
"to_the_url_where_you_load_the": "para a URL onde você carrega o",
|
||||
"want_to_learn_how_to_add_user_attributes": "Quer aprender como adicionar atributos de usuário, eventos personalizados e mais?",
|
||||
"you_are_done": "Você terminou \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "você pode definir o id do usuário com",
|
||||
"your_app_now_communicates_with_formbricks": "Seu app agora se comunica com o Formbricks - enviando eventos e carregando pesquisas automaticamente!"
|
||||
"setup_alert_title": "Como conectar"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "Esse é seu único projeto, não pode ser deletado. Crie um novo projeto primeiro.",
|
||||
@@ -1256,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "Fechar pesquisa automaticamente após",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "Fechar automaticamente a pesquisa depois de um certo número de respostas.",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Feche automaticamente a pesquisa se o usuário não responder depois de alguns segundos.",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "Fecha automaticamente a pesquisa no começo do dia (UTC).",
|
||||
"automatically_mark_the_survey_as_complete_after": "Marcar automaticamente a pesquisa como concluída após",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "Liberar automaticamente a pesquisa no começo do dia (UTC).",
|
||||
"back_button_label": "Voltar",
|
||||
"background_styling": "Estilo de Fundo",
|
||||
"brand_color": "Cor da marca",
|
||||
@@ -1306,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "Escolha as ações que disparam a pesquisa.",
|
||||
"choose_where_to_run_the_survey": "Escolha onde realizar a pesquisa.",
|
||||
"city": "cidade",
|
||||
"close_survey_on_date": "Fechar pesquisa na data",
|
||||
"close_survey_on_response_limit": "Fechar pesquisa ao atingir limite de respostas",
|
||||
"color": "cor",
|
||||
"column_used_in_logic_error": "Esta coluna é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
|
||||
@@ -1514,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "Redirecionar cartão de agradecimento",
|
||||
"redirect_to_url": "Redirecionar para URL",
|
||||
"redirect_to_url_not_available_on_free_plan": "Redirecionar para URL não está disponível no plano gratuito",
|
||||
"release_survey_on_date": "Lançar pesquisa na data",
|
||||
"remove_description": "Remover descrição",
|
||||
"remove_translations": "Remover traduções",
|
||||
"require_answer": "Preciso de Resposta",
|
||||
@@ -1858,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "Pesquisa deletada com sucesso!",
|
||||
"survey_duplicated_successfully": "Pesquisa duplicada com sucesso.",
|
||||
"survey_duplication_error": "Falha ao duplicar a pesquisa.",
|
||||
"survey_status_tooltip": "Para atualizar o status da pesquisa, atualize o cronograma e feche a configuração nas opções de resposta da pesquisa.",
|
||||
"templates": {
|
||||
"all_channels": "Todos os canais",
|
||||
"all_industries": "Todas as indústrias",
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
"multiple_languages": "Várias línguas",
|
||||
"name": "Nome",
|
||||
"new": "Novo",
|
||||
"new_survey": "Novo inquérito",
|
||||
"new_version_available": "Formbricks {version} está aqui. Atualize agora!",
|
||||
"next": "Seguinte",
|
||||
"no_background_image_found": "Nenhuma imagem de fundo encontrada.",
|
||||
@@ -342,7 +341,6 @@
|
||||
"save": "Guardar",
|
||||
"save_changes": "Guardar alterações",
|
||||
"saving": "Guardando",
|
||||
"scheduled": "Agendado",
|
||||
"search": "Procurar",
|
||||
"security": "Segurança",
|
||||
"segment": "Segmento",
|
||||
@@ -382,7 +380,6 @@
|
||||
"survey_live": "Inquérito ao vivo",
|
||||
"survey_not_found": "Inquérito não encontrado",
|
||||
"survey_paused": "Inquérito pausado.",
|
||||
"survey_scheduled": "Inquérito agendado.",
|
||||
"survey_type": "Tipo de Inquérito",
|
||||
"surveys": "Inquéritos",
|
||||
"switch_to": "Mudar para {environment}",
|
||||
@@ -763,47 +760,21 @@
|
||||
"unable_to_delete_api_key": "Não é possível eliminar a chave API"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "Este é o URL do seu backend Formbricks.",
|
||||
"app_connection": "Ligação de Aplicação",
|
||||
"app_connection_description": "Ligue a sua aplicação ao Formbricks",
|
||||
"cache_update_delay_description": "Quando fizer atualizações para inquéritos, contactos, ações ou outros dados, pode demorar até 5 minutos para que essas alterações apareçam na sua aplicação local a correr o SDK do Formbricks. Este atraso deve-se a uma limitação no nosso atual sistema de cache. Estamos a trabalhar ativamente na reformulação da cache e lançaremos uma correção no Formbricks 4.0.",
|
||||
"cache_update_delay_title": "As alterações serão refletidas após 5 minutos devido ao armazenamento em cache.",
|
||||
"check_out_the_docs": "Consulte a documentação.",
|
||||
"dive_into_the_docs": "Mergulhe na documentação.",
|
||||
"does_your_widget_work": "O seu widget funciona?",
|
||||
"environment_id": "O Seu ID de Ambiente",
|
||||
"environment_id_description": "Este id identifica de forma única este ambiente Formbricks.",
|
||||
"environment_id_description_with_environment_id": "Usado para identificar o ambiente correto: {environmentId} é o seu.",
|
||||
"formbricks_sdk": "SDK Formbricks",
|
||||
"formbricks_sdk_connected": "O SDK do Formbricks está conectado",
|
||||
"formbricks_sdk_not_connected": "O SDK do Formbricks ainda não está conectado",
|
||||
"formbricks_sdk_not_connected_description": "Ligue o seu website ou aplicação ao Formbricks",
|
||||
"have_a_problem": "Tem um problema?",
|
||||
"how_to_setup": "Como configurar",
|
||||
"how_to_setup_description": "Siga estes passos para configurar o widget Formbricks na sua aplicação.",
|
||||
"identifying_your_users": "identificar os seus utilizadores",
|
||||
"if_you_are_planning_to": "Se está a planear",
|
||||
"insert_this_code_into_the": "Insira este código no",
|
||||
"need_a_more_detailed_setup_guide_for": "Precisa de um guia de configuração mais detalhado para",
|
||||
"not_working": "Não está a funcionar?",
|
||||
"open_an_issue_on_github": "Abrir um problema no GitHub",
|
||||
"open_the_browser_console_to_see_the_logs": "Abra a consola do navegador para ver os registos.",
|
||||
"receiving_data": "A receber dados \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "Verificar novamente",
|
||||
"scroll_to_the_top": "Rolar para o topo!",
|
||||
"setup_alert_description": "Siga este tutorial passo-a-passo para ligar a sua aplicação ou website em menos de 5 minutos",
|
||||
"setup_alert_title": "Como conectar",
|
||||
"step_1": "Passo 1: Instalar com pnpm, npm ou yarn",
|
||||
"step_2": "Passo 2: Inicializar widget",
|
||||
"step_2_description": "Importar Formbricks e inicializar o widget no seu Componente (por exemplo, App.tsx):",
|
||||
"step_3": "Passo 3: Modo de depuração",
|
||||
"switch_on_the_debug_mode_by_appending": "Ativar o modo de depuração adicionando",
|
||||
"tag_of_your_app": "tag da sua aplicação",
|
||||
"to_the_url_where_you_load_the": "para o URL onde carrega o",
|
||||
"want_to_learn_how_to_add_user_attributes": "Quer aprender a adicionar atributos de utilizador, eventos personalizados e mais?",
|
||||
"you_are_done": "Está concluído \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "pode definir o ID do utilizador com",
|
||||
"your_app_now_communicates_with_formbricks": "A sua aplicação agora comunica com o Formbricks - enviando eventos e carregando inquéritos automaticamente!"
|
||||
"setup_alert_title": "Como conectar"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "Este é o seu único projeto, não pode ser eliminado. Crie um novo projeto primeiro.",
|
||||
@@ -1256,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "Fechar automaticamente o inquérito após",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "Fechar automaticamente o inquérito após um certo número de respostas",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Fechar automaticamente o inquérito se o utilizador não responder após um certo número de segundos.",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "Encerrar automaticamente o inquérito no início do dia (UTC).",
|
||||
"automatically_mark_the_survey_as_complete_after": "Marcar automaticamente o inquérito como concluído após",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "Lançar automaticamente o inquérito no início do dia (UTC).",
|
||||
"back_button_label": "Rótulo do botão \"Voltar\"",
|
||||
"background_styling": "Estilo de Fundo",
|
||||
"brand_color": "Cor da marca",
|
||||
@@ -1306,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "Escolha as ações que desencadeiam o inquérito.",
|
||||
"choose_where_to_run_the_survey": "Escolha onde realizar o inquérito.",
|
||||
"city": "Cidade",
|
||||
"close_survey_on_date": "Encerrar inquérito na data",
|
||||
"close_survey_on_response_limit": "Fechar inquérito no limite de respostas",
|
||||
"color": "Cor",
|
||||
"column_used_in_logic_error": "Esta coluna é usada na lógica da pergunta {questionIndex}. Por favor, remova-a da lógica primeiro.",
|
||||
@@ -1514,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "Redirecionar cartão de agradecimento",
|
||||
"redirect_to_url": "Redirecionar para Url",
|
||||
"redirect_to_url_not_available_on_free_plan": "Redirecionar para URL não está disponível no plano gratuito",
|
||||
"release_survey_on_date": "Lançar inquérito na data",
|
||||
"remove_description": "Remover descrição",
|
||||
"remove_translations": "Remover traduções",
|
||||
"require_answer": "Exigir Resposta",
|
||||
@@ -1858,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "Inquérito eliminado com sucesso!",
|
||||
"survey_duplicated_successfully": "Inquérito duplicado com sucesso.",
|
||||
"survey_duplication_error": "Falha ao duplicar o inquérito.",
|
||||
"survey_status_tooltip": "Para atualizar o estado do inquérito, atualize o agendamento e feche a configuração nas opções de resposta do inquérito.",
|
||||
"templates": {
|
||||
"all_channels": "Todos os canais",
|
||||
"all_industries": "Todas as indústrias",
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
"multiple_languages": "Mai multe limbi",
|
||||
"name": "Nume",
|
||||
"new": "Nou",
|
||||
"new_survey": "Chestionar Nou",
|
||||
"new_version_available": "Formbricks {version} este disponibil. Actualizați acum!",
|
||||
"next": "Următorul",
|
||||
"no_background_image_found": "Nu a fost găsită nicio imagine de fundal.",
|
||||
@@ -342,7 +341,6 @@
|
||||
"save": "Salvează",
|
||||
"save_changes": "Salvează modificările",
|
||||
"saving": "Salvare",
|
||||
"scheduled": "Programat",
|
||||
"search": "Căutare",
|
||||
"security": "Securitate",
|
||||
"segment": "Segment",
|
||||
@@ -382,7 +380,6 @@
|
||||
"survey_live": "Chestionar activ",
|
||||
"survey_not_found": "Sondajul nu a fost găsit",
|
||||
"survey_paused": "Chestionar oprit.",
|
||||
"survey_scheduled": "Chestionar programat.",
|
||||
"survey_type": "Tip Chestionar",
|
||||
"surveys": "Sondaje",
|
||||
"switch_to": "Comută la {environment}",
|
||||
@@ -763,47 +760,21 @@
|
||||
"unable_to_delete_api_key": "Imposibil de șters cheia API"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "Acesta este URL-ul backend-ului tău Formbricks.",
|
||||
"app_connection": "Conectare aplicație",
|
||||
"app_connection_description": "Conectează aplicația ta la Formbricks.",
|
||||
"cache_update_delay_description": "Când faci actualizări la sondaje, contacte, acțiuni sau alte date, poate dura până la 5 minute pentru ca aceste modificări să apară în aplicația locală care rulează SDK Formbricks. Această întârziere se datorează unei limitări în sistemul nostru actual de caching. Revedem activ cache-ul și vom lansa o soluție în Formbricks 4.0.",
|
||||
"cache_update_delay_title": "Modificările vor fi reflectate după 5 minute datorită memorării în cache",
|
||||
"check_out_the_docs": "Consultați documentația.",
|
||||
"dive_into_the_docs": "Accesați documentația.",
|
||||
"does_your_widget_work": "Funcționează widgetul dvs.?",
|
||||
"environment_id": "ID-ul mediului tău",
|
||||
"environment_id_description": "Acest id identifică în mod unic acest mediu Formbricks.",
|
||||
"environment_id_description_with_environment_id": "Folosit pentru a identifica mediul corect: {environmentId} este al tău.",
|
||||
"formbricks_sdk": "SDK Formbricks",
|
||||
"formbricks_sdk_connected": "SDK Formbricks este conectat",
|
||||
"formbricks_sdk_not_connected": "Formbricks SDK nu este încă conectat.",
|
||||
"formbricks_sdk_not_connected_description": "Conectează-ți site-ul sau aplicația cu Formbricks",
|
||||
"have_a_problem": "Aveți o problemă?",
|
||||
"how_to_setup": "Cum să configurezi",
|
||||
"how_to_setup_description": "Urmează acești pași pentru a configura widget-ul Formbricks în aplicația ta.",
|
||||
"identifying_your_users": "identificarea utilizatorilor tăi",
|
||||
"if_you_are_planning_to": "Dacă planifici să",
|
||||
"insert_this_code_into_the": "Insereză acest cod în",
|
||||
"need_a_more_detailed_setup_guide_for": "Aveți nevoie de un ghid de configurare mai detaliat pentru",
|
||||
"not_working": "Nu funcționează?",
|
||||
"open_an_issue_on_github": "Deschideți o problemă pe GitHub",
|
||||
"open_the_browser_console_to_see_the_logs": "Deschide consola browserului pentru a vedea jurnalele.",
|
||||
"receiving_data": "Recepționare date \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "Re-verifică",
|
||||
"scroll_to_the_top": "Derulați în partea de sus!",
|
||||
"setup_alert_description": "Urmează acest tutorial pas cu pas pentru a-ți conecta aplicația sau site-ul în mai puțin de 5 minute.",
|
||||
"setup_alert_title": "Cum să conectezi",
|
||||
"step_1": "Pasul 1: Instalează cu pnpm, npm sau yarn",
|
||||
"step_2": "Pasul 2: Inițializează widget-ul",
|
||||
"step_2_description": "Importați Formbricks și inițializați widgetul în componenta dumneavoastră (de exemplu, App.tsx):",
|
||||
"step_3": "Pasul 3: Modul de depanare",
|
||||
"switch_on_the_debug_mode_by_appending": "Activează modul de depanare prin adăugare",
|
||||
"tag_of_your_app": "eticheta aplicației tale",
|
||||
"to_the_url_where_you_load_the": "la adresa URL de unde încarci",
|
||||
"want_to_learn_how_to_add_user_attributes": "Doriți să aflați cum să adăugați atribute ale utilizatorului, evenimente personalizate și altele?",
|
||||
"you_are_done": "Ai terminat \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "poți seta ID-ul utilizatorului cu",
|
||||
"your_app_now_communicates_with_formbricks": "Aplicația ta comunică acum cu Formbricks - trimite evenimente și încarcă automat sondajele!"
|
||||
"setup_alert_title": "Cum să conectezi"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "Acesta este singurul tău proiect, nu poate fi șters. Creează mai întâi un proiect nou.",
|
||||
@@ -1256,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "Închideți automat sondajul după",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "Închideți automat sondajul după un număr anumit de răspunsuri.",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "Închideți automat sondajul dacă utilizatorul nu răspunde după un anumit număr de secunde.",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "Închide automat sondajul la începutul zilei (UTC).",
|
||||
"automatically_mark_the_survey_as_complete_after": "Marcați automat sondajul ca finalizat după",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "Eliberați automat sondajul la începutul zilei (UTC).",
|
||||
"back_button_label": "Etichetă buton \"Înapoi\"",
|
||||
"background_styling": "Stilizare fundal",
|
||||
"brand_color": "Culoarea brandului",
|
||||
@@ -1306,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "Alegeți acțiunile care declanșează sondajul.",
|
||||
"choose_where_to_run_the_survey": "Alegeți unde să rulați chestionarul.",
|
||||
"city": "Oraș",
|
||||
"close_survey_on_date": "Închide sondajul la dată",
|
||||
"close_survey_on_response_limit": "Închideți sondajul la limită de răspunsuri",
|
||||
"color": "Culoare",
|
||||
"column_used_in_logic_error": "Această coloană este folosită în logica întrebării {questionIndex}. Vă rugăm să o eliminați din logică mai întâi.",
|
||||
@@ -1514,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "Redirecționează cardul de mulțumire",
|
||||
"redirect_to_url": "Redirecționează către URL",
|
||||
"redirect_to_url_not_available_on_free_plan": "\"Redirecționarea către URL nu este disponibilă în planul gratuit\"",
|
||||
"release_survey_on_date": "Eliberați sondajul la dată",
|
||||
"remove_description": "Eliminați descrierea",
|
||||
"remove_translations": "Eliminați traducerile",
|
||||
"require_answer": "Cere răspuns",
|
||||
@@ -1858,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "\"Sondaj șters cu succes!\"",
|
||||
"survey_duplicated_successfully": "\"Sondaj duplicat cu succes!\"",
|
||||
"survey_duplication_error": "Eșec la duplicarea sondajului.",
|
||||
"survey_status_tooltip": "Pentru a actualiza starea sondajului, actualizați programarea și setările de închidere în opțiunile de răspuns la sondaj.",
|
||||
"templates": {
|
||||
"all_channels": "Toate canalele",
|
||||
"all_industries": "Toate industriile",
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
"account_settings": "帐户设置",
|
||||
"action": "操作",
|
||||
"actions": "操作",
|
||||
"actions_description": "代码 和 无代码 操作 用于 触发 拦截 调查 在 应用程序 和 网站 中。",
|
||||
"active_surveys": "活跃 调查",
|
||||
"activity": "活动",
|
||||
"add": "添加",
|
||||
@@ -263,7 +264,6 @@
|
||||
"multiple_languages": "多种 语言",
|
||||
"name": "名称",
|
||||
"new": "新建",
|
||||
"new_survey": "新 调查",
|
||||
"new_version_available": "Formbricks {version} 在 这里。立即 升级!",
|
||||
"next": "下一步",
|
||||
"no_background_image_found": "未找到 背景 图片。",
|
||||
@@ -341,7 +341,6 @@
|
||||
"save": "保存",
|
||||
"save_changes": "保存 更改",
|
||||
"saving": "保存",
|
||||
"scheduled": "计划好的",
|
||||
"search": "搜索",
|
||||
"security": "安全",
|
||||
"segment": "细分",
|
||||
@@ -381,7 +380,6 @@
|
||||
"survey_live": "调查 运行中",
|
||||
"survey_not_found": "调查 未找到",
|
||||
"survey_paused": "调查 暂停。",
|
||||
"survey_scheduled": "调查 计划好的",
|
||||
"survey_type": "调查 类型",
|
||||
"surveys": "调查",
|
||||
"switch_to": "切换到 {environment}",
|
||||
@@ -762,45 +760,21 @@
|
||||
"unable_to_delete_api_key": "无法删除 API Key"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "这是你的 Formbricks 后台的 URL 。",
|
||||
"app_connection": "应用程序 连接",
|
||||
"app_connection_description": "连接 您 的 应用 与 Formbricks。",
|
||||
"cache_update_delay_description": "当 你 对 调查 、 联系人 、 操作 或 其他 数据 进行 更新 时 , 可能 需要 最多 5 分钟 更改 才能 显示 在 你 本地 运行 Formbricks SDK 的 应用程序 中 。 这个 延迟 是 由于 我们 当前 缓存 系统 的 限制 。 我们 正在 积极 重新设计 缓存 并 将 在 Formbricks 4.0 中 发布 修复 。",
|
||||
"cache_update_delay_title": "更改 将 在 5 分钟 后 由于 缓存 而 显示",
|
||||
"check_out_the_docs": "查看 文档。",
|
||||
"dive_into_the_docs": "深入 文档。",
|
||||
"does_your_widget_work": "您的 widget 工作吗?",
|
||||
"environment_id": "你的 环境 ID",
|
||||
"environment_id_description": "这个 id 独特地 标识 这个 Formbricks 环境。",
|
||||
"environment_id_description_with_environment_id": "用于识别正确环境: {environmentId} 是你的。",
|
||||
"formbricks_sdk": "Formbricks SDK",
|
||||
"formbricks_sdk_connected": "Formbricks SDK 已连接",
|
||||
"formbricks_sdk_not_connected": "Formbricks SDK 尚未连接。",
|
||||
"formbricks_sdk_not_connected_description": "连接 您 的 网站 或 应用 与 Formbricks",
|
||||
"have_a_problem": "有问题?",
|
||||
"how_to_setup": "如何设置",
|
||||
"how_to_setup_description": "遵循这些步骤在你的应用中设置 Formbricks 小部件。",
|
||||
"identifying_your_users": "识别 您 的 用户",
|
||||
"if_you_are_planning_to": "如果你 正在计划",
|
||||
"insert_this_code_into_the": "将此代码插入到",
|
||||
"need_a_more_detailed_setup_guide_for": "需要 更详细 的 设置 指南 吗",
|
||||
"not_working": "不好用?",
|
||||
"open_an_issue_on_github": "在 GitHub 上 提交 问题。",
|
||||
"open_the_browser_console_to_see_the_logs": "打开 浏览器 控制台 查看 日志。",
|
||||
"receiving_data": "接收 数据 \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "重新检查",
|
||||
"scroll_to_the_top": "滚动 到 顶部!",
|
||||
"step_1": "步骤 1: 使用 pnpm 、npm 或 yarn 安装",
|
||||
"step_2": "步骤 2: 初始化小组件",
|
||||
"step_2_description": "在你的组件 (例如 App.tsx) 中 导入 Formbricks 并初始化 小组件 :",
|
||||
"step_3": "步骤 3: 调试 模式",
|
||||
"switch_on_the_debug_mode_by_appending": "通过附加 启用 调试 模式",
|
||||
"tag_of_your_app": "您的 应用 标签",
|
||||
"to_the_url_where_you_load_the": "到您加载的 URL ",
|
||||
"want_to_learn_how_to_add_user_attributes": "想 学习 如何 添加 用户 属性、 自定义 事件 等等 吗?",
|
||||
"you_are_done": "完成了 \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "您 可以 设置 用户 ID 通过 ",
|
||||
"your_app_now_communicates_with_formbricks": "您的 app 现在可与 Formbricks 通信 - 自动发送 events 和加载 surveys!"
|
||||
"setup_alert_description": "按照 此 步骤教程 在 5 分钟 以内 连接 你的 应用 或 网站。",
|
||||
"setup_alert_title": "如何 连接"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "这是 您 唯一的 项目,不可 删除。请 先 创建一个新的 项目。",
|
||||
@@ -1253,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "自动 关闭 调查 后",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "自动 关闭 调查 在 达到 一定数量 的 回应 后",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "用户未在一定秒数内应答时 自动关闭 问卷",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "自动在 每天 开始时 (UTC) 关闭 调查",
|
||||
"automatically_mark_the_survey_as_complete_after": "自动 标记 调查 为 完成 在",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "自动在 每天 开始时 (UTC) 发布 调查",
|
||||
"back_button_label": "\"返回\" 按钮标签",
|
||||
"background_styling": "背景 样式",
|
||||
"brand_color": "品牌 颜色",
|
||||
@@ -1303,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "选择 触发 调查 的 动作 。",
|
||||
"choose_where_to_run_the_survey": "选择 调查 运行 的 位置 。",
|
||||
"city": "城市",
|
||||
"close_survey_on_date": "在日期关闭 调查",
|
||||
"close_survey_on_response_limit": "在响应限制时关闭 调查",
|
||||
"color": "颜色",
|
||||
"column_used_in_logic_error": "\"这个 列 在 问题 {questionIndex} 的 逻辑 中 使用。请 先 从 逻辑 中 删除 它。\"",
|
||||
@@ -1511,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "重定向感谢卡",
|
||||
"redirect_to_url": "重定向到 URL",
|
||||
"redirect_to_url_not_available_on_free_plan": "重 定 向 到 URL 不 可 用 于 免 费 计 划",
|
||||
"release_survey_on_date": "在日期发布 调查",
|
||||
"remove_description": "移除 描述",
|
||||
"remove_translations": "移除 翻译",
|
||||
"require_answer": "需要回答",
|
||||
@@ -1855,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "调查 删除 成功",
|
||||
"survey_duplicated_successfully": "调查成功复制。",
|
||||
"survey_duplication_error": "无法复制 调查。",
|
||||
"survey_status_tooltip": "要更新 调查 状态,更新 调查回答 选项中的计划和关闭 设置。",
|
||||
"templates": {
|
||||
"all_channels": "所有 渠道",
|
||||
"all_industries": "所有 行业",
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
"multiple_languages": "多種語言",
|
||||
"name": "名稱",
|
||||
"new": "新增",
|
||||
"new_survey": "新增問卷",
|
||||
"new_version_available": "Formbricks '{'version'}' 已推出。立即升級!",
|
||||
"next": "下一步",
|
||||
"no_background_image_found": "找不到背景圖片。",
|
||||
@@ -342,7 +341,6 @@
|
||||
"save": "儲存",
|
||||
"save_changes": "儲存變更",
|
||||
"saving": "儲存",
|
||||
"scheduled": "已排程",
|
||||
"search": "搜尋",
|
||||
"security": "安全性",
|
||||
"segment": "區隔",
|
||||
@@ -382,7 +380,6 @@
|
||||
"survey_live": "問卷已上線",
|
||||
"survey_not_found": "找不到問卷",
|
||||
"survey_paused": "問卷已暫停。",
|
||||
"survey_scheduled": "問卷已排程。",
|
||||
"survey_type": "問卷類型",
|
||||
"surveys": "問卷",
|
||||
"switch_to": "切換至 '{'environment'}'",
|
||||
@@ -763,47 +760,21 @@
|
||||
"unable_to_delete_api_key": "無法刪除 API 金鑰"
|
||||
},
|
||||
"app-connection": {
|
||||
"api_host_description": "這是您 Formbricks 後端的網址。",
|
||||
"app_connection": "應用程式連線",
|
||||
"app_connection_description": "將您的應用程式連線至 Formbricks。",
|
||||
"cache_update_delay_description": "當您對調查、聯絡人、操作或其他資料進行更新時,可能需要長達 5 分鐘這些變更才能顯示在執行 Formbricks SDK 的本地應用程式中。此延遲是因我們目前快取系統的限制。我們正積極重新設計快取,並將在 Formbricks 4.0 中發佈修補程式。",
|
||||
"cache_update_delay_title": "更改將於 5 分鐘後因快取而反映",
|
||||
"check_out_the_docs": "查看文件。",
|
||||
"dive_into_the_docs": "深入瞭解文件。",
|
||||
"does_your_widget_work": "您的小工具運作嗎?",
|
||||
"environment_id": "您的 EnvironmentId",
|
||||
"environment_id_description": "此 ID 可唯一識別此 Formbricks 環境。",
|
||||
"environment_id_description_with_environment_id": "用於識別正確的環境:'{'environmentId'}' 是您的。",
|
||||
"formbricks_sdk": "Formbricks SDK",
|
||||
"formbricks_sdk_connected": "Formbricks SDK 已連線",
|
||||
"formbricks_sdk_not_connected": "Formbricks SDK 尚未連線。",
|
||||
"formbricks_sdk_not_connected_description": "將您的網站或應用程式與 Formbricks 連線",
|
||||
"have_a_problem": "有問題嗎?",
|
||||
"how_to_setup": "如何設定",
|
||||
"how_to_setup_description": "請按照這些步驟在您的應用程式中設定 Formbricks 小工具。",
|
||||
"identifying_your_users": "識別您的使用者",
|
||||
"if_you_are_planning_to": "如果您計劃",
|
||||
"insert_this_code_into_the": "將此程式碼插入",
|
||||
"need_a_more_detailed_setup_guide_for": "需要更詳細的設定指南,適用於",
|
||||
"not_working": "無法運作?",
|
||||
"open_an_issue_on_github": "在 GitHub 上開啟問題",
|
||||
"open_the_browser_console_to_see_the_logs": "開啟瀏覽器主控台以查看記錄。",
|
||||
"receiving_data": "正在接收資料 \uD83D\uDC83\uD83D\uDD7A",
|
||||
"recheck": "重新檢查",
|
||||
"scroll_to_the_top": "捲動至頂端!",
|
||||
"setup_alert_description": "遵循 此 分步 教程 ,在 5 分鐘 內 將您的應用程式 或 網站 連線 。",
|
||||
"setup_alert_title": "如何 連線",
|
||||
"step_1": "步驟 1:使用 pnpm、npm 或 yarn 安裝",
|
||||
"step_2": "步驟 2:初始化小工具",
|
||||
"step_2_description": "匯入 Formbricks 並在您的元件中初始化小工具(例如,App.tsx):",
|
||||
"step_3": "步驟 3:偵錯模式",
|
||||
"switch_on_the_debug_mode_by_appending": "藉由附加以下項目開啟偵錯模式",
|
||||
"tag_of_your_app": "您應用程式的標籤",
|
||||
"to_the_url_where_you_load_the": "到您載入",
|
||||
"want_to_learn_how_to_add_user_attributes": "想瞭解如何新增使用者屬性、自訂事件等嗎?",
|
||||
"you_are_done": "您已完成 \uD83C\uDF89",
|
||||
"you_can_set_the_user_id_with": "您可以使用 user id 設定",
|
||||
"your_app_now_communicates_with_formbricks": "您的應用程式現在可與 Formbricks 通訊 - 自動傳送事件和載入問卷!"
|
||||
"setup_alert_title": "如何 連線"
|
||||
},
|
||||
"general": {
|
||||
"cannot_delete_only_project": "這是您唯一的專案,無法刪除。請先建立新專案。",
|
||||
@@ -1256,9 +1227,7 @@
|
||||
"automatically_close_survey_after": "在指定時間自動關閉問卷",
|
||||
"automatically_close_the_survey_after_a_certain_number_of_responses": "在收到一定數量的回覆後自動關閉問卷。",
|
||||
"automatically_close_the_survey_if_the_user_does_not_respond_after_certain_number_of_seconds": "如果用戶在特定秒數後未回應,則自動關閉問卷。",
|
||||
"automatically_closes_the_survey_at_the_beginning_of_the_day_utc": "在指定日期(UTC時間)自動關閉問卷。",
|
||||
"automatically_mark_the_survey_as_complete_after": "在指定時間後自動將問卷標記為完成",
|
||||
"automatically_release_the_survey_at_the_beginning_of_the_day_utc": "在指定日期(UTC時間)自動發佈問卷。",
|
||||
"back_button_label": "「返回」按鈕標籤",
|
||||
"background_styling": "背景樣式設定",
|
||||
"brand_color": "品牌顏色",
|
||||
@@ -1306,7 +1275,6 @@
|
||||
"choose_the_actions_which_trigger_the_survey": "選擇觸發問卷的操作。",
|
||||
"choose_where_to_run_the_survey": "選擇在哪裡執行問卷。",
|
||||
"city": "城市",
|
||||
"close_survey_on_date": "在指定日期關閉問卷",
|
||||
"close_survey_on_response_limit": "在回應次數上限關閉問卷",
|
||||
"color": "顏色",
|
||||
"column_used_in_logic_error": "此 column 用於問題 '{'questionIndex'}' 的邏輯中。請先從邏輯中移除。",
|
||||
@@ -1514,7 +1482,6 @@
|
||||
"redirect_thank_you_card": "重新導向感謝卡片",
|
||||
"redirect_to_url": "重新導向至網址",
|
||||
"redirect_to_url_not_available_on_free_plan": "重新導向至網址在免費方案中不可用",
|
||||
"release_survey_on_date": "在指定日期發佈問卷",
|
||||
"remove_description": "移除描述",
|
||||
"remove_translations": "移除翻譯",
|
||||
"require_answer": "要求回答",
|
||||
@@ -1858,7 +1825,6 @@
|
||||
"survey_deleted_successfully": "問卷已成功刪除!",
|
||||
"survey_duplicated_successfully": "問卷已成功複製。",
|
||||
"survey_duplication_error": "無法複製問卷。",
|
||||
"survey_status_tooltip": "若要更新問卷狀態,請更新問卷回應選項中的排程和關閉設定。",
|
||||
"templates": {
|
||||
"all_channels": "所有管道",
|
||||
"all_industries": "所有產業",
|
||||
|
||||
10
packages/android/.gitignore
vendored
10
packages/android/.gitignore
vendored
@@ -1,10 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Formbricks GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,53 +0,0 @@
|
||||
# Setup
|
||||
|
||||
To run the project, open the `android` folder in **Android Studio**. The project contains two modules:
|
||||
|
||||
- **app**: A demo application to exercise the SDK.
|
||||
- **formbricksSDK**: The SDK package.
|
||||
|
||||
Before launching the app, update the mandatory variables in `MainActivity`:
|
||||
|
||||
```kotlin
|
||||
val config = FormbricksConfig.Builder("[API_HOST]", "[ENVIRONMENT_ID]")
|
||||
.setLoggingEnabled(true)
|
||||
.setFragmentManager(supportFragmentManager)
|
||||
```
|
||||
|
||||
Once these values are properly set, the demo app can be launched.
|
||||
The app consists of a single view, `FormbricksDemo`. It is a very simple Jetpack Compose view with a single button.
|
||||
The button's action should be updated according to the survey actions:
|
||||
|
||||
```kotlin
|
||||
Formbricks.track("click_demo_button")
|
||||
```
|
||||
|
||||
Replace `"click_demo_button"` with the desired action.
|
||||
|
||||
---
|
||||
|
||||
# Documentation
|
||||
|
||||
You can generate developer documentation for the SDK using **Dokka**.
|
||||
To do this, navigate to the `android` folder in a `Terminal` window (or open it in Android Studio) and run:
|
||||
|
||||
```sh
|
||||
./gradlew dokkaHtml
|
||||
```
|
||||
|
||||
This will generate the developer documentation in the `formbricksSDK/build/dokka/html` folder.
|
||||
|
||||
---
|
||||
|
||||
## Unit Tests
|
||||
|
||||
The SDK includes a unit test to verify the Manager's functionality. To run it:
|
||||
|
||||
1. Open the `FormbricksInstrumentedTest` file.
|
||||
2. Since the SDK requires a `Context` for initialization, it uses an instrumented test.
|
||||
3. Click the double arrow next to `class FormbricksInstrumentedTest` to execute the tests.
|
||||
|
||||
To generate a coverage report, navigate to the `android` folder in a `Terminal` window (or open it in Android Studio) and run:
|
||||
|
||||
```sh
|
||||
./gradlew createDebugCoverageReport
|
||||
```
|
||||
1
packages/android/app/.gitignore
vendored
1
packages/android/app/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,54 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
kotlin("kapt")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.formbricks.demo"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.formbricks.demo"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":formbricksSDK"))
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
}
|
||||
26
packages/android/app/proguard-rules.pro
vendored
26
packages/android/app/proguard-rules.pro
vendored
@@ -1,26 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-dontwarn androidx.databinding.**
|
||||
-keep class androidx.databinding.** { *; }
|
||||
-keep class * extends androidx.databinding.DataBinderMapper { *; }
|
||||
-keep class com.formbricks.formbrickssdk.DataBinderMapperImpl { *; }
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.formbricks.demo
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.formbricks.demo", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Demo"
|
||||
tools:targetApi="31" >
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:exported="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,91 +0,0 @@
|
||||
package com.formbricks.demo
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Button
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
import com.formbricks.formbrickssdk.FormbricksCallback
|
||||
import com.formbricks.formbrickssdk.helper.FormbricksConfig
|
||||
import com.formbricks.formbrickssdk.model.enums.SuccessType
|
||||
import java.util.UUID
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
Formbricks.callback = object: FormbricksCallback {
|
||||
override fun onSurveyStarted() {
|
||||
Log.d("FormbricksCallback", "onSurveyStarted")
|
||||
}
|
||||
|
||||
override fun onSurveyFinished() {
|
||||
Log.d("FormbricksCallback", "onSurveyFinished")
|
||||
}
|
||||
|
||||
override fun onSurveyClosed() {
|
||||
Log.d("FormbricksCallback", "onSurveyClosed")
|
||||
}
|
||||
|
||||
override fun onPageCommitVisible() {
|
||||
Log.d("FormbricksCallback", "onPageCommitVisible")
|
||||
}
|
||||
|
||||
override fun onError(error: Exception) {
|
||||
Log.d("FormbricksCallback", "onError from the CB: ${error.localizedMessage}")
|
||||
}
|
||||
|
||||
override fun onSuccess(successType: SuccessType) {
|
||||
Log.d("FormbricksCallback", "onSuccess: ${successType.name}")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val config = FormbricksConfig.Builder("[appUrl]","[environmentId]")
|
||||
.setLoggingEnabled(true)
|
||||
.setFragmentManager(supportFragmentManager)
|
||||
|
||||
Formbricks.setup(this, config.build())
|
||||
|
||||
setContentView(R.layout.activity_main)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
insets
|
||||
}
|
||||
|
||||
val button = findViewById<Button>(R.id.button)
|
||||
button.setOnClickListener {
|
||||
Formbricks.track("click_demo_button")
|
||||
}
|
||||
|
||||
val setUserIdButton = findViewById<Button>(R.id.setUserId)
|
||||
setUserIdButton.setOnClickListener {
|
||||
Formbricks.setUserId(UUID.randomUUID().toString())
|
||||
}
|
||||
|
||||
val setAttributeButton = findViewById<Button>(R.id.setAttribute)
|
||||
setAttributeButton.setOnClickListener {
|
||||
Formbricks.setAttribute("test@web.com", "email")
|
||||
}
|
||||
|
||||
val setAttributesButton = findViewById<Button>(R.id.setAttributes)
|
||||
setAttributesButton.setOnClickListener {
|
||||
Formbricks.setAttributes(mapOf(Pair("attr1", "val1"), Pair("attr2", "val2")))
|
||||
}
|
||||
|
||||
val setLanguageButton = findViewById<Button>(R.id.setLanguage)
|
||||
setLanguageButton.setOnClickListener {
|
||||
Formbricks.setLanguage("vi")
|
||||
}
|
||||
|
||||
val logoutButton = findViewById<Button>(R.id.logout)
|
||||
logoutButton.setOnClickListener {
|
||||
Formbricks.logout()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -1,30 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -1,93 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Track Action"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.495"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.24" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setUserId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="161dp"
|
||||
android:layout_marginTop="27dp"
|
||||
android:layout_marginEnd="154dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="setUserId"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toTopOf="@+id/setLanguage"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/button"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setLanguage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="161dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="128dp"
|
||||
android:layout_marginBottom="11dp"
|
||||
android:text="setLanguage"
|
||||
app:layout_constraintBottom_toTopOf="@+id/setAttribute"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/setUserId" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setAttribute"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="161dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="128dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:text="setAttribute"
|
||||
app:layout_constraintBottom_toTopOf="@+id/setAttributes"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/setLanguage" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setAttributes"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="161dp"
|
||||
android:layout_marginTop="1dp"
|
||||
android:layout_marginEnd="120dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="setAttributes"
|
||||
app:layout_constraintBottom_toTopOf="@+id/logout"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/setAttribute" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/logout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="177dp"
|
||||
android:layout_marginTop="9dp"
|
||||
android:layout_marginEnd="146dp"
|
||||
android:layout_marginBottom="199dp"
|
||||
android:text="logout"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/setAttributes" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 982 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -1,3 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">Demo</string>
|
||||
</resources>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Demo" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- No specific include/exclude -->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">192.168.29.120</domain>
|
||||
<domain includeSubdomains="true">localhost</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.formbricks.demo
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.android.library) apply false
|
||||
}
|
||||
1
packages/android/formbricksSDK/.gitignore
vendored
1
packages/android/formbricksSDK/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
/build
|
||||
@@ -1,97 +0,0 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
kotlin("plugin.serialization") version "2.1.0"
|
||||
id("org.jetbrains.dokka") version "1.9.10"
|
||||
id("jacoco")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.formbricks.formbrickssdk"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
enableAndroidTestCoverage = true
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "META-INF/library_release.kotlin_module"
|
||||
excludes += "classes.dex"
|
||||
excludes += "**.**"
|
||||
pickFirsts += "**/DataBinderMapperImpl.java"
|
||||
pickFirsts += "**/DataBinderMapperImpl.class"
|
||||
pickFirsts += "**/formbrickssdk/DataBinderMapperImpl.java"
|
||||
pickFirsts += "**/formbrickssdk/DataBinderMapperImpl.class"
|
||||
}
|
||||
}
|
||||
viewBinding {
|
||||
enable = true
|
||||
}
|
||||
dataBinding {
|
||||
enable = true
|
||||
}
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
viewBinding = true
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
extensions.configure<JacocoTaskExtension> {
|
||||
isIncludeNoLocationClasses = true
|
||||
excludes = listOf(
|
||||
"jdk.internal.*",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.annotation)
|
||||
implementation(libs.androidx.appcompat)
|
||||
|
||||
implementation(libs.gson)
|
||||
implementation(libs.retrofit)
|
||||
implementation(libs.retrofit.converter.gson)
|
||||
implementation(libs.retrofit.converter.scalars)
|
||||
implementation(libs.okhttp3.logging.interceptor)
|
||||
|
||||
implementation(libs.material)
|
||||
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.androidx.legacy.support.v4)
|
||||
implementation(libs.androidx.lifecycle.livedata.ktx)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
||||
implementation(libs.androidx.fragment.ktx)
|
||||
implementation(libs.androidx.databinding.common)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(project(":formbricksSDK"))
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
-keep class com.formbricks.formbrickssdk.DataBinderMapperImpl { *; }
|
||||
-keep class com.formbricks.formbrickssdk.Formbricks { *; }
|
||||
-keep class com.formbricks.formbrickssdk.helper.FormbricksConfig { *; }
|
||||
-keep class com.formbricks.formbrickssdk.model.error.SDKError { *; }
|
||||
-keep interface com.formbricks.formbrickssdk.FormbricksCallback { *; }
|
||||
@@ -1,38 +0,0 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
-keeppackagenames com.formbricks.**
|
||||
|
||||
-keep class com.formbricks.** { *; }
|
||||
|
||||
-keepclassmembers,allowobfuscation class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable,Exceptions,InnerClasses,Signature,Deprecated,*Annotation*,EnclosingMethod
|
||||
|
||||
# add all known-to-be-safely-shrinkable classes to the beginning of line below
|
||||
-keep class !androidx.legacy.**,!com.google.android.**,!androidx.** { *; }
|
||||
-keep class android.support.v4.app.** { *; }
|
||||
|
||||
# Retrofit
|
||||
-dontwarn okio.**
|
||||
-keep class com.squareup.okhttp.** { *; }
|
||||
-keep interface com.squareup.okhttp.** { *; }
|
||||
-keep class retrofit.** { *; }
|
||||
-dontwarn com.squareup.okhttp.**
|
||||
|
||||
-keep class retrofit.** { *; }
|
||||
-keepclasseswithmembers class * {
|
||||
@retrofit.http.* <methods>;
|
||||
}
|
||||
|
||||
-keep class com.formbricks.formbrickssdk.DataBinderMapperImpl { *; }
|
||||
-keep class com.formbricks.formbrickssdk.Formbricks { *; }
|
||||
-keep class com.formbricks.formbrickssdk.helper.FormbricksConfig { *; }
|
||||
-keep class com.formbricks.formbrickssdk.model.error.SDKError { *; }
|
||||
-keep interface com.formbricks.formbrickssdk.FormbricksCallback { *; }
|
||||
@@ -1,167 +0,0 @@
|
||||
package com.formbricks.formbrickssdk
|
||||
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.formbricks.formbrickssdk.api.FormbricksApi
|
||||
import com.formbricks.formbrickssdk.helper.FormbricksConfig
|
||||
import com.formbricks.formbrickssdk.manager.SurveyManager
|
||||
import com.formbricks.formbrickssdk.manager.UserManager
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class FormbricksInstrumentedTest {
|
||||
|
||||
private val environmentId = "environmentId"
|
||||
private val appUrl = "http://appUrl"
|
||||
private val userId = "6CCCE716-6783-4D0F-8344-9C7DFA43D8F7"
|
||||
private val surveyID = "cm6ovw6j7000gsf0kduf4oo4i"
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
Formbricks.applicationContext = appContext
|
||||
UserManager.logout()
|
||||
SurveyManager.environmentDataHolder = null
|
||||
FormbricksApi.service = MockFormbricksApiService()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFormbricks() {
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.formbricks.formbrickssdk.test", appContext.packageName)
|
||||
|
||||
// Everything should be in the default state
|
||||
assertFalse(Formbricks.isInitialized)
|
||||
assertEquals(0, SurveyManager.filteredSurveys.size)
|
||||
assertNull(SurveyManager.environmentDataHolder)
|
||||
assertNull(UserManager.userId)
|
||||
assertEquals("default", Formbricks.language)
|
||||
|
||||
// Use methods before init should have no effect
|
||||
Formbricks.setUserId("userId")
|
||||
Formbricks.setLanguage("de")
|
||||
Formbricks.setAttributes(mapOf("testA" to "testB"))
|
||||
Formbricks.setAttribute("test", "testKey")
|
||||
assertNull(UserManager.userId)
|
||||
assertEquals("default", Formbricks.language)
|
||||
Formbricks.track("click_demo_button")
|
||||
waitForSeconds(1)
|
||||
assertFalse(SurveyManager.isShowingSurvey)
|
||||
Formbricks.logout()
|
||||
Formbricks.setFragmentManager(MockFragmentManager())
|
||||
Formbricks.setLanguage("")
|
||||
|
||||
// Call the setup and initialize the SDK
|
||||
Formbricks.setup(appContext, FormbricksConfig.Builder(appUrl, environmentId).setLoggingEnabled(true).build())
|
||||
waitForSeconds(1)
|
||||
|
||||
// Should be ignored, becuase we don't have user ID yet
|
||||
Formbricks.setAttributes(mapOf("testA" to "testB"))
|
||||
Formbricks.setAttribute("test", "testKey")
|
||||
assertNull(UserManager.userId)
|
||||
|
||||
// Verify the base variables are set properly
|
||||
assertTrue(Formbricks.isInitialized)
|
||||
assertEquals(appUrl, Formbricks.appUrl)
|
||||
assertEquals(environmentId, Formbricks.environmentId)
|
||||
|
||||
// User manager default state. There is no user yet.
|
||||
assertEquals(UserManager.displays?.count(), 0)
|
||||
assertEquals(UserManager.responses?.count(), 0)
|
||||
assertEquals(UserManager.segments?.count(), 0)
|
||||
|
||||
// Check error state handling
|
||||
(FormbricksApi.service as MockFormbricksApiService).isErrorResponseNeeded = true
|
||||
assertFalse(SurveyManager.hasApiError)
|
||||
SurveyManager.refreshEnvironmentIfNeeded(true)
|
||||
waitForSeconds(1)
|
||||
assertTrue(SurveyManager.hasApiError)
|
||||
(FormbricksApi.service as MockFormbricksApiService).isErrorResponseNeeded = false
|
||||
|
||||
// Authenticate the user
|
||||
Formbricks.setUserId(userId)
|
||||
waitForSeconds(2)
|
||||
assertEquals(userId, UserManager.userId)
|
||||
assertNotNull(UserManager.syncTimer)
|
||||
|
||||
// The environment should be fetched already
|
||||
assertNotNull(SurveyManager.environmentDataHolder)
|
||||
|
||||
// Check if the filter method works properly
|
||||
assertEquals(1, SurveyManager.filteredSurveys.size)
|
||||
assertFalse(SurveyManager.isShowingSurvey)
|
||||
|
||||
// Track an unknown event, shouldn't show the survey
|
||||
Formbricks.track("unknown_event")
|
||||
assertFalse(SurveyManager.isShowingSurvey)
|
||||
|
||||
// Track a known event, thus, the survey should be shown.
|
||||
SurveyManager.isShowingSurvey = false
|
||||
Formbricks.track("click_demo_button")
|
||||
waitForSeconds(1)
|
||||
assertTrue(SurveyManager.isShowingSurvey)
|
||||
|
||||
// Validate display and response
|
||||
SurveyManager.onNewDisplay(surveyID)
|
||||
SurveyManager.postResponse(surveyID)
|
||||
assertEquals(1, UserManager.responses?.size)
|
||||
assertEquals(1, UserManager.displays?.size)
|
||||
|
||||
// Track a valid event, but the survey should not shown, because we already gave a response.
|
||||
SurveyManager.isShowingSurvey = false
|
||||
Formbricks.track("click_demo_button")
|
||||
waitForSeconds(1)
|
||||
assertFalse(SurveyManager.isShowingSurvey)
|
||||
|
||||
// Validate logout
|
||||
assertNotNull(UserManager.userId)
|
||||
assertNotNull(UserManager.lastDisplayedAt)
|
||||
assertNotEquals(UserManager.displays?.count(), 0)
|
||||
assertNotEquals(UserManager.responses?.count(), 0)
|
||||
assertNotEquals(UserManager.segments?.count(), 0)
|
||||
assertNotNull(UserManager.expiresAt)
|
||||
Formbricks.logout()
|
||||
assertNull(UserManager.userId)
|
||||
assertNull(UserManager.lastDisplayedAt)
|
||||
assertNull(UserManager.displays)
|
||||
assertEquals(UserManager.responses?.count(), 0)
|
||||
assertEquals(UserManager.segments?.count(), 0)
|
||||
assertNull(UserManager.expiresAt)
|
||||
|
||||
// Setting the language
|
||||
assertEquals("default", Formbricks.language)
|
||||
Formbricks.setLanguage("de")
|
||||
assertEquals("de", Formbricks.language)
|
||||
|
||||
// Clear the responses
|
||||
Formbricks.logout()
|
||||
SurveyManager.filterSurveys()
|
||||
|
||||
Formbricks.track("click_demo_button")
|
||||
waitForSeconds(1)
|
||||
assertTrue(SurveyManager.isShowingSurvey)
|
||||
}
|
||||
|
||||
private fun waitForSeconds(seconds: Long) {
|
||||
val latch = CountDownLatch(1)
|
||||
latch.await(seconds, TimeUnit.SECONDS)
|
||||
}
|
||||
}
|
||||
|
||||
class MockFragmentManager : FragmentManager()
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.formbricks.formbrickssdk
|
||||
|
||||
import com.formbricks.formbrickssdk.model.environment.EnvironmentDataHolder
|
||||
import com.formbricks.formbrickssdk.model.environment.EnvironmentResponse
|
||||
import com.formbricks.formbrickssdk.model.user.PostUserBody
|
||||
import com.formbricks.formbrickssdk.model.user.UserResponse
|
||||
import com.formbricks.formbrickssdk.network.FormbricksApiService
|
||||
import com.google.gson.Gson
|
||||
|
||||
class MockFormbricksApiService: FormbricksApiService() {
|
||||
private val gson = Gson()
|
||||
private val environmentJson = MockFormbricksApiService::class.java.getResource("/Environment.json")!!.readText()
|
||||
private val userJson = MockFormbricksApiService::class.java.getResource("/User.json")!!.readText()
|
||||
private val environment = gson.fromJson(environmentJson, EnvironmentResponse::class.java)
|
||||
private val user = gson.fromJson(userJson, UserResponse::class.java)
|
||||
var isErrorResponseNeeded = false
|
||||
|
||||
override fun getEnvironmentStateObject(environmentId: String): Result<EnvironmentDataHolder> {
|
||||
return if (isErrorResponseNeeded) {
|
||||
Result.failure(RuntimeException())
|
||||
} else {
|
||||
Result.success(EnvironmentDataHolder(environment.data, mapOf()))
|
||||
}
|
||||
}
|
||||
|
||||
override fun postUser(environmentId: String, body: PostUserBody): Result<UserResponse> {
|
||||
return if (isErrorResponseNeeded) {
|
||||
Result.failure(RuntimeException())
|
||||
} else {
|
||||
Result.success(user)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,372 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"data": {
|
||||
"actionClasses": [
|
||||
{
|
||||
"id": "cm6ow6hht000isf0k39hbmi5f",
|
||||
"key": "click_demo_button",
|
||||
"name": "Clicked the demo button",
|
||||
"noCodeConfig": null,
|
||||
"type": "code"
|
||||
}
|
||||
],
|
||||
"project": {
|
||||
"clickOutsideClose": true,
|
||||
"darkOverlay": false,
|
||||
"id": "cm6ovvfnv0003sf0k7zi8r3ac",
|
||||
"inAppSurveyBranding": true,
|
||||
"placement": "bottomRight",
|
||||
"recontactDays": 7,
|
||||
"styling": {
|
||||
"allowStyleOverwrite": true,
|
||||
"brandColor": {
|
||||
"light": "#126dec"
|
||||
}
|
||||
}
|
||||
},
|
||||
"surveys": [
|
||||
{
|
||||
"autoClose": null,
|
||||
"delay": 0,
|
||||
"displayLimit": 1,
|
||||
"displayOption": "displayMultiple",
|
||||
"displayPercentage": 100,
|
||||
"endings": [
|
||||
{
|
||||
"buttonLabel": {
|
||||
"default": "Create your own Survey\u200c\u200c\u200d\u200d\u200c\u200c\u200d\u200c\u200c\u200c\u200c\u200d\u200d\u200d\u200c\u200c\u200d\u200c\u200c\u200c\u200d\u200d\u200c\u200c\u200c\u200c\u200c\u200c\u200c\u200c\u200c\u200d\u200c\u200d\u200c\u200c"
|
||||
},
|
||||
"buttonLink": "https://formbricks.com",
|
||||
"headline": {
|
||||
"default": "Thank you!\u200c\u200c\u200d\u200d\u200c\u200c\u200d\u200c\u200c\u200c\u200c\u200d\u200d\u200d\u200c\u200c\u200c\u200c\u200c\u200c\u200d\u200d\u200d\u200c\u200c\u200c\u200c\u200c\u200c\u200c\u200c\u200d\u200c\u200d\u200c\u200c"
|
||||
},
|
||||
"id": "ik26bumalvsg2hs0sz7kd0dh",
|
||||
"subheader": {
|
||||
"default": "We appreciate your feedback.\u200c\u200c\u200d\u200d\u200c\u200c\u200d\u200c\u200c\u200c\u200c\u200d\u200d\u200d\u200c\u200c\u200c\u200c\u200c\u200c\u200d\u200d\u200d\u200c\u200c\u200d\u200c\u200c\u200c\u200c\u200c\u200d\u200c\u200d\u200c\u200c"
|
||||
},
|
||||
"type": "endScreen"
|
||||
}
|
||||
],
|
||||
"hiddenFields": {
|
||||
"enabled": true,
|
||||
"fieldIds": []
|
||||
},
|
||||
"id": "cm6ovw6j7000gsf0kduf4oo4i",
|
||||
"isBackButtonHidden": false,
|
||||
"languages": [],
|
||||
"name": "Start from scratch",
|
||||
"projectOverwrites": null,
|
||||
"questions": [
|
||||
{
|
||||
"allowMultipleFiles": true,
|
||||
"allowedFileExtensions": ["jpeg", "jpg", "png"],
|
||||
"backButtonLabel": {
|
||||
"default": "Back"
|
||||
},
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"headline": {
|
||||
"default": "Upload some file"
|
||||
},
|
||||
"id": "bak70kjkubxq66eup7i6l4lp",
|
||||
"logic": [],
|
||||
"required": false,
|
||||
"subheader": {
|
||||
"default": "Yeeeaaahh"
|
||||
},
|
||||
"type": "fileUpload"
|
||||
},
|
||||
{
|
||||
"backButtonLabel": {
|
||||
"default": "Back\u200c\u200c\u200d\u200d\u200c\u200c\u200c\u200d\u200c\u200c\u200c\u200d\u200d\u200c\u200c\u200d\u200c\u200c\u200c\u200c\u200d\u200d\u200c\u200d\u200c\u200c\u200c\u200c\u200c\u200c\u200c\u200d\u200c\u200d\u200c\u200c"
|
||||
},
|
||||
"buttonExternal": true,
|
||||
"buttonLabel": {
|
||||
"default": "Book interview\u200c\u200c\u200d\u200d\u200c\u200c\u200c\u200d\u200c\u200c\u200c\u200d\u200d\u200c\u200d\u200c\u200c\u200c\u200c\u200c\u200d\u200d\u200c\u200d\u200c\u200d\u200c\u200c\u200c\u200c\u200c\u200d\u200c\u200d\u200c\u200c"
|
||||
},
|
||||
"buttonUrl": "https://telex.hu",
|
||||
"dismissButtonLabel": {
|
||||
"default": "Skip\u200c\u200c\u200d\u200d\u200c\u200c\u200c\u200d\u200c\u200c\u200c\u200d\u200d\u200c\u200d\u200c\u200c\u200c\u200c\u200c\u200d\u200d\u200c\u200d\u200d\u200c\u200c\u200c\u200c\u200c\u200c\u200d\u200c\u200d\u200c\u200c"
|
||||
},
|
||||
"headline": {
|
||||
"default": "Click something"
|
||||
},
|
||||
"html": {
|
||||
"default": "<p class=\"fb-editor-paragraph\"><br></p><p class=\"fb-editor-paragraph\"><br></p><p class=\"fb-editor-paragraph\" dir=\"ltr\"><span style=\"white-space: pre-wrap;\">yeah</span></p><p class=\"fb-editor-paragraph\"><br></p>"
|
||||
},
|
||||
"id": "zmjh52gfc92yo4ph2zru4nhn",
|
||||
"required": true,
|
||||
"type": "cta"
|
||||
},
|
||||
{
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"charLimit": {
|
||||
"enabled": false
|
||||
},
|
||||
"headline": {
|
||||
"default": "How are things going?"
|
||||
},
|
||||
"id": "ve69a3mrkqixhtom8y2mtd9q",
|
||||
"inputType": "text",
|
||||
"logic": [],
|
||||
"longAnswer": true,
|
||||
"placeholder": {
|
||||
"default": "This is a placeholder. Type your answer though.."
|
||||
},
|
||||
"required": false,
|
||||
"type": "openText"
|
||||
},
|
||||
{
|
||||
"backButtonLabel": {
|
||||
"default": "Back"
|
||||
},
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"headline": {
|
||||
"default": "Rate this question"
|
||||
},
|
||||
"id": "qo5khwchqa7g34qzdwb7p6kr",
|
||||
"isColorCodingEnabled": false,
|
||||
"lowerLabel": {
|
||||
"default": "Not good"
|
||||
},
|
||||
"range": 5,
|
||||
"required": true,
|
||||
"scale": "smiley",
|
||||
"type": "rating",
|
||||
"upperLabel": {
|
||||
"default": "Very good"
|
||||
}
|
||||
},
|
||||
{
|
||||
"backButtonLabel": {
|
||||
"default": "Back"
|
||||
},
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"choices": [
|
||||
{
|
||||
"id": "wr01x35k5spyaz5x1d7vsa8g",
|
||||
"label": {
|
||||
"default": "One"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "zvqn3rj26ph3krr1e4n8vn91",
|
||||
"label": {
|
||||
"default": "Kett\u0151"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "f4e2xuspnajsz8kk7ctrc7sn",
|
||||
"label": {
|
||||
"default": "Tres"
|
||||
}
|
||||
}
|
||||
],
|
||||
"headline": {
|
||||
"default": "Select something"
|
||||
},
|
||||
"id": "prdku49xb0gfpmipwc97scd4",
|
||||
"required": true,
|
||||
"shuffleOption": "none",
|
||||
"type": "multipleChoiceMulti"
|
||||
},
|
||||
{
|
||||
"backButtonLabel": {
|
||||
"default": "Back"
|
||||
},
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"format": "M-d-y",
|
||||
"headline": {
|
||||
"default": "Select a date please"
|
||||
},
|
||||
"id": "yjape17lgfe7v74gbxwadj04",
|
||||
"required": true,
|
||||
"type": "date"
|
||||
},
|
||||
{
|
||||
"addressLine1": {
|
||||
"placeholder": {
|
||||
"default": "Address Line 1"
|
||||
},
|
||||
"required": false,
|
||||
"show": true
|
||||
},
|
||||
"addressLine2": {
|
||||
"placeholder": {
|
||||
"default": "Address Line 2"
|
||||
},
|
||||
"required": false,
|
||||
"show": false
|
||||
},
|
||||
"backButtonLabel": {
|
||||
"default": "Back"
|
||||
},
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"city": {
|
||||
"placeholder": {
|
||||
"default": "City"
|
||||
},
|
||||
"required": false,
|
||||
"show": true
|
||||
},
|
||||
"country": {
|
||||
"placeholder": {
|
||||
"default": "Country"
|
||||
},
|
||||
"required": false,
|
||||
"show": true
|
||||
},
|
||||
"headline": {
|
||||
"default": "What is your address?"
|
||||
},
|
||||
"id": "z78kl00vherihw2oqv2loj2y",
|
||||
"required": false,
|
||||
"state": {
|
||||
"placeholder": {
|
||||
"default": "State"
|
||||
},
|
||||
"required": false,
|
||||
"show": true
|
||||
},
|
||||
"type": "address",
|
||||
"zip": {
|
||||
"placeholder": {
|
||||
"default": "Zip"
|
||||
},
|
||||
"required": false,
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"backButtonLabel": {
|
||||
"default": "Back"
|
||||
},
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"headline": {
|
||||
"default": "I dare you to check this!"
|
||||
},
|
||||
"html": {
|
||||
"default": "<p class=\"fb-editor-paragraph\"><br></p><p class=\"fb-editor-paragraph\" dir=\"ltr\"><span style=\"white-space: pre-wrap;\">I double dare you</span></p>"
|
||||
},
|
||||
"id": "qq30m24f14vhw356kqfzlrlr",
|
||||
"label": {
|
||||
"default": "Oookay"
|
||||
},
|
||||
"required": true,
|
||||
"type": "consent"
|
||||
},
|
||||
{
|
||||
"allowMulti": true,
|
||||
"backButtonLabel": {
|
||||
"default": "Back"
|
||||
},
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"choices": [
|
||||
{
|
||||
"id": "m8uxhdlq4yerpzfykb6mfpeb",
|
||||
"imageUrl": "http://localhost:3000/storage/cm6ovvfoc000asf0k39wbzc8s/public/Screenshot%25202021-10-18%2520at%252011.35.15--fid--e7d90f81-56fb-4ed7-ad64-3ec869bc96d6.png"
|
||||
},
|
||||
{
|
||||
"id": "ayv30brvesfxrl7dmj2kig4u",
|
||||
"imageUrl": "http://localhost:3000/storage/cm6ovvfoc000asf0k39wbzc8s/public/Screenshot%25202022-11-22%2520at%252014.37.51--fid--5e4edd58-62b0-4df8-a096-e8e59b1bf080.png"
|
||||
}
|
||||
],
|
||||
"headline": {
|
||||
"default": "Select a picture"
|
||||
},
|
||||
"id": "krehs9wlntwtjf9hlh8pp82w",
|
||||
"required": true,
|
||||
"type": "pictureSelection"
|
||||
}
|
||||
],
|
||||
"recontactDays": 0,
|
||||
"segment": {
|
||||
"createdAt": "2025-02-03T10:04:21.922Z",
|
||||
"description": null,
|
||||
"environmentId": "cm6ovvfoc000asf0k39wbzc8s",
|
||||
"filters": [],
|
||||
"id": "cm6ovw6jl000hsf0knn547w0y",
|
||||
"isPrivate": true,
|
||||
"surveys": ["cm6ovw6j7000gsf0kduf4oo4i"],
|
||||
"title": "cm6ovw6j7000gsf0kduf4oo4i",
|
||||
"updatedAt": "2025-02-03T10:04:21.922Z"
|
||||
},
|
||||
"showLanguageSwitch": null,
|
||||
"status": "inProgress",
|
||||
"styling": {
|
||||
"background": {
|
||||
"bg": "#fff",
|
||||
"bgType": "color"
|
||||
},
|
||||
"brandColor": {
|
||||
"light": "#60fa0d"
|
||||
},
|
||||
"cardArrangement": {
|
||||
"appSurveys": "straight",
|
||||
"linkSurveys": "straight"
|
||||
},
|
||||
"cardBackgroundColor": {
|
||||
"light": "#ffffff"
|
||||
},
|
||||
"cardBorderColor": {
|
||||
"light": "#f8fafc"
|
||||
},
|
||||
"inputBorderColor": {
|
||||
"light": "#090e14"
|
||||
},
|
||||
"inputColor": {
|
||||
"light": "#ffffff"
|
||||
},
|
||||
"isDarkModeEnabled": false,
|
||||
"isLogoHidden": false,
|
||||
"overwriteThemeStyling": true,
|
||||
"questionColor": {
|
||||
"light": "#3a09ff"
|
||||
},
|
||||
"roundness": 8
|
||||
},
|
||||
"triggers": [
|
||||
{
|
||||
"actionClass": {
|
||||
"name": "Clicked the demo button"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "app",
|
||||
"variables": [],
|
||||
"welcomeCard": {
|
||||
"buttonLabel": {
|
||||
"default": "Next"
|
||||
},
|
||||
"enabled": false,
|
||||
"fileUrl": "",
|
||||
"headline": {
|
||||
"default": "Welcome!"
|
||||
},
|
||||
"html": {
|
||||
"default": "<p class=\"fb-editor-paragraph\" dir=\"ltr\"><span style=\"white-space: pre-wrap;\">Thanks for providing your feedback - let's go!</span></p>"
|
||||
},
|
||||
"showResponseCount": false,
|
||||
"timeToFinish": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"expiresAt": "2035-03-06T10:33:38.647Z"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"state": {
|
||||
"data": {
|
||||
"contactId": "cm6ovw6jl000hsf0knn547xyz",
|
||||
"displays": [],
|
||||
"lastDisplayAt": null,
|
||||
"responses": [],
|
||||
"segments": ["cm6ovw6jl000hsf0knn547w0y"],
|
||||
"userId": "6CCCE716-6783-4D0F-8344-9C7DFA43D8F7"
|
||||
},
|
||||
"expiresAt": "2035-03-06T10:59:32.359Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="35" />
|
||||
</manifest>
|
||||
@@ -1,256 +0,0 @@
|
||||
package com.formbricks.formbrickssdk
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import androidx.annotation.Keep
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.formbricks.formbrickssdk.api.FormbricksApi
|
||||
import com.formbricks.formbrickssdk.helper.FormbricksConfig
|
||||
import com.formbricks.formbrickssdk.logger.Logger
|
||||
import com.formbricks.formbrickssdk.manager.SurveyManager
|
||||
import com.formbricks.formbrickssdk.manager.UserManager
|
||||
import com.formbricks.formbrickssdk.model.enums.SuccessType
|
||||
import com.formbricks.formbrickssdk.model.error.SDKError
|
||||
import com.formbricks.formbrickssdk.webview.FormbricksFragment
|
||||
import java.lang.RuntimeException
|
||||
|
||||
@Keep
|
||||
interface FormbricksCallback {
|
||||
fun onSurveyStarted()
|
||||
fun onSurveyFinished()
|
||||
fun onSurveyClosed()
|
||||
fun onPageCommitVisible()
|
||||
fun onError(error: Exception)
|
||||
fun onSuccess(successType: SuccessType)
|
||||
}
|
||||
|
||||
|
||||
@Keep
|
||||
object Formbricks {
|
||||
internal lateinit var applicationContext: Context
|
||||
|
||||
internal lateinit var environmentId: String
|
||||
internal lateinit var appUrl: String
|
||||
internal var language: String = "default"
|
||||
internal var loggingEnabled: Boolean = true
|
||||
private var fragmentManager: FragmentManager? = null
|
||||
internal var isInitialized = false
|
||||
|
||||
var callback: FormbricksCallback? = null
|
||||
|
||||
/**
|
||||
* Initializes the Formbricks SDK with the given [Context] config [FormbricksConfig].
|
||||
* This method is mandatory to be called, and should be only once per application lifecycle.
|
||||
* To show a survey, the SDK needs a [FragmentManager] instance.
|
||||
*
|
||||
* ```
|
||||
* class MainActivity : FragmentActivity() {
|
||||
*
|
||||
* override fun onCreate() {
|
||||
* super.onCreate()
|
||||
* val config = FormbricksConfig.Builder("http://localhost:3000","my_environment_id")
|
||||
* .setLoggingEnabled(true)
|
||||
* .setFragmentManager(supportFragmentManager)
|
||||
* .build())
|
||||
* Formbricks.setup(this, config.build())
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
fun setup(context: Context, config: FormbricksConfig, forceRefresh: Boolean = false) {
|
||||
if (isInitialized && !forceRefresh) {
|
||||
val error = SDKError.sdkIsAlreadyInitialized
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
applicationContext = context
|
||||
|
||||
appUrl = config.appUrl
|
||||
environmentId = config.environmentId
|
||||
loggingEnabled = config.loggingEnabled
|
||||
fragmentManager = config.fragmentManager
|
||||
|
||||
config.userId?.let { UserManager.set(it) }
|
||||
config.attributes?.let { UserManager.setAttributes(it) }
|
||||
config.attributes?.get("language")?.let { UserManager.setLanguage(it) }
|
||||
|
||||
FormbricksApi.initialize()
|
||||
SurveyManager.refreshEnvironmentIfNeeded(force = forceRefresh)
|
||||
UserManager.syncUserStateIfNeeded()
|
||||
|
||||
isInitialized = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user id for the current user with the given [String].
|
||||
* The SDK must be initialized before calling this method.
|
||||
*
|
||||
* ```
|
||||
* Formbricks.setUserId("my_user_id")
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
fun setUserId(userId: String) {
|
||||
if (!isInitialized) {
|
||||
val error = SDKError.sdkIsNotInitialized
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
if(UserManager.userId != null) {
|
||||
val error = RuntimeException("A userId is already set ${UserManager.userId} - please call logout first before setting a new one")
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
UserManager.set(userId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an attribute for the current user with the given [String] value and [String] key.
|
||||
* The SDK must be initialized before calling this method.
|
||||
*
|
||||
* ```
|
||||
* Formbricks.setAttribute("my_attribute", "key")
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
fun setAttribute(attribute: String, key: String) {
|
||||
if (!isInitialized) {
|
||||
val error = SDKError.sdkIsNotInitialized
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
UserManager.addAttribute(attribute, key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user attributes for the current user with the given [Map] of [String] values and [String] keys.
|
||||
* The SDK must be initialized before calling this method.
|
||||
*
|
||||
* ```
|
||||
* Formbricks.setAttributes(mapOf(Pair("key", "my_attribute")))
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
fun setAttributes(attributes: Map<String, String>) {
|
||||
if (!isInitialized) {
|
||||
val error = SDKError.sdkIsNotInitialized
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
UserManager.setAttributes(attributes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the language for the current user with the given [String].
|
||||
* The SDK must be initialized before calling this method.
|
||||
*
|
||||
* ```
|
||||
* Formbricks.setLanguage("de")
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
fun setLanguage(language: String) {
|
||||
if (!isInitialized) {
|
||||
val error = SDKError.sdkIsNotInitialized
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
Formbricks.language = language
|
||||
UserManager.setLanguage(language)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks an action with the given [String]. The SDK will process the action and it will present the survey if any of them can be triggered.
|
||||
* The SDK must be initialized before calling this method.
|
||||
*
|
||||
* ```
|
||||
* Formbricks.track("button_clicked")
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
fun track(action: String) {
|
||||
if (!isInitialized) {
|
||||
val error = SDKError.sdkIsNotInitialized
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
if (!isInternetAvailable()) {
|
||||
val error = SDKError.connectionIsNotAvailable
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
SurveyManager.track(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs out the current user. This will clear the user attributes and the user id.
|
||||
* The SDK must be initialized before calling this method.
|
||||
*
|
||||
* ```
|
||||
* Formbricks.logout()
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
fun logout() {
|
||||
if (!isInitialized) {
|
||||
val error = SDKError.sdkIsNotInitialized
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
callback?.onSuccess(SuccessType.LOGOUT_SUCCESS)
|
||||
UserManager.logout()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the [FragmentManager] instance. The SDK always needs the actual [FragmentManager] to
|
||||
* display surveys, so make sure you update it whenever it changes.
|
||||
* The SDK must be initialized before calling this method.
|
||||
*
|
||||
* ```
|
||||
* Formbricks.setFragmentManager(supportFragmentMananger)
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
fun setFragmentManager(fragmentManager: FragmentManager) {
|
||||
this.fragmentManager = fragmentManager
|
||||
}
|
||||
|
||||
/// Assembles the survey fragment and presents it
|
||||
internal fun showSurvey(id: String) {
|
||||
if (fragmentManager == null) {
|
||||
val error = SDKError.fragmentManagerIsNotSet
|
||||
callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
fragmentManager?.let {
|
||||
FormbricksFragment.show(it, surveyId = id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the phone has active network connection
|
||||
private fun isInternetAvailable(): Boolean {
|
||||
val connectivityManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val network = connectivityManager.activeNetwork ?: return false
|
||||
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
|
||||
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.api
|
||||
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
import com.formbricks.formbrickssdk.model.environment.EnvironmentDataHolder
|
||||
import com.formbricks.formbrickssdk.model.user.PostUserBody
|
||||
import com.formbricks.formbrickssdk.model.user.UserResponse
|
||||
import com.formbricks.formbrickssdk.network.FormbricksApiService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object FormbricksApi {
|
||||
var service = FormbricksApiService()
|
||||
|
||||
private suspend fun <T> retryApiCall(
|
||||
retries: Int = 2,
|
||||
delayTime: Long = 1000,
|
||||
block: suspend () -> Result<T>
|
||||
): Result<T> {
|
||||
repeat(retries) { attempt ->
|
||||
val result = block()
|
||||
if (result.isSuccess) return result
|
||||
println("⚠️ Retry ${attempt + 1} due to error: ${result.exceptionOrNull()?.localizedMessage}")
|
||||
delay(delayTime)
|
||||
}
|
||||
return block()
|
||||
}
|
||||
|
||||
fun initialize() {
|
||||
service.initialize(
|
||||
appUrl = Formbricks.appUrl,
|
||||
isLoggingEnabled = Formbricks.loggingEnabled
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getEnvironmentState(): Result<EnvironmentDataHolder> = withContext(Dispatchers.IO) {
|
||||
retryApiCall {
|
||||
try {
|
||||
val response = service.getEnvironmentStateObject(Formbricks.environmentId)
|
||||
val result = response.getOrThrow()
|
||||
Result.success(result)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun postUser(userId: String, attributes: Map<String, *>?): Result<UserResponse> = withContext(Dispatchers.IO) {
|
||||
retryApiCall {
|
||||
try {
|
||||
val result = service.postUser(Formbricks.environmentId, PostUserBody.create(userId, attributes)).getOrThrow()
|
||||
Result.success(result)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.api.error
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FormbricksAPIError(
|
||||
@SerializedName("code") val code: String,
|
||||
@SerializedName("message") val messageText: String,
|
||||
@SerializedName("details") val details: Map<String, String>? = null
|
||||
) : RuntimeException(messageText)
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.extensions
|
||||
|
||||
import com.formbricks.formbrickssdk.model.environment.EnvironmentDataHolder
|
||||
import com.formbricks.formbrickssdk.model.user.UserState
|
||||
import com.formbricks.formbrickssdk.model.user.UserStateData
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
internal const val dateFormatPattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
|
||||
|
||||
fun Date.dateString(): String {
|
||||
val dateFormat = SimpleDateFormat(dateFormatPattern, Locale.getDefault())
|
||||
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return dateFormat.format(this)
|
||||
}
|
||||
|
||||
fun UserStateData.lastDisplayAt(): Date? {
|
||||
lastDisplayAt?.let {
|
||||
try {
|
||||
val formatter = SimpleDateFormat(dateFormatPattern, Locale.getDefault())
|
||||
formatter.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return formatter.parse(it)
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun UserState.expiresAt(): Date? {
|
||||
expiresAt?.let {
|
||||
try {
|
||||
val formatter = SimpleDateFormat(dateFormatPattern, Locale.getDefault())
|
||||
formatter.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return formatter.parse(it)
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun EnvironmentDataHolder.expiresAt(): Date? {
|
||||
data?.expiresAt?.let {
|
||||
try {
|
||||
val formatter = SimpleDateFormat(dateFormatPattern, Locale.getDefault())
|
||||
formatter.timeZone = TimeZone.getTimeZone("UTC")
|
||||
return formatter.parse(it)
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.extensions
|
||||
|
||||
/**
|
||||
* Swift like guard statement.
|
||||
* To achieve that, on null the statement must return an empty T object
|
||||
*/
|
||||
inline fun <reified T> T?.guard(block: T?.() -> Unit): T {
|
||||
this?.let {
|
||||
return it
|
||||
} ?: run {
|
||||
block()
|
||||
}
|
||||
|
||||
return T::class.java.newInstance()
|
||||
}
|
||||
|
||||
inline fun String?.guardEmpty(block: String?.() -> Unit): String {
|
||||
if (isNullOrBlank()) {
|
||||
block()
|
||||
} else {
|
||||
return this
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
inline fun <T: Any> guardLet(vararg elements: T?, closure: () -> Nothing): List<T> {
|
||||
return if (elements.all { it != null }) {
|
||||
elements.filterNotNull()
|
||||
} else {
|
||||
closure()
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.helper
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import androidx.fragment.app.FragmentManager
|
||||
|
||||
/**
|
||||
* Configuration options for the SDK
|
||||
*
|
||||
* Use the [Builder] to configure the options, then pass the result of [build] to the setup method.
|
||||
*/
|
||||
@Keep
|
||||
class FormbricksConfig private constructor(
|
||||
val appUrl: String,
|
||||
val environmentId: String,
|
||||
val userId: String?,
|
||||
val attributes: Map<String,String>?,
|
||||
val loggingEnabled: Boolean,
|
||||
val fragmentManager: FragmentManager?
|
||||
) {
|
||||
class Builder(private val appUrl: String, private val environmentId: String) {
|
||||
private var userId: String? = null
|
||||
private var attributes: MutableMap<String,String> = mutableMapOf()
|
||||
private var loggingEnabled = false
|
||||
private var fragmentManager: FragmentManager? = null
|
||||
|
||||
fun setUserId(userId: String): Builder {
|
||||
this.userId = userId
|
||||
return this
|
||||
}
|
||||
|
||||
fun setAttributes(attributes: MutableMap<String,String>): Builder {
|
||||
this.attributes = attributes
|
||||
return this
|
||||
}
|
||||
|
||||
fun addAttribute(attribute: String, key: String): Builder {
|
||||
this.attributes[key] = attribute
|
||||
return this
|
||||
}
|
||||
|
||||
fun setLoggingEnabled(loggingEnabled: Boolean): Builder {
|
||||
this.loggingEnabled = loggingEnabled
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFragmentManager(fragmentManager: FragmentManager): Builder {
|
||||
this.fragmentManager = fragmentManager
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): FormbricksConfig {
|
||||
return FormbricksConfig(
|
||||
appUrl = appUrl,
|
||||
environmentId = environmentId,
|
||||
userId = userId,
|
||||
attributes = attributes,
|
||||
loggingEnabled = loggingEnabled,
|
||||
fragmentManager = fragmentManager
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.helper
|
||||
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
fun mapToJsonElement(map: Map<String, Any?>): JsonElement {
|
||||
return buildJsonObject {
|
||||
map.forEach { (key, value) ->
|
||||
when (value) {
|
||||
is String -> put(key, value)
|
||||
is Number -> put(key, value)
|
||||
is Boolean -> put(key, value)
|
||||
is Map<*, *> -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
put(key, mapToJsonElement(value as Map<String, Any?>))
|
||||
}
|
||||
is List<*> -> {
|
||||
put(key, JsonArray(value.map { elem -> mapToJsonElementItem(elem) }))
|
||||
}
|
||||
null -> put(key, JsonNull)
|
||||
else -> throw IllegalArgumentException("Unsupported type: ${value::class}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun mapToJsonElementItem(value: Any?): JsonElement {
|
||||
return when (value) {
|
||||
is String -> JsonPrimitive(value)
|
||||
is Number -> JsonPrimitive(value)
|
||||
is Boolean -> JsonPrimitive(value)
|
||||
is Map<*, *> -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
mapToJsonElement(value as Map<String, Any?>)
|
||||
}
|
||||
is List<*> -> JsonArray(value.map { elem -> mapToJsonElementItem(elem) })
|
||||
null -> JsonNull
|
||||
else -> throw IllegalArgumentException("Unsupported type: ${value::class}")
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.logger
|
||||
|
||||
import android.util.Log
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
|
||||
object Logger {
|
||||
fun d(message: String) {
|
||||
if (Formbricks.loggingEnabled) {
|
||||
Log.d("FormbricksSDK", message)
|
||||
}
|
||||
}
|
||||
|
||||
fun e(exception: RuntimeException) {
|
||||
if (Formbricks.loggingEnabled) {
|
||||
Log.e("FormbricksSDK", exception.localizedMessage, exception)
|
||||
}
|
||||
}
|
||||
|
||||
fun w(message: String? = "Warning", exception: RuntimeException? = null) {
|
||||
if (Formbricks.loggingEnabled) {
|
||||
Log.w("FormbricksSDK", message, exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,351 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.manager
|
||||
|
||||
import android.content.Context
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
import com.formbricks.formbrickssdk.api.FormbricksApi
|
||||
import com.formbricks.formbrickssdk.extensions.expiresAt
|
||||
import com.formbricks.formbrickssdk.extensions.guard
|
||||
import com.formbricks.formbrickssdk.logger.Logger
|
||||
import com.formbricks.formbrickssdk.model.environment.EnvironmentDataHolder
|
||||
import com.formbricks.formbrickssdk.model.environment.Survey
|
||||
import com.formbricks.formbrickssdk.model.error.SDKError
|
||||
import com.formbricks.formbrickssdk.model.enums.SuccessType
|
||||
import com.formbricks.formbrickssdk.model.user.Display
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.RuntimeException
|
||||
import java.util.Date
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* The SurveyManager is responsible for managing the surveys that are displayed to the user.
|
||||
* Filtering surveys based on the user's segments, responses, and displays.
|
||||
*/
|
||||
object SurveyManager {
|
||||
private const val REFRESH_STATE_ON_ERROR_TIMEOUT_IN_MINUTES = 10
|
||||
private const val FORMBRICKS_PREFS = "formbricks_prefs"
|
||||
private const val PREF_FORMBRICKS_DATA_HOLDER = "formbricksDataHolder"
|
||||
|
||||
internal val refreshTimer = Timer()
|
||||
internal var displayTimer = Timer()
|
||||
internal var hasApiError = false
|
||||
internal var isShowingSurvey = false
|
||||
private val prefManager by lazy { Formbricks.applicationContext.getSharedPreferences(FORMBRICKS_PREFS, Context.MODE_PRIVATE) }
|
||||
internal var filteredSurveys: MutableList<Survey> = mutableListOf()
|
||||
|
||||
private var environmentDataHolderJson: String?
|
||||
get() {
|
||||
return prefManager.getString(PREF_FORMBRICKS_DATA_HOLDER, "")
|
||||
}
|
||||
set(value) {
|
||||
if (null != value) {
|
||||
prefManager.edit().putString(PREF_FORMBRICKS_DATA_HOLDER, value).apply()
|
||||
} else {
|
||||
prefManager.edit().remove(PREF_FORMBRICKS_DATA_HOLDER).apply()
|
||||
}
|
||||
}
|
||||
|
||||
private var backingEnvironmentDataHolder: EnvironmentDataHolder? = null
|
||||
var environmentDataHolder: EnvironmentDataHolder?
|
||||
get() {
|
||||
if (null != backingEnvironmentDataHolder) {
|
||||
return backingEnvironmentDataHolder
|
||||
}
|
||||
synchronized(this) {
|
||||
backingEnvironmentDataHolder = environmentDataHolderJson?.let { json ->
|
||||
try {
|
||||
Gson().fromJson(json, EnvironmentDataHolder::class.java)
|
||||
} catch (e: Exception) {
|
||||
Formbricks.callback?.onError(e)
|
||||
Logger.e(RuntimeException("Unable to retrieve environment data from the local storage."))
|
||||
null
|
||||
}
|
||||
}
|
||||
return backingEnvironmentDataHolder
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
synchronized(this) {
|
||||
backingEnvironmentDataHolder = value
|
||||
environmentDataHolderJson = Gson().toJson(value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills up the [filteredSurveys] array
|
||||
*/
|
||||
fun filterSurveys() {
|
||||
val surveys = environmentDataHolder?.data?.data?.surveys.guard { return }
|
||||
val displays = UserManager.displays ?: listOf()
|
||||
val responses = UserManager.responses ?: listOf()
|
||||
val segments = UserManager.segments ?: listOf()
|
||||
|
||||
filteredSurveys = filterSurveysBasedOnDisplayType(surveys, displays, responses).toMutableList()
|
||||
filteredSurveys = filterSurveysBasedOnRecontactDays(filteredSurveys, environmentDataHolder?.data?.data?.project?.recontactDays?.toInt()).toMutableList()
|
||||
|
||||
if (UserManager.userId != null) {
|
||||
if (segments.isEmpty()) {
|
||||
filteredSurveys = mutableListOf()
|
||||
return
|
||||
}
|
||||
|
||||
filteredSurveys = filterSurveysBasedOnSegments(filteredSurveys, segments).toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the environment state needs to be refreshed based on its [expiresAt] property,
|
||||
* and if so, refreshes it, starts the refresh timer, and filters the surveys.
|
||||
*/
|
||||
fun refreshEnvironmentIfNeeded(force: Boolean = false) {
|
||||
if (!force) {
|
||||
environmentDataHolder?.expiresAt()?.let {
|
||||
if (it.after(Date())) {
|
||||
Logger.d("Environment state is still valid until $it")
|
||||
filterSurveys()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
environmentDataHolder = FormbricksApi.getEnvironmentState().getOrThrow()
|
||||
startRefreshTimer(environmentDataHolder?.expiresAt())
|
||||
filterSurveys()
|
||||
hasApiError = false
|
||||
Formbricks.callback?.onSuccess(SuccessType.GET_ENVIRONMENT_SUCCESS)
|
||||
} catch (e: Exception) {
|
||||
hasApiError = true
|
||||
val error = SDKError.unableToRefreshEnvironment
|
||||
Formbricks.callback?.onError(error)
|
||||
Logger.e(error)
|
||||
startErrorTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are any surveys to display, based in the track action, and if so, displays the first one.
|
||||
* Handles the display percentage and the delay of the survey.
|
||||
*/
|
||||
fun track(action: String) {
|
||||
val actionClasses = environmentDataHolder?.data?.data?.actionClasses ?: listOf()
|
||||
val codeActionClasses = actionClasses.filter { it.type == "code" }
|
||||
val actionClass = codeActionClasses.firstOrNull { it.key == action }
|
||||
val firstSurveyWithActionClass = filteredSurveys.firstOrNull { survey ->
|
||||
val triggers = survey.triggers ?: listOf()
|
||||
triggers.firstOrNull { it.actionClass?.name.equals(actionClass?.name) } != null
|
||||
}
|
||||
|
||||
if (firstSurveyWithActionClass == null) {
|
||||
Formbricks.callback?.onError(SDKError.surveyNotFoundError)
|
||||
return
|
||||
}
|
||||
|
||||
val isMultiLangSurvey = (firstSurveyWithActionClass.languages?.size ?: 0) > 1
|
||||
if(isMultiLangSurvey) {
|
||||
val currentLanguage = Formbricks.language
|
||||
val languageCode = getLanguageCode(firstSurveyWithActionClass, currentLanguage)
|
||||
|
||||
if (languageCode == null) {
|
||||
val error = RuntimeException("Survey “${firstSurveyWithActionClass.name}” is not available in language “$currentLanguage”. Skipping.")
|
||||
Formbricks.callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
Formbricks.setLanguage(languageCode)
|
||||
}
|
||||
|
||||
val shouldDisplay = shouldDisplayBasedOnPercentage(firstSurveyWithActionClass.displayPercentage)
|
||||
|
||||
if (shouldDisplay) {
|
||||
firstSurveyWithActionClass.id.let {
|
||||
isShowingSurvey = true
|
||||
val timeout = firstSurveyWithActionClass.delay ?: 0.0
|
||||
stopDisplayTimer()
|
||||
displayTimer.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
Formbricks.showSurvey(it)
|
||||
}
|
||||
|
||||
}, Date(System.currentTimeMillis() + timeout.toLong() * 1000))
|
||||
}
|
||||
} else {
|
||||
Formbricks.callback?.onError(SDKError.surveyNotDisplayedError)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopDisplayTimer() {
|
||||
displayTimer.cancel()
|
||||
displayTimer = Timer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a survey response to the Formbricks API.
|
||||
*/
|
||||
fun postResponse(surveyId: String?) {
|
||||
val id = surveyId.guard {
|
||||
val error = SDKError.missingSurveyId
|
||||
Formbricks.callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
UserManager.onResponse(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new display for the survey. It is called when the survey is displayed to the user.
|
||||
*/
|
||||
fun onNewDisplay(surveyId: String?) {
|
||||
val id = surveyId.guard {
|
||||
val error = SDKError.missingSurveyId
|
||||
Formbricks.callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
UserManager.onDisplay(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a timer to refresh the environment state after the given timeout [expiresAt].
|
||||
*/
|
||||
private fun startRefreshTimer(expiresAt: Date?) {
|
||||
val date = expiresAt.guard { return }
|
||||
refreshTimer.schedule(object: TimerTask() {
|
||||
override fun run() {
|
||||
Logger.d("Refreshing environment state.")
|
||||
refreshEnvironmentIfNeeded()
|
||||
}
|
||||
|
||||
}, date)
|
||||
}
|
||||
|
||||
/**
|
||||
* When an error occurs, it starts a timer to refresh the environment state after the given timeout.
|
||||
*/
|
||||
private fun startErrorTimer() {
|
||||
val targetDate = Date(System.currentTimeMillis() + 1000 * 60 * REFRESH_STATE_ON_ERROR_TIMEOUT_IN_MINUTES)
|
||||
refreshTimer.schedule(object: TimerTask() {
|
||||
override fun run() {
|
||||
Logger.d("Refreshing environment state after an error")
|
||||
refreshEnvironmentIfNeeded()
|
||||
}
|
||||
|
||||
}, targetDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the surveys based on the display type and limit.
|
||||
*/
|
||||
private fun filterSurveysBasedOnDisplayType(surveys: List<Survey>, displays: List<Display>, responses: List<String>): List<Survey> {
|
||||
return surveys.filter { survey ->
|
||||
when (survey.displayOption) {
|
||||
"respondMultiple" -> true
|
||||
|
||||
"displayOnce" -> {
|
||||
displays.none { it.surveyId == survey.id }
|
||||
}
|
||||
|
||||
"displayMultiple" -> {
|
||||
responses.none { it == survey.id }
|
||||
}
|
||||
|
||||
"displaySome" -> {
|
||||
survey.displayLimit?.let { limit ->
|
||||
if (responses.any { it == survey.id }) {
|
||||
return@filter false
|
||||
}
|
||||
displays.count { it.surveyId == survey.id } < limit
|
||||
} ?: true
|
||||
}
|
||||
|
||||
else -> {
|
||||
val error = SDKError.invalidDisplayOption
|
||||
Formbricks.callback?.onError(error)
|
||||
Logger.e(error)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the surveys based on the recontact days and the [UserManager.lastDisplayedAt] date.
|
||||
*/
|
||||
private fun filterSurveysBasedOnRecontactDays(surveys: List<Survey>, defaultRecontactDays: Int?): List<Survey> {
|
||||
return surveys.filter { survey ->
|
||||
val lastDisplayedAt = UserManager.lastDisplayedAt.guard { return@filter true }
|
||||
|
||||
val recontactDays = survey.recontactDays ?: defaultRecontactDays
|
||||
|
||||
if (recontactDays != null) {
|
||||
val daysBetween = TimeUnit.MILLISECONDS.toDays(Date().time - lastDisplayedAt.time)
|
||||
return@filter daysBetween >= recontactDays.toInt()
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the surveys based on the user's segments.
|
||||
*/
|
||||
private fun filterSurveysBasedOnSegments(surveys: List<Survey>, segments: List<String>): List<Survey> {
|
||||
return surveys.filter { survey ->
|
||||
val segmentId = survey.segment?.id?.guard { return@filter false }
|
||||
segments.contains(segmentId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if the survey should be displayed based on the display percentage.
|
||||
*/
|
||||
private fun shouldDisplayBasedOnPercentage(displayPercentage: Double?): Boolean {
|
||||
val percentage = displayPercentage.guard { return true }
|
||||
val randomNum = (0 until 10000).random() / 100.0
|
||||
return randomNum <= percentage
|
||||
}
|
||||
|
||||
private fun getLanguageCode(survey: Survey, language: String?): String? {
|
||||
// 1) Gather all valid codes
|
||||
val availableLanguageCodes = survey.languages
|
||||
?.map { it.language.code }
|
||||
?: emptyList()
|
||||
|
||||
// 2) No input or explicit "default" → default
|
||||
val raw = language
|
||||
?.lowercase()
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?: return "default"
|
||||
if (raw == "default") return "default"
|
||||
|
||||
// 3) Find matching entry by code or alias
|
||||
val selected = survey.languages
|
||||
?.firstOrNull { entry ->
|
||||
entry.language.code.lowercase() == raw ||
|
||||
entry.language.alias?.lowercase() == raw
|
||||
}
|
||||
|
||||
// 4) If that entry is marked default → default
|
||||
if (selected?.default == true) return "default"
|
||||
|
||||
// 5) If missing, disabled, or not in the available list → null
|
||||
if (selected == null
|
||||
|| !selected.enabled
|
||||
|| !availableLanguageCodes.contains(selected.language.code)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 6) Otherwise return its code
|
||||
return selected.language.code
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.manager
|
||||
|
||||
import android.content.Context
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
import com.formbricks.formbrickssdk.api.FormbricksApi
|
||||
import com.formbricks.formbrickssdk.extensions.dateString
|
||||
import com.formbricks.formbrickssdk.extensions.expiresAt
|
||||
import com.formbricks.formbrickssdk.extensions.guard
|
||||
import com.formbricks.formbrickssdk.extensions.lastDisplayAt
|
||||
import com.formbricks.formbrickssdk.logger.Logger
|
||||
import com.formbricks.formbrickssdk.model.error.SDKError
|
||||
import com.formbricks.formbrickssdk.model.enums.SuccessType
|
||||
import com.formbricks.formbrickssdk.model.user.Display
|
||||
import com.formbricks.formbrickssdk.network.queue.UpdateQueue
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Date
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
/**
|
||||
* Store and manage user state and sync with the server when needed.
|
||||
*/
|
||||
object UserManager {
|
||||
private const val FORMBROCKS_PERFS = "formbricks_prefs"
|
||||
private const val USER_ID_KEY = "userIdKey"
|
||||
private const val CONTACT_ID_KEY = "contactIdKey"
|
||||
private const val SEGMENTS_KEY = "segmentsKey"
|
||||
private const val DISPLAYS_KEY = "displaysKey"
|
||||
private const val RESPONSES_KEY = "responsesKey"
|
||||
private const val LAST_DISPLAYED_AT_KEY = "lastDisplayedAtKey"
|
||||
private const val EXPIRES_AT_KEY = "expiresAtKey"
|
||||
private val prefManager by lazy { Formbricks.applicationContext.getSharedPreferences(FORMBROCKS_PERFS, Context.MODE_PRIVATE) }
|
||||
|
||||
private var backingUserId: String? = null
|
||||
private var backingContactId: String? = null
|
||||
private var backingSegments: List<String>? = null
|
||||
private var backingDisplays: List<Display>? = null
|
||||
private var backingResponses: List<String>? = null
|
||||
private var backingLastDisplayedAt: Date? = null
|
||||
private var backingExpiresAt: Date? = null
|
||||
internal val syncTimer = Timer()
|
||||
|
||||
/**
|
||||
* Starts an update queue with the given user id.
|
||||
*
|
||||
* @param userId
|
||||
*/
|
||||
fun set(userId: String) {
|
||||
UpdateQueue.current.setUserId(userId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an update queue with the given attribute.
|
||||
*
|
||||
* @param attribute
|
||||
* @param key
|
||||
*/
|
||||
fun addAttribute(attribute: String, key: String) {
|
||||
UpdateQueue.current.addAttribute(key, attribute)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an update queue with the given attributes.
|
||||
*
|
||||
* @param attributes
|
||||
*/
|
||||
fun setAttributes(attributes: Map<String, String>) {
|
||||
UpdateQueue.current.setAttributes(attributes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an update queue with the given language..
|
||||
*
|
||||
* @param language
|
||||
*/
|
||||
fun setLanguage(language: String) {
|
||||
UpdateQueue.current.setLanguage(language)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves [surveyId] to the [displays] property and the the current date to the [lastDisplayedAt] property.
|
||||
*
|
||||
* @param surveyId
|
||||
*/
|
||||
fun onDisplay(surveyId: String) {
|
||||
val lastDisplayedAt = Date()
|
||||
val newDisplays = displays?.toMutableList() ?: mutableListOf()
|
||||
newDisplays.add(Display(surveyId, lastDisplayedAt.dateString()))
|
||||
displays = newDisplays
|
||||
this.lastDisplayedAt = lastDisplayedAt
|
||||
SurveyManager.filterSurveys()
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves [surveyId] to the [responses] property.
|
||||
*
|
||||
* @param surveyId
|
||||
*/
|
||||
fun onResponse(surveyId: String) {
|
||||
val newResponses = responses?.toMutableList() ?: mutableListOf()
|
||||
newResponses.add(surveyId)
|
||||
responses = newResponses
|
||||
SurveyManager.filterSurveys()
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the user state with the server if the user id is set and the expiration date has passed.
|
||||
*/
|
||||
fun syncUserStateIfNeeded() {
|
||||
val id = userId
|
||||
val expiresAt = expiresAt
|
||||
if (id != null && expiresAt != null && Date().before(expiresAt)) {
|
||||
syncUser(id)
|
||||
} else {
|
||||
backingSegments = emptyList()
|
||||
backingDisplays = emptyList()
|
||||
backingResponses = emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs the user state with the server, calls the [SurveyManager.filterSurveys] method and starts the sync timer.
|
||||
*
|
||||
* @param id
|
||||
* @param attributes
|
||||
*/
|
||||
fun syncUser(id: String, attributes: Map<String, String>? = null) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val userResponse = FormbricksApi.postUser(id, attributes).getOrThrow()
|
||||
userId = userResponse.data.state.data.userId
|
||||
contactId = userResponse.data.state.data.contactId
|
||||
segments = userResponse.data.state.data.segments
|
||||
displays = userResponse.data.state.data.displays
|
||||
responses = userResponse.data.state.data.responses
|
||||
lastDisplayedAt = userResponse.data.state.data.lastDisplayAt()
|
||||
expiresAt = userResponse.data.state.expiresAt()
|
||||
val languageFromUserResponse = userResponse.data.state.data.language
|
||||
|
||||
if(languageFromUserResponse != null) {
|
||||
Formbricks.language = languageFromUserResponse
|
||||
}
|
||||
|
||||
UpdateQueue.current.reset()
|
||||
SurveyManager.filterSurveys()
|
||||
startSyncTimer()
|
||||
Formbricks.callback?.onSuccess(SuccessType.SET_USER_SUCCESS)
|
||||
} catch (e: Exception) {
|
||||
val error = SDKError.unableToPostResponse
|
||||
Formbricks.callback?.onError(error)
|
||||
Logger.e(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs out the user and clears the user state.
|
||||
*/
|
||||
fun logout() {
|
||||
val isUserIdDefined = userId != null
|
||||
|
||||
if (!isUserIdDefined) {
|
||||
val error = SDKError.noUserIdSetError
|
||||
Formbricks.callback?.onError(error)
|
||||
Logger.e(error)
|
||||
}
|
||||
|
||||
prefManager.edit().apply {
|
||||
remove(CONTACT_ID_KEY)
|
||||
remove(USER_ID_KEY)
|
||||
remove(SEGMENTS_KEY)
|
||||
remove(DISPLAYS_KEY)
|
||||
remove(RESPONSES_KEY)
|
||||
remove(LAST_DISPLAYED_AT_KEY)
|
||||
remove(EXPIRES_AT_KEY)
|
||||
apply()
|
||||
}
|
||||
|
||||
backingUserId = null
|
||||
backingContactId = null
|
||||
backingSegments = null
|
||||
backingDisplays = null
|
||||
backingResponses = null
|
||||
backingLastDisplayedAt = null
|
||||
backingExpiresAt = null
|
||||
Formbricks.language = "default"
|
||||
UpdateQueue.current.reset()
|
||||
|
||||
if(isUserIdDefined) {
|
||||
Logger.d("User logged out successfully!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun startSyncTimer() {
|
||||
val expiresAt = expiresAt.guard { return }
|
||||
val userId = userId.guard { return }
|
||||
syncTimer.schedule(object: TimerTask() {
|
||||
override fun run() {
|
||||
syncUser(userId)
|
||||
}
|
||||
|
||||
}, expiresAt)
|
||||
}
|
||||
|
||||
|
||||
var userId: String?
|
||||
get() = backingUserId ?: prefManager.getString(USER_ID_KEY, null).also { backingUserId = it }
|
||||
private set(value) {
|
||||
backingUserId = value
|
||||
prefManager.edit().putString(USER_ID_KEY, value).apply()
|
||||
}
|
||||
|
||||
var contactId: String?
|
||||
get() = backingContactId ?: prefManager.getString(CONTACT_ID_KEY, null).also { backingContactId = it }
|
||||
private set(value) {
|
||||
backingContactId = value
|
||||
prefManager.edit().putString(CONTACT_ID_KEY, value).apply()
|
||||
}
|
||||
|
||||
var segments: List<String>?
|
||||
get() = backingSegments ?: prefManager.getStringSet(SEGMENTS_KEY, emptySet())?.toList().also { backingSegments = it }
|
||||
private set(value) {
|
||||
backingSegments = value
|
||||
prefManager.edit().putStringSet(SEGMENTS_KEY, value?.toSet()).apply()
|
||||
}
|
||||
|
||||
var displays: List<Display>?
|
||||
get() {
|
||||
if (backingDisplays == null) {
|
||||
val json = prefManager.getString(DISPLAYS_KEY, null)
|
||||
if (json != null) {
|
||||
backingDisplays = Gson().fromJson(json, Array<Display>::class.java).toList()
|
||||
}
|
||||
}
|
||||
return backingDisplays
|
||||
}
|
||||
private set(value) {
|
||||
backingDisplays = value
|
||||
prefManager.edit().putString(DISPLAYS_KEY, Gson().toJson(value)).apply()
|
||||
}
|
||||
|
||||
var responses: List<String>?
|
||||
get() = backingResponses ?: prefManager.getStringSet(RESPONSES_KEY, emptySet())?.toList().also { backingResponses = it }
|
||||
private set(value) {
|
||||
backingResponses = value
|
||||
prefManager.edit().putStringSet(RESPONSES_KEY, value?.toSet()).apply()
|
||||
}
|
||||
|
||||
var lastDisplayedAt: Date?
|
||||
get() = backingLastDisplayedAt ?: prefManager.getLong(LAST_DISPLAYED_AT_KEY, 0L).takeIf { it > 0 }?.let { Date(it) }.also { backingLastDisplayedAt = it }
|
||||
private set(value) {
|
||||
backingLastDisplayedAt = value
|
||||
prefManager.edit().putLong(LAST_DISPLAYED_AT_KEY, value?.time ?: 0L).apply()
|
||||
}
|
||||
|
||||
var expiresAt: Date?
|
||||
get() = backingExpiresAt ?: prefManager.getLong(EXPIRES_AT_KEY, 0L).takeIf { it > 0 }?.let { Date(it) }.also { backingExpiresAt = it }
|
||||
private set(value) {
|
||||
backingExpiresAt = value
|
||||
prefManager.edit().putLong(EXPIRES_AT_KEY, value?.time ?: 0L).apply()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model
|
||||
|
||||
interface BaseFormbricksResponse
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.enums
|
||||
|
||||
enum class SuccessType {
|
||||
SET_USER_SUCCESS,
|
||||
GET_ENVIRONMENT_SUCCESS,
|
||||
LOGOUT_SUCCESS
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ActionClass(
|
||||
@SerializedName("id") val id: String?,
|
||||
@SerializedName("type") val type: String?,
|
||||
@SerializedName("name") val name: String?,
|
||||
@SerializedName("key") val key: String?,
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ActionClassReference(
|
||||
@SerializedName("name") val name: String?
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class BrandColor(
|
||||
@SerializedName("light") val light: String?
|
||||
)
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class EnvironmentData(
|
||||
@SerializedName("surveys") val surveys: List<Survey>?,
|
||||
@SerializedName("actionClasses") val actionClasses: List<ActionClass>?,
|
||||
@SerializedName("project") val project: Project
|
||||
)
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonElement
|
||||
|
||||
data class EnvironmentDataHolder(
|
||||
val data: EnvironmentResponseData?,
|
||||
val originalResponseMap: Map<String, Any>
|
||||
)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun EnvironmentDataHolder.getSurveyJson(surveyId: String): JsonElement? {
|
||||
val responseMap = originalResponseMap["data"] as? Map<*, *>
|
||||
val dataMap = responseMap?.get("data") as? Map<*, *>
|
||||
val surveyArray = dataMap?.get("surveys") as? ArrayList<Map<String, Any?>>
|
||||
val firstSurvey = surveyArray?.firstOrNull { it["id"] == surveyId }
|
||||
firstSurvey?.let {
|
||||
return Gson().toJsonTree(it)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun EnvironmentDataHolder.getStyling(surveyId: String): JsonElement? {
|
||||
val responseMap = originalResponseMap["data"] as? Map<*, *>
|
||||
val dataMap = responseMap?.get("data") as? Map<*, *>
|
||||
val surveyArray = dataMap?.get("surveys") as? ArrayList<Map<String, Any?>>
|
||||
val firstSurvey = surveyArray?.firstOrNull { it["id"] == surveyId }
|
||||
firstSurvey?.get("styling")?.let {
|
||||
return Gson().toJsonTree(it)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun EnvironmentDataHolder.getProjectStylingJson(): JsonElement? {
|
||||
val responseMap = originalResponseMap["data"] as? Map<*, *>
|
||||
val dataMap = responseMap?.get("data") as? Map<*, *>
|
||||
val projectMap = dataMap?.get("project") as? Map<*, *>
|
||||
val stylingMap = projectMap?.get("styling") as? Map<String, Any?>
|
||||
stylingMap?.let {
|
||||
return Gson().toJsonTree(it)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.formbricks.formbrickssdk.model.BaseFormbricksResponse
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class EnvironmentResponse(
|
||||
@SerializedName("data") val data: EnvironmentResponseData,
|
||||
): BaseFormbricksResponse
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class EnvironmentResponseData(
|
||||
@SerializedName("data") val data: EnvironmentData,
|
||||
@SerializedName("expiresAt") val expiresAt: String?
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Project(
|
||||
@SerializedName("id") val id: String?,
|
||||
@SerializedName("recontactDays") val recontactDays: Double?,
|
||||
@SerializedName("clickOutsideClose") val clickOutsideClose: Boolean?,
|
||||
@SerializedName("darkOverlay") val darkOverlay: Boolean?,
|
||||
@SerializedName("placement") val placement: String?,
|
||||
@SerializedName("inAppSurveyBranding") val inAppSurveyBranding: Boolean?,
|
||||
@SerializedName("styling") val styling: Styling?
|
||||
)
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Segment(
|
||||
@SerializedName("id") val id: String? = null,
|
||||
@SerializedName("createdAt") val createdAt: String? = null,
|
||||
@SerializedName("updatedAt") val updatedAt: String? = null,
|
||||
@SerializedName("title") val title: String? = null,
|
||||
@SerializedName("description") val description: String? = null,
|
||||
@SerializedName("isPrivate") val isPrivate: Boolean? = null,
|
||||
@SerializedName("filters") val filters: List<String>? = null,
|
||||
@SerializedName("environmentId") val environmentId: String? = null,
|
||||
@SerializedName("surveys") val surveys: List<String>? = null
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Styling(
|
||||
@SerializedName("roundness") val roundness: Double? = null,
|
||||
@SerializedName("allowStyleOverwrite") val allowStyleOverwrite: Boolean? = null,
|
||||
)
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SurveyLanguage(
|
||||
@SerializedName("enabled") val enabled: Boolean,
|
||||
@SerializedName("default") val default: Boolean,
|
||||
@SerializedName("language") val language: LanguageDetail
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class LanguageDetail(
|
||||
@SerializedName("id") val id: String,
|
||||
@SerializedName("code") val code: String,
|
||||
@SerializedName("alias") val alias: String?,
|
||||
@SerializedName("projectId") val projectId: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Survey(
|
||||
@SerializedName("id") val id: String,
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("triggers") val triggers: List<Trigger>?,
|
||||
@SerializedName("recontactDays") val recontactDays: Double?,
|
||||
@SerializedName("displayLimit") val displayLimit: Double?,
|
||||
@SerializedName("delay") val delay: Double?,
|
||||
@SerializedName("displayPercentage") val displayPercentage: Double?,
|
||||
@SerializedName("displayOption") val displayOption: String?,
|
||||
@SerializedName("segment") val segment: Segment?,
|
||||
@SerializedName("styling") val styling: Styling?,
|
||||
@SerializedName("languages") val languages: List<SurveyLanguage>?
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.environment
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Trigger(
|
||||
@SerializedName("actionClass") val actionClass: ActionClassReference?
|
||||
)
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.error
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
object SDKError {
|
||||
// Errors related to SDK initialization and configuration
|
||||
val sdkIsNotInitialized = RuntimeException("Formbricks SDK is not initialized")
|
||||
val sdkIsAlreadyInitialized = RuntimeException("Formbricks SDK is already initialized")
|
||||
val fragmentManagerIsNotSet = RuntimeException("The fragment manager is not set.")
|
||||
|
||||
// Errors related to network and connectivity
|
||||
val connectionIsNotAvailable = RuntimeException("There is no connection.")
|
||||
val unableToLoadFormbicksJs = RuntimeException("Unable to load Formbricks Javascript package.")
|
||||
|
||||
// Errors related to surveys
|
||||
val surveyDisplayFetchError =
|
||||
RuntimeException("Error: creating display: TypeError: Failure to fetch the survey data.")
|
||||
val surveyNotDisplayedError = RuntimeException("Survey was not displayed due to display percentage restrictions.")
|
||||
val unableToRefreshEnvironment = RuntimeException("Unable to refresh environment state.")
|
||||
val missingSurveyId = RuntimeException("Survey id is mandatory to set.")
|
||||
val invalidDisplayOption = RuntimeException("Invalid Display Option.")
|
||||
val unableToPostResponse = RuntimeException("Unable to post survey response.")
|
||||
val surveyNotFoundError = RuntimeException("No survey found matching the action class.")
|
||||
val noUserIdSetError = RuntimeException("No userId is set, please set a userId first using the setUserId function")
|
||||
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.javascript
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
enum class EventType {
|
||||
@SerializedName("onClose") ON_CLOSE,
|
||||
@SerializedName("onDisplayCreated") ON_DISPLAY_CREATED,
|
||||
@SerializedName("onResponseCreated") ON_RESPONSE_CREATED,
|
||||
@SerializedName("onFilePick") ON_FILE_PICK,
|
||||
@SerializedName("onSurveyLibraryLoadError") ON_SURVEY_LIBRARY_LOAD_ERROR
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.javascript
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FileUploadData(
|
||||
@SerializedName("event") val event: EventType,
|
||||
@SerializedName("fileUploadParams") val fileUploadParams: FileUploadParams,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun from(string: String): FileUploadData {
|
||||
return Gson().fromJson(string, FileUploadData::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class FileUploadParams(
|
||||
@SerializedName("allowedFileExtensions") val allowedFileExtensions: String?,
|
||||
@SerializedName("allowMultipleFiles") val allowMultipleFiles: Boolean
|
||||
) {
|
||||
fun allowedExtensionsArray(): Array<String> {
|
||||
return allowedFileExtensions?.split(",")?.map { it }?.toTypedArray() ?: arrayOf()
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.javascript
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class JsMessageData(
|
||||
@SerializedName("event") val event: EventType,
|
||||
) {
|
||||
companion object {
|
||||
fun from(string: String): JsMessageData {
|
||||
return try {
|
||||
Gson().fromJson(string, JsMessageData::class.java)
|
||||
} catch (e: Exception) {
|
||||
throw IllegalArgumentException("Invalid JSON format: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.upload
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FetchStorageUrlRequestBody (
|
||||
@SerializedName("fileName") val fileName: String,
|
||||
@SerializedName("fileType") val fileType: String,
|
||||
@SerializedName("allowedFileExtensions") val allowedFileExtensions: List<String>?,
|
||||
@SerializedName("surveyId") val surveyId: String,
|
||||
@SerializedName("accessType") val accessType: String,
|
||||
) {
|
||||
companion object {
|
||||
fun create(fileName: String, fileType: String, allowedFileExtensions: List<String>?, surveyId: String, accessType: String = "public"): FetchStorageUrlRequestBody {
|
||||
return FetchStorageUrlRequestBody(fileName, fileType, allowedFileExtensions, surveyId, accessType)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.upload
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class FetchStorageUrlResponse(
|
||||
@SerializedName("data") val data: StorageData
|
||||
)
|
||||
@@ -1,28 +0,0 @@
|
||||
//package com.formbricks.formbrickssdk.model.upload
|
||||
//
|
||||
//import com.formbricks.formbrickssdk.model.javascript.FileData
|
||||
//import com.google.gson.annotations.SerializedName
|
||||
//
|
||||
//data class FileUploadBody(
|
||||
// @SerializedName("fileName") val fileName: String,
|
||||
// @SerializedName("fileType") val fileType: String,
|
||||
// @SerializedName("surveyId") val surveyId: String?,
|
||||
// @SerializedName("signature") val signature: String,
|
||||
// @SerializedName("timestamp") val timestamp: String,
|
||||
// @SerializedName("uuid") val uuid: String,
|
||||
// @SerializedName("fileBase64String") val fileBase64String: String,
|
||||
//) {
|
||||
// companion object {
|
||||
// fun create(file: FileData, storageData: StorageData, surveyId: String?): FileUploadBody {
|
||||
// return FileUploadBody(
|
||||
// fileName = storageData.updatedFileName,
|
||||
// fileType = file.type,
|
||||
// surveyId = surveyId,
|
||||
// signature = storageData.signingData.signature,
|
||||
// uuid = storageData.signingData.uuid,
|
||||
// timestamp = storageData.signingData.timestamp.toString(),
|
||||
// fileBase64String = file.base64
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.upload
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SigningData(
|
||||
@SerializedName("signature") val signature: String,
|
||||
@SerializedName("timestamp") val timestamp: Long,
|
||||
@SerializedName("uuid") val uuid: String
|
||||
)
|
||||
@@ -1,10 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.upload
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class StorageData(
|
||||
@SerializedName("signedUrl") val signedUrl: String,
|
||||
@SerializedName("signingData") val signingData: SigningData,
|
||||
@SerializedName("updatedFileName") val updatedFileName: String,
|
||||
@SerializedName("fileUrl") val fileUrl: String
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.user
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Display(
|
||||
@SerializedName("surveyId") val surveyId: String,
|
||||
@SerializedName("createdAt") val createdAt: String
|
||||
)
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.user
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PostUserBody(
|
||||
@SerializedName("userId") val userId: String,
|
||||
@SerializedName("attributes") val attributes: Map<String, *>?
|
||||
) {
|
||||
companion object {
|
||||
fun create(userId: String, attributes: Map<String, *>?): PostUserBody {
|
||||
return PostUserBody(userId, attributes)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.user
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UserResponse(
|
||||
@SerializedName("data") val data: UserResponseData
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.user
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UserResponseData(
|
||||
@SerializedName("state") val state: UserState
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.user
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UserState(
|
||||
@SerializedName("data") val data: UserStateData,
|
||||
@SerializedName("expiresAt") val expiresAt: String?
|
||||
)
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.model.user
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class UserStateData(
|
||||
@SerializedName("userId") val userId: String?,
|
||||
@SerializedName("contactId") val contactId: String?,
|
||||
@SerializedName("segments") val segments: List<String>?,
|
||||
@SerializedName("displays") val displays: List<Display>?,
|
||||
@SerializedName("responses") val responses: List<String>?,
|
||||
@SerializedName("lastDisplayAt") val lastDisplayAt: String?,
|
||||
@SerializedName("language") val language: String?
|
||||
)
|
||||
@@ -1,66 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.network
|
||||
|
||||
import com.formbricks.formbrickssdk.api.error.FormbricksAPIError
|
||||
import com.formbricks.formbrickssdk.helper.mapToJsonElement
|
||||
import com.formbricks.formbrickssdk.model.environment.EnvironmentDataHolder
|
||||
import com.formbricks.formbrickssdk.model.environment.EnvironmentResponse
|
||||
import com.formbricks.formbrickssdk.model.user.PostUserBody
|
||||
import com.formbricks.formbrickssdk.model.user.UserResponse
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
|
||||
open class FormbricksApiService {
|
||||
|
||||
private lateinit var retrofit: Retrofit
|
||||
|
||||
fun initialize(appUrl: String, isLoggingEnabled: Boolean) {
|
||||
retrofit = FormbricksRetrofitBuilder(appUrl, isLoggingEnabled)
|
||||
.getBuilder()
|
||||
.build()
|
||||
}
|
||||
|
||||
open fun getEnvironmentStateObject(environmentId: String): Result<EnvironmentDataHolder> {
|
||||
val result = execute {
|
||||
retrofit.create(FormbricksService::class.java)
|
||||
.getEnvironmentState(environmentId)
|
||||
}
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
val resultMap = result.getOrThrow()
|
||||
val resultJson = mapToJsonElement(resultMap).jsonObject
|
||||
val environmentResponse = json.decodeFromJsonElement<EnvironmentResponse>(resultJson)
|
||||
val data = EnvironmentDataHolder(environmentResponse.data, resultMap)
|
||||
return Result.success(data)
|
||||
}
|
||||
|
||||
open fun postUser(environmentId: String, body: PostUserBody): Result<UserResponse> {
|
||||
return execute {
|
||||
retrofit.create(FormbricksService::class.java)
|
||||
.postUser(environmentId, body)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> execute(apiCall: () -> Call<T>): Result<T> {
|
||||
val call = apiCall().execute()
|
||||
return if (call.isSuccessful) {
|
||||
val body = call.body()
|
||||
if (body == null) {
|
||||
Result.failure(RuntimeException("Invalid response"))
|
||||
|
||||
} else {
|
||||
Result.success(body)
|
||||
}
|
||||
} else {
|
||||
return try {
|
||||
val errorResponse =
|
||||
Gson().fromJson(call.errorBody()?.string(), FormbricksAPIError::class.java)
|
||||
Result.failure(errorResponse)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
//package com.formbricks.formbrickssdk.network
|
||||
//
|
||||
//import com.formbricks.formbrickssdk.api.error.FormbricksAPIError
|
||||
//import com.formbricks.formbrickssdk.model.upload.FileUploadBody
|
||||
//import com.google.gson.Gson
|
||||
//import retrofit2.Call
|
||||
//import retrofit2.Retrofit
|
||||
//
|
||||
//class FormbricksFileUploadService(appUrl: String, isLoggingEnabled: Boolean) {
|
||||
// private var retrofit: Retrofit = FormbricksRetrofitBuilder(appUrl, isLoggingEnabled)
|
||||
// .getBuilder()
|
||||
// .build()
|
||||
//
|
||||
//
|
||||
// fun uploadFile(path: String, body: FileUploadBody): Result<Map<String, *>> {
|
||||
// return execute {
|
||||
// retrofit.create(FormbricksService::class.java)
|
||||
// .uploadFile(path, body)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private inline fun <T> execute(apiCall: () -> Call<T>): Result<T> {
|
||||
// val call = apiCall().execute()
|
||||
// return if (call.isSuccessful) {
|
||||
// val body = call.body()
|
||||
// if (body == null) {
|
||||
// Result.failure(RuntimeException("Invalid response"))
|
||||
// } else {
|
||||
// Result.success(body)
|
||||
// }
|
||||
// } else {
|
||||
// return try {
|
||||
// val errorResponse =
|
||||
// Gson().fromJson(call.errorBody()?.string(), FormbricksAPIError::class.java)
|
||||
// Result.failure(errorResponse)
|
||||
// } catch (e: Exception) {
|
||||
// Result.failure(e)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.network
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class FormbricksRetrofitBuilder(private val baseUrl: String, private val loggingEnabled: Boolean) {
|
||||
|
||||
fun getBuilder(): Retrofit.Builder {
|
||||
val clientBuilder = OkHttpClient.Builder()
|
||||
.connectTimeout(CONNECT_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
||||
.readTimeout(READ_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
||||
.followSslRedirects(true)
|
||||
if (loggingEnabled) {
|
||||
val logging = HttpLoggingInterceptor()
|
||||
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
clientBuilder.addInterceptor(logging)
|
||||
}
|
||||
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(clientBuilder.build())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CONNECT_TIMEOUT_MS = 30 * 1000 // 30 seconds
|
||||
private const val READ_TIMEOUT_MS = 30 * 1000 // 30 seconds
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.network
|
||||
|
||||
import com.formbricks.formbrickssdk.model.user.PostUserBody
|
||||
import com.formbricks.formbrickssdk.model.user.UserResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface FormbricksService {
|
||||
@GET("$API_PREFIX/client/{environmentId}/environment")
|
||||
fun getEnvironmentState(@Path("environmentId") environmentId: String): Call<Map<String, Any>>
|
||||
|
||||
@POST("$API_PREFIX/client/{environmentId}/user")
|
||||
fun postUser(@Path("environmentId") environmentId: String, @Body body: PostUserBody): Call<UserResponse>
|
||||
|
||||
companion object {
|
||||
const val API_PREFIX = "/api/v2"
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.network.queue
|
||||
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
import com.formbricks.formbrickssdk.logger.Logger
|
||||
import com.formbricks.formbrickssdk.manager.UserManager
|
||||
import com.formbricks.formbrickssdk.model.error.SDKError
|
||||
import java.util.*
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
/**
|
||||
* Update queue. This class is used to queue updates to the user.
|
||||
* The given properties will be sent to the backend and updated in
|
||||
* the user object when the debounce interval is reached.
|
||||
*/
|
||||
class UpdateQueue private constructor() {
|
||||
|
||||
private var userId: String? = null
|
||||
private var attributes: MutableMap<String, String>? = null
|
||||
private var language: String? = null
|
||||
private var timer: Timer? = null
|
||||
|
||||
fun setUserId(userId: String) {
|
||||
this.userId = userId
|
||||
startDebounceTimer()
|
||||
}
|
||||
|
||||
fun setAttributes(attributes: Map<String, String>) {
|
||||
this.attributes = attributes.toMutableMap()
|
||||
startDebounceTimer()
|
||||
}
|
||||
|
||||
fun addAttribute(key: String, attribute: String) {
|
||||
if (attributes == null) {
|
||||
attributes = mutableMapOf()
|
||||
}
|
||||
attributes?.put(key, attribute)
|
||||
startDebounceTimer()
|
||||
}
|
||||
|
||||
fun setLanguage(language: String) {
|
||||
val effectiveUserId = userId ?: UserManager.userId
|
||||
|
||||
if(effectiveUserId != null) {
|
||||
addAttribute("language", language)
|
||||
startDebounceTimer()
|
||||
} else {
|
||||
Logger.d("UpdateQueue - updating language locally: ${language}")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
userId = null
|
||||
attributes = null
|
||||
language = null
|
||||
}
|
||||
|
||||
private fun startDebounceTimer() {
|
||||
timer?.cancel()
|
||||
timer = timer("debounceTimer", false, DEBOUNCE_INTERVAL, DEBOUNCE_INTERVAL) {
|
||||
commit()
|
||||
timer?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
private fun commit() {
|
||||
val effectiveUserId = userId
|
||||
?: UserManager.userId
|
||||
if (effectiveUserId == null) {
|
||||
val error = SDKError.noUserIdSetError
|
||||
Formbricks.callback?.onError(error)
|
||||
Logger.e(error)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.d("UpdateQueue - commit() called on UpdateQueue with $effectiveUserId and $attributes")
|
||||
UserManager.syncUser(effectiveUserId, attributes)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEBOUNCE_INTERVAL: Long = 500 // 500 ms
|
||||
val current: UpdateQueue = UpdateQueue()
|
||||
}
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.webview
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.OpenableColumns
|
||||
import android.util.Base64
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
import com.formbricks.formbrickssdk.R
|
||||
import com.formbricks.formbrickssdk.databinding.FragmentFormbricksBinding
|
||||
import com.formbricks.formbrickssdk.logger.Logger
|
||||
import com.formbricks.formbrickssdk.manager.SurveyManager
|
||||
import com.formbricks.formbrickssdk.model.error.SDKError
|
||||
import com.formbricks.formbrickssdk.model.javascript.FileUploadData
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.gson.JsonObject
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.util.Timer
|
||||
|
||||
|
||||
class FormbricksFragment : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: FragmentFormbricksBinding
|
||||
private lateinit var surveyId: String
|
||||
private val closeTimer = Timer()
|
||||
private val viewModel: FormbricksViewModel by viewModels()
|
||||
|
||||
private var webAppInterface = WebAppInterface(object : WebAppInterface.WebAppCallback {
|
||||
override fun onClose() {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Formbricks.callback?.onSurveyClosed()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisplayCreated() {
|
||||
Formbricks.callback?.onSurveyStarted()
|
||||
SurveyManager.onNewDisplay(surveyId)
|
||||
}
|
||||
|
||||
override fun onResponseCreated() {
|
||||
SurveyManager.postResponse(surveyId)
|
||||
}
|
||||
|
||||
override fun onFilePick(data: FileUploadData) {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.setType("*/*")
|
||||
.putExtra(Intent.EXTRA_MIME_TYPES, data.fileUploadParams.allowedExtensionsArray())
|
||||
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, data.fileUploadParams.allowMultipleFiles)
|
||||
|
||||
resultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
override fun onSurveyLibraryLoadError() {
|
||||
Formbricks.callback?.onError(SDKError.unableToLoadFormbicksJs)
|
||||
dismiss()
|
||||
}
|
||||
})
|
||||
|
||||
var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val intent: Intent? = result.data
|
||||
var uriArray: MutableList<Uri> = mutableListOf()
|
||||
|
||||
val dataString = intent?.dataString
|
||||
if (null != dataString) {
|
||||
uriArray = arrayOf(Uri.parse(dataString)).toMutableList()
|
||||
} else {
|
||||
val clipData = intent?.clipData
|
||||
if (null != clipData) {
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
val uri = clipData.getItemAt(i).uri
|
||||
uriArray.add(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val jsonArray = com.google.gson.JsonArray()
|
||||
uriArray.forEach { uri ->
|
||||
val type = activity?.contentResolver?.getType(uri)
|
||||
val fileName = getFileName(uri)
|
||||
val base64 = "data:${type};base64,${uriToBase64(uri)}"
|
||||
val json = JsonObject()
|
||||
json.addProperty("name", fileName)
|
||||
json.addProperty("type", type)
|
||||
json.addProperty("base64", base64)
|
||||
jsonArray.add(json)
|
||||
}
|
||||
binding.formbricksWebview.evaluateJavascript("""window.formbricksSurveys.onFilePick($jsonArray)""") { result ->
|
||||
print(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = FragmentFormbricksBinding.inflate(inflater).apply {
|
||||
lifecycleOwner = viewLifecycleOwner
|
||||
}
|
||||
binding.viewModel = viewModel
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
setStyle(STYLE_NO_FRAME, R.style.BottomSheetDialog)
|
||||
return super.onCreateDialog(savedInstanceState)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val view: FrameLayout = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet)!!
|
||||
view.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
val behavior = BottomSheetBehavior.from(view)
|
||||
behavior.peekHeight = resources.displayMetrics.heightPixels
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.isFitToContents = false
|
||||
behavior.setState(BottomSheetBehavior.STATE_EXPANDED)
|
||||
|
||||
dialog?.setCancelable(false)
|
||||
|
||||
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
dialog?.window?.setDimAmount(0.0f)
|
||||
binding.formbricksWebview.setBackgroundColor(Color.TRANSPARENT)
|
||||
binding.formbricksWebview.let {
|
||||
it.webChromeClient = object : WebChromeClient() {
|
||||
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
|
||||
consoleMessage?.let { cm ->
|
||||
if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
|
||||
Formbricks.callback?.onError(SDKError.surveyDisplayFetchError)
|
||||
dismiss()
|
||||
}
|
||||
val log = "[CONSOLE:${cm.messageLevel()}] \"${cm.message()}\", source: ${cm.sourceId()} (${cm.lineNumber()})"
|
||||
Logger.d(log)
|
||||
}
|
||||
return super.onConsoleMessage(consoleMessage)
|
||||
}
|
||||
}
|
||||
|
||||
it.settings.apply {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
loadWithOverviewMode = true
|
||||
useWideViewPort = true
|
||||
}
|
||||
|
||||
it.webViewClient = object : WebViewClient() {
|
||||
override fun onReceivedError(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?,
|
||||
error: WebResourceError?
|
||||
) {
|
||||
super.onReceivedError(view, request, error)
|
||||
Logger.d("WebView Error: ${error?.description}")
|
||||
}
|
||||
|
||||
override fun onPageCommitVisible(view: WebView?, url: String?) {
|
||||
dialog?.window?.setDimAmount(0.5f)
|
||||
super.onPageCommitVisible(view, url)
|
||||
}
|
||||
}
|
||||
|
||||
it.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (hasFocus) {
|
||||
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
}
|
||||
}
|
||||
|
||||
it.setInitialScale(1)
|
||||
|
||||
it.addJavascriptInterface(webAppInterface, WebAppInterface.INTERFACE_NAME)
|
||||
}
|
||||
|
||||
viewModel.loadHtml(surveyId)
|
||||
}
|
||||
|
||||
private fun getFileName(uri: Uri): String? {
|
||||
var fileName: String? = null
|
||||
activity?.contentResolver?.query(uri, null, null, null, null)?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (nameIndex != -1 && cursor.moveToFirst()) {
|
||||
fileName = cursor.getString(nameIndex)
|
||||
}
|
||||
}
|
||||
return fileName
|
||||
}
|
||||
|
||||
private fun uriToBase64(uri: Uri): String? {
|
||||
return try {
|
||||
val inputStream: InputStream? = activity?.contentResolver?.openInputStream(uri)
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val buffer = ByteArray(1024)
|
||||
var bytesRead: Int
|
||||
|
||||
while (inputStream?.read(buffer).also { bytesRead = it ?: -1 } != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead)
|
||||
}
|
||||
|
||||
inputStream?.close()
|
||||
outputStream.close()
|
||||
|
||||
Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG: String by lazy { FormbricksFragment::class.java.simpleName }
|
||||
|
||||
fun show(childFragmentManager: FragmentManager, surveyId: String) {
|
||||
val fragment = FormbricksFragment()
|
||||
fragment.surveyId = surveyId
|
||||
fragment.show(childFragmentManager, TAG)
|
||||
}
|
||||
|
||||
private const val CLOSING_TIMEOUT_IN_SECONDS = 5L
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.webview
|
||||
|
||||
import android.webkit.WebView
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
import com.formbricks.formbrickssdk.extensions.guard
|
||||
import com.formbricks.formbrickssdk.manager.SurveyManager
|
||||
import com.formbricks.formbrickssdk.manager.UserManager
|
||||
import com.formbricks.formbrickssdk.model.environment.EnvironmentDataHolder
|
||||
import com.formbricks.formbrickssdk.model.environment.getProjectStylingJson
|
||||
import com.formbricks.formbrickssdk.model.environment.getStyling
|
||||
import com.formbricks.formbrickssdk.model.environment.getSurveyJson
|
||||
import com.google.gson.JsonObject
|
||||
|
||||
/**
|
||||
* A view model for the Formbricks WebView.
|
||||
* It generates the HTML string with the necessary data to render the survey.
|
||||
*/
|
||||
class FormbricksViewModel : ViewModel() {
|
||||
var html = MutableLiveData<String>()
|
||||
|
||||
/**
|
||||
* The HTML template to render the Formbricks WebView.
|
||||
*/
|
||||
private val htmlTemplate = """
|
||||
<!doctype html>
|
||||
<html>
|
||||
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0">
|
||||
|
||||
<head>
|
||||
<title>Formbricks WebView Survey</title>
|
||||
</head>
|
||||
|
||||
<body style="overflow: hidden; height: 100vh; display: flex; flex-direction: column; justify-content: flex-end;">
|
||||
<div id="formbricks-react-native" style="width: 100%;"></div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
var json = `{{WEBVIEW_DATA}}`
|
||||
|
||||
function onClose() {
|
||||
FormbricksJavascript.message(JSON.stringify({ event: "onClose" }));
|
||||
};
|
||||
|
||||
function onDisplayCreated() {
|
||||
FormbricksJavascript.message(JSON.stringify({ event: "onDisplayCreated" }));
|
||||
};
|
||||
|
||||
function onResponseCreated() {
|
||||
FormbricksJavascript.message(JSON.stringify({ event: "onResponseCreated" }));
|
||||
};
|
||||
|
||||
function loadSurvey() {
|
||||
const options = JSON.parse(json);
|
||||
const surveyProps = {
|
||||
...options,
|
||||
onDisplayCreated,
|
||||
onResponseCreated,
|
||||
onClose,
|
||||
};
|
||||
|
||||
window.formbricksSurveys.renderSurvey(surveyProps);
|
||||
}
|
||||
|
||||
// Function to attach click listener to file inputs
|
||||
function attachFilePickerOverride() {
|
||||
const inputs = document.querySelectorAll('input[type="file"]');
|
||||
inputs.forEach(input => {
|
||||
if (!input.getAttribute('data-file-picker-overridden')) {
|
||||
input.setAttribute('data-file-picker-overridden', 'true');
|
||||
|
||||
const allowedFileExtensions = input.getAttribute('data-accept-extensions');
|
||||
const allowMultipleFiles = input.getAttribute('data-accept-multiple');
|
||||
|
||||
input.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
FormbricksJavascript.message(JSON.stringify({
|
||||
event: "onFilePick",
|
||||
fileUploadParams: {
|
||||
allowedFileExtensions: allowedFileExtensions,
|
||||
allowMultipleFiles: allowMultipleFiles === "true",
|
||||
},
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initially attach the override
|
||||
attachFilePickerOverride();
|
||||
|
||||
// Set up a MutationObserver to catch dynamically added file inputs
|
||||
const observer = new MutationObserver(function (mutations) {
|
||||
attachFilePickerOverride();
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.src = "${Formbricks.appUrl}/js/surveys.umd.cjs";
|
||||
script.async = true;
|
||||
script.onload = () => loadSurvey();
|
||||
script.onerror = (error) => {
|
||||
FormbricksJavascript.message(JSON.stringify({ event: "onSurveyLibraryLoadError" }));
|
||||
console.error("Failed to load Formbricks Surveys library:", error);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
</script>
|
||||
</html>
|
||||
"""
|
||||
|
||||
fun loadHtml(surveyId: String) {
|
||||
val environment = SurveyManager.environmentDataHolder.guard { return }
|
||||
val json = getJson(environment, surveyId)
|
||||
val htmlString = htmlTemplate.replace("{{WEBVIEW_DATA}}", json)
|
||||
html.postValue(htmlString)
|
||||
}
|
||||
|
||||
private fun getJson(environmentDataHolder: EnvironmentDataHolder, surveyId: String): String {
|
||||
val jsonObject = JsonObject()
|
||||
environmentDataHolder.getSurveyJson(surveyId).let { jsonObject.add("survey", it) }
|
||||
jsonObject.addProperty("isBrandingEnabled", true)
|
||||
jsonObject.addProperty("appUrl", Formbricks.appUrl)
|
||||
jsonObject.addProperty("environmentId", Formbricks.environmentId)
|
||||
jsonObject.addProperty("contactId", UserManager.contactId)
|
||||
jsonObject.addProperty("isWebEnvironment", false)
|
||||
|
||||
val isMultiLangSurvey =
|
||||
(environmentDataHolder.data?.data?.surveys?.first { it.id == surveyId }?.languages?.size
|
||||
?: 0) > 1
|
||||
|
||||
if (isMultiLangSurvey) {
|
||||
jsonObject.addProperty("languageCode", Formbricks.language)
|
||||
} else {
|
||||
jsonObject.addProperty("languageCode", "default")
|
||||
}
|
||||
|
||||
val hasCustomStyling = environmentDataHolder.data?.data?.surveys?.first { it.id == surveyId }?.styling != null
|
||||
val enabled = environmentDataHolder.data?.data?.project?.styling?.allowStyleOverwrite ?: false
|
||||
if (hasCustomStyling && enabled) {
|
||||
environmentDataHolder.getStyling(surveyId)?.let { jsonObject.add("styling", it) }
|
||||
} else {
|
||||
environmentDataHolder.getProjectStylingJson()?.let { jsonObject.add("styling", it) }
|
||||
}
|
||||
|
||||
return jsonObject.toString()
|
||||
.replace("#", "%23") // Hex color code's # breaks the JSON
|
||||
.replace("\\\"","'") // " is replaced to ' in the html codes in the JSON
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@BindingAdapter("htmlText")
|
||||
fun WebView.setHtmlText(htmlString: String?) {
|
||||
loadData(htmlString ?: "", "text/html", "UTF-8")
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package com.formbricks.formbrickssdk.webview
|
||||
|
||||
import android.webkit.JavascriptInterface
|
||||
import com.formbricks.formbrickssdk.Formbricks
|
||||
import com.formbricks.formbrickssdk.logger.Logger
|
||||
import com.formbricks.formbrickssdk.model.javascript.JsMessageData
|
||||
import com.formbricks.formbrickssdk.model.javascript.EventType
|
||||
import com.formbricks.formbrickssdk.model.javascript.FileUploadData
|
||||
import com.google.gson.JsonParseException
|
||||
import java.lang.RuntimeException
|
||||
|
||||
class WebAppInterface(private val callback: WebAppCallback?) {
|
||||
|
||||
interface WebAppCallback {
|
||||
fun onClose()
|
||||
fun onDisplayCreated()
|
||||
fun onResponseCreated()
|
||||
fun onFilePick(data: FileUploadData)
|
||||
fun onSurveyLibraryLoadError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Javascript interface to get messages from the WebView's embedded JS
|
||||
*/
|
||||
@JavascriptInterface
|
||||
fun message(data: String) {
|
||||
Logger.d(data)
|
||||
|
||||
try {
|
||||
val jsMessage = JsMessageData.from(data)
|
||||
when (jsMessage.event) {
|
||||
EventType.ON_CLOSE -> callback?.onClose()
|
||||
EventType.ON_DISPLAY_CREATED -> callback?.onDisplayCreated()
|
||||
EventType.ON_RESPONSE_CREATED -> callback?.onResponseCreated()
|
||||
EventType.ON_FILE_PICK -> { callback?.onFilePick(FileUploadData.from(data)) }
|
||||
EventType.ON_SURVEY_LIBRARY_LOAD_ERROR -> { callback?.onSurveyLibraryLoadError() }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Formbricks.callback?.onError(e)
|
||||
Logger.e(RuntimeException(e.message))
|
||||
} catch (e: JsonParseException) {
|
||||
Logger.e(RuntimeException("Failed to parse JSON message: $data"))
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Formbricks.callback?.onError(e)
|
||||
Logger.e(RuntimeException("Invalid message format: $data"))
|
||||
} catch (e: Exception) {
|
||||
Formbricks.callback?.onError(e)
|
||||
Logger.e(RuntimeException("Unexpected error processing message: $data"))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INTERFACE_NAME = "FormbricksJavascript"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:bind="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.formbricks.formbrickssdk.webview.FormbricksViewModel" />
|
||||
</data>
|
||||
|
||||
|
||||
<WebView
|
||||
android:id="@+id/formbricks_webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent"
|
||||
bind:htmlText="@{viewModel.html}"/>
|
||||
|
||||
</layout>
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="BottomSheetDialog" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
|
||||
<item name="bottomSheetStyle">@style/BottomSheetModal</item>
|
||||
</style>
|
||||
|
||||
<style name="BottomSheetModal" parent="Widget.Design.BottomSheet.Modal">
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,23 +0,0 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. For more details, visit
|
||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
@@ -1,59 +0,0 @@
|
||||
[versions]
|
||||
agp = "8.8.0"
|
||||
kotlin = "2.0.0"
|
||||
coreKtx = "1.10.1"
|
||||
|
||||
lifecycleRuntimeKtx = "2.6.1"
|
||||
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
|
||||
appcompat = "1.6.1"
|
||||
material = "1.10.0"
|
||||
|
||||
androidx-annotation = "1.8.0"
|
||||
|
||||
kotlinx-serialization-json = "1.8.0"
|
||||
|
||||
retrofit = "2.9.0"
|
||||
okhttp3 = "4.11.0"
|
||||
gson = "2.10.1"
|
||||
legacySupportV4 = "1.0.0"
|
||||
lifecycleLivedataKtx = "2.8.7"
|
||||
lifecycleViewmodelKtx = "2.8.7"
|
||||
fragmentKtx = "1.8.5"
|
||||
databindingCommon = "8.8.0"
|
||||
activity = "1.10.1"
|
||||
constraintlayout = "2.1.4"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
||||
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
||||
retrofit-converter-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
|
||||
okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp3" }
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
androidx-legacy-support-v4 = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupportV4" }
|
||||
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }
|
||||
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
|
||||
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
|
||||
androidx-databinding-common = { group = "androidx.databinding", name = "databinding-common", version.ref = "databindingCommon" }
|
||||
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
|
||||
BIN
packages/android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
packages/android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,6 +0,0 @@
|
||||
#Mon Feb 10 09:17:42 CET 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
185
packages/android/gradlew
vendored
185
packages/android/gradlew
vendored
@@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
packages/android/gradlew.bat
vendored
89
packages/android/gradlew.bat
vendored
@@ -1,89 +0,0 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,24 +0,0 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google {
|
||||
content {
|
||||
includeGroupByRegex("com\\.android.*")
|
||||
includeGroupByRegex("com\\.google.*")
|
||||
includeGroupByRegex("androidx.*")
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Formbricks"
|
||||
include(":app")
|
||||
include(":formbricksSDK")
|
||||
2
packages/ios/.gitignore
vendored
2
packages/ios/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
# Xcode user-specific UI state
|
||||
**/xcuserdata/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user