diff --git a/.gitignore b/.gitignore index a6b60ba..3573cac 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ Thumbs.db # Temp files temp/ /*.zip +*.uid +rc.json diff --git a/addons/godotx_firebase/export_plugin.gd b/addons/godotx_firebase/export_plugin.gd index 8b88add..28918d8 100644 --- a/addons/godotx_firebase/export_plugin.gd +++ b/addons/godotx_firebase/export_plugin.gd @@ -279,6 +279,41 @@ class AndroidExportPlugin extends EditorExportPlugin: out_file.store_buffer(content) out_file.close() + print("[Firebase] ✓ Copied google-services.json → " + dest_res_path) - print("[Firebase] Copied Android config to " + dest_res_path) + # Patch Gradle files to declare and apply the Crashlytics Gradle plugin. + # The plugin is required at build time to inject a build UUID into the APK. + # Without it the app crashes on launch when Crashlytics is enabled. + if get_option("firebase/enable_crashlytics"): + _patch_gradle_file( + "res://android/build/settings.gradle", + "id 'com.google.gms.google-services' version '4.4.2'", + "id 'com.google.gms.google-services' version '4.4.2'\n id 'com.google.firebase.crashlytics' version '3.0.3'", + "settings.gradle" + ) + _patch_gradle_file( + "res://android/build/build.gradle", + "id 'com.google.gms.google-services'", + "id 'com.google.gms.google-services'\n id 'com.google.firebase.crashlytics'", + "build.gradle" + ) + + + func _patch_gradle_file(res_path: String, needle: String, replacement: String, label: String) -> void: + if not FileAccess.file_exists(res_path): + push_warning("[Firebase] %s not found, skipping Crashlytics Gradle plugin injection" % label) + return + var f := FileAccess.open(res_path, FileAccess.READ) + var text := f.get_as_text() + f.close() + if "firebase.crashlytics" in text: + return + var patched := text.replace(needle, replacement) + if patched == text: + push_warning("[Firebase] Could not inject Crashlytics plugin into %s — pattern not found" % label) + return + var out := FileAccess.open(res_path, FileAccess.WRITE) + out.store_string(patched) + out.close() + print("[Firebase] ✓ Injected Crashlytics Gradle plugin into %s" % label) diff --git a/android/.build_version b/android/.build_version index 48e9b82..6cec3aa 100644 --- a/android/.build_version +++ b/android/.build_version @@ -1 +1 @@ -4.5.1.stable +4.6.1.stable diff --git a/export_presets.cfg b/export_presets.cfg index a3a52fb..b7eac7a 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -3,7 +3,6 @@ name="iOS" platform="iOS" runnable=true -advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" @@ -11,6 +10,11 @@ include_filter="" exclude_filter=".godot/*,android/*,ios/*,build/*,temp/*,godot/*,godot-cpp/*,extras/*,*.import" export_path="build/ios/GodotxFirebaseSample.xcodeproj" patches=PackedStringArray() +patch_delta_encoding=false +patch_delta_compression_level_zstd=19 +patch_delta_min_reduction=0.1 +patch_delta_include_filters="*" +patch_delta_exclude_filters="" encryption_include_filters="" encryption_exclude_filters="" seed=0 @@ -38,6 +42,10 @@ application/additional_plist_content="" application/icon_interpolation=4 application/export_project_only=true application/delete_old_export_files_unconditionally=false +plugins/GodotxFirebaseCrashlytics=true +plugins/GodotxFirebaseMessaging=true +plugins/GodotxFirebaseCore=true +plugins/GodotxFirebaseAnalytics=true entitlements/increased_memory_limit=false entitlements/game_center=false entitlements/push_notifications="Development" @@ -146,10 +154,10 @@ privacy/collected_data/browsing_history/collected=false privacy/collected_data/browsing_history/linked_to_user=false privacy/collected_data/browsing_history/used_for_tracking=false privacy/collected_data/browsing_history/collection_purposes=0 -privacy/collected_data/search_hhistory/collected=false -privacy/collected_data/search_hhistory/linked_to_user=false -privacy/collected_data/search_hhistory/used_for_tracking=false -privacy/collected_data/search_hhistory/collection_purposes=0 +privacy/collected_data/search_history/collected=false +privacy/collected_data/search_history/linked_to_user=false +privacy/collected_data/search_history/used_for_tracking=false +privacy/collected_data/search_history/collection_purposes=0 privacy/collected_data/user_id/collected=false privacy/collected_data/user_id/linked_to_user=false privacy/collected_data/user_id/used_for_tracking=false @@ -260,10 +268,10 @@ storyboard/custom_image@2x="" storyboard/custom_image@3x="" storyboard/use_custom_bg_color=false storyboard/custom_bg_color=Color(0, 0, 0, 1) -plugins/GodotxFirebaseCrashlytics=true -plugins/GodotxFirebaseMessaging=true -plugins/GodotxFirebaseCore=true -plugins/GodotxFirebaseAnalytics=true +privacy/collected_data/search_hhistory/collected=false +privacy/collected_data/search_hhistory/linked_to_user=false +privacy/collected_data/search_hhistory/used_for_tracking=false +privacy/collected_data/search_hhistory/collection_purposes=0 plugins/GodotxFirebase=true plugins/FirebaseBridge=true firebase/ios_config_file="res://temp/GoogleService-Info.plist" @@ -277,7 +285,6 @@ firebase/enable_core=true name="Android" platform="Android" runnable=true -advanced_options=false dedicated_server=false custom_features="" export_filter="all_resources" @@ -285,6 +292,11 @@ include_filter="" exclude_filter=".godot/*,android/*,ios/*,build/*,temp/*,godot/*,godot-cpp/*,extras/*,*.import" export_path="build/android/GodotxFirebaseSample.apk" patches=PackedStringArray() +patch_delta_encoding=false +patch_delta_compression_level_zstd=19 +patch_delta_min_reduction=0.1 +patch_delta_include_filters="*" +patch_delta_exclude_filters="" encryption_include_filters="" encryption_exclude_filters="" seed=0 @@ -309,7 +321,7 @@ architectures/arm64-v8a=true architectures/x86=false architectures/x86_64=false version/code=1 -version/name="1.0.0" +version/name="4.6.1" package/unique_name="com.godotx.firebase" package/name="" package/signed=true @@ -414,6 +426,7 @@ permissions/manage_accounts=false permissions/manage_app_tokens=false permissions/manage_documents=false permissions/manage_external_storage=false +permissions/manage_media=false permissions/master_clear=false permissions/media_content_control=false permissions/modify_audio_settings=false diff --git a/scenes/Main.tscn b/scenes/Main.tscn index af1843a..cd10f21 100644 --- a/scenes/Main.tscn +++ b/scenes/Main.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=2 format=3 uid="uid://bk7bnp6eunbxp"] +[gd_scene load_steps=3 format=3 uid="uid://bk7bnp6eunbxp"] [ext_resource type="Script" path="res://scripts/Main.gd" id="1"] +[ext_resource type="Script" path="res://scripts/TestButton.gd" id="2"] [node name="Main" type="Control"] layout_mode = 3 @@ -16,172 +17,154 @@ layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 -offset_left = 40.0 -offset_top = 140.0 -offset_right = -40.0 -offset_bottom = -60.0 grow_horizontal = 2 grow_vertical = 2 -theme_override_constants/separation = 20 +theme_override_constants/separation = 0 -[node name="Title" type="Label" parent="VBoxContainer"] +[node name="HeaderGroup" type="PanelContainer" parent="VBoxContainer"] +custom_minimum_size = Vector2(0, 100) layout_mode = 2 -theme_override_font_sizes/font_size = 48 -text = "Firebase Plugin Test" -horizontal_alignment = 1 -[node name="HSeparator" type="HSeparator" parent="VBoxContainer"] +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/HeaderGroup"] layout_mode = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_right = 20 -[node name="StatusLabel" type="Label" parent="VBoxContainer"] -custom_minimum_size = Vector2(0, 60) -layout_mode = 2 -theme_override_font_sizes/font_size = 32 -text = "Ready" -horizontal_alignment = 1 -autowrap_mode = 3 -vertical_alignment = 1 - -[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"] +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HeaderGroup/MarginContainer"] layout_mode = 2 -[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] +[node name="BackButton" type="Button" parent="VBoxContainer/HeaderGroup/MarginContainer/HBoxContainer"] +visible = false +custom_minimum_size = Vector2(80, 60) layout_mode = 2 -size_flags_vertical = 3 +size_flags_vertical = 4 +theme_override_font_sizes/font_size = 32 +text = "<" -[node name="ContentContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"] +[node name="ViewTitle" type="Label" parent="VBoxContainer/HeaderGroup/MarginContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 -size_flags_vertical = 3 -theme_override_constants/separation = 15 +theme_override_font_sizes/font_size = 42 +text = "Firebase Harness" +horizontal_alignment = 1 +vertical_alignment = 1 -[node name="CoreLabel" type="Label" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="HSeparator" type="HSeparator" parent="VBoxContainer"] layout_mode = 2 -theme_override_font_sizes/font_size = 34 -text = "🔥 Firebase Core" -[node name="InitializeButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="ContextGroup" type="MarginContainer" parent="VBoxContainer"] layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Initialize Firebase" +size_flags_vertical = 3 +theme_override_constants/margin_left = 40 +theme_override_constants/margin_top = 20 +theme_override_constants/margin_right = 40 +theme_override_constants/margin_bottom = 20 -[node name="HSeparator" type="HSeparator" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="Dashboard" type="ScrollContainer" parent="VBoxContainer/ContextGroup"] layout_mode = 2 -[node name="AnalyticsLabel" type="Label" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="List" type="VBoxContainer" parent="VBoxContainer/ContextGroup/Dashboard"] layout_mode = 2 -theme_override_font_sizes/font_size = 34 -text = "📊 Firebase Analytics" +size_flags_horizontal = 3 +theme_override_constants/separation = 20 -[node name="LogEventButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="InitializeButton" type="Button" parent="VBoxContainer/ContextGroup/Dashboard/List"] +custom_minimum_size = Vector2(0, 120) layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Log Test Event" +theme_override_font_sizes/font_size = 36 +text = "🔥 INITIALIZE FIREBASE" +script = ExtResource("2") -[node name="LogScreenButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/ContextGroup/Dashboard/List"] layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Log Screen View" +theme_override_constants/separation = 20 -[node name="HSeparator2" type="HSeparator" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="AnalyticsButton" type="Button" parent="VBoxContainer/ContextGroup/Dashboard/List"] +disabled = true +custom_minimum_size = Vector2(0, 100) layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "ANALYTICS" +script = ExtResource("2") -[node name="CrashlyticsLabel" type="Label" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="CrashlyticsButton" type="Button" parent="VBoxContainer/ContextGroup/Dashboard/List"] +disabled = true +custom_minimum_size = Vector2(0, 100) layout_mode = 2 -theme_override_font_sizes/font_size = 34 -text = "🐛 Firebase Crashlytics" +theme_override_font_sizes/font_size = 32 +text = "CRASHLYTICS" +script = ExtResource("2") -[node name="LogCrashlyticsButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="MessagingButton" type="Button" parent="VBoxContainer/ContextGroup/Dashboard/List"] +disabled = true +custom_minimum_size = Vector2(0, 100) layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Log Message" +theme_override_font_sizes/font_size = 32 +text = "MESSAGING" +script = ExtResource("2") -[node name="SetUserIDButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) -layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Set User ID" -[node name="SetCustomValueButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) -layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Set Custom Value" -[node name="ForceCrashButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="ModuleContainer" type="Control" parent="VBoxContainer/ContextGroup"] +visible = false layout_mode = 2 -theme_override_colors/font_color = Color(1, 0.3, 0.3, 1) -theme_override_font_sizes/font_size = 26 -text = "⚠ Force Crash (Test)" -[node name="HSeparator3" type="HSeparator" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="LogGroup" type="VBoxContainer" parent="VBoxContainer"] +custom_minimum_size = Vector2(0, 400) layout_mode = 2 +theme_override_constants/separation = 10 -[node name="MessagingLabel" type="Label" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/LogGroup"] layout_mode = 2 -theme_override_font_sizes/font_size = 34 -text = "💬 Firebase Messaging" -[node name="RequestPermissionButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/LogGroup"] layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Request Notification Permission" +size_flags_vertical = 3 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 10 -[node name="GetTokenButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/LogGroup/MarginContainer"] layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Get FCM Token" +theme_override_constants/separation = 10 -[node name="SubscribeTopicButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="LogTitle" type="Label" parent="VBoxContainer/LogGroup/MarginContainer/VBoxContainer"] layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Subscribe to 'test_topic'" +theme_override_font_sizes/font_size = 28 +text = "📝 Output Log" -[node name="UnsubscribeTopicButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) +[node name="LogOutput" type="TextEdit" parent="VBoxContainer/LogGroup/MarginContainer/VBoxContainer"] layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Unsubscribe from 'test_topic'" +size_flags_vertical = 3 +theme_override_font_sizes/font_size = 22 +editable = false +wrap_mode = 1 -[node name="HSeparator4" type="HSeparator" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="LogControls" type="HBoxContainer" parent="VBoxContainer/LogGroup/MarginContainer/VBoxContainer"] layout_mode = 2 +theme_override_constants/separation = 40 -[node name="LogLabel" type="Label" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="ClearLogButton" type="Button" parent="VBoxContainer/LogGroup/MarginContainer/VBoxContainer/LogControls"] +custom_minimum_size = Vector2(200, 70) layout_mode = 2 -theme_override_font_sizes/font_size = 34 -text = "📝 Output Log" +theme_override_font_sizes/font_size = 24 +text = "Clear Logs" -[node name="LogOutput" type="TextEdit" parent="VBoxContainer/ScrollContainer/ContentContainer"] +[node name="Control" type="Control" parent="VBoxContainer/LogGroup/MarginContainer/VBoxContainer/LogControls"] layout_mode = 2 -custom_minimum_size = Vector2(0, 450) size_flags_horizontal = 3 + +[node name="CopyLogButton" type="Button" parent="VBoxContainer/LogGroup/MarginContainer/VBoxContainer/LogControls"] +custom_minimum_size = Vector2(200, 70) +layout_mode = 2 theme_override_font_sizes/font_size = 24 -editable = false -wrap_mode = 1 +text = "Copy Logs" + +[connection signal="pressed" from="VBoxContainer/HeaderGroup/MarginContainer/HBoxContainer/BackButton" to="." method="show_dashboard"] +[connection signal="pressed" from="VBoxContainer/ContextGroup/Dashboard/List/InitializeButton" to="." method="_on_initialize_pressed"] +[connection signal="pressed" from="VBoxContainer/ContextGroup/Dashboard/List/AnalyticsButton" to="." method="show_module" binds= ["Analytics"]] +[connection signal="pressed" from="VBoxContainer/ContextGroup/Dashboard/List/CrashlyticsButton" to="." method="show_module" binds= ["Crashlytics"]] +[connection signal="pressed" from="VBoxContainer/ContextGroup/Dashboard/List/MessagingButton" to="." method="show_module" binds= ["Messaging"]] -[node name="ClearLogButton" type="Button" parent="VBoxContainer/ScrollContainer/ContentContainer"] -custom_minimum_size = Vector2(0, 70) -layout_mode = 2 -theme_override_font_sizes/font_size = 26 -text = "Clear Log" - -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/InitializeButton" to="." method="_on_initialize_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/LogEventButton" to="." method="_on_log_event_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/LogScreenButton" to="." method="_on_log_screen_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/LogCrashlyticsButton" to="." method="_on_log_crashlytics_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/SetUserIDButton" to="." method="_on_set_user_id_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/SetCustomValueButton" to="." method="_on_set_custom_value_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/ForceCrashButton" to="." method="_on_force_crash_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/RequestPermissionButton" to="." method="_on_request_permission_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/GetTokenButton" to="." method="_on_get_token_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/SubscribeTopicButton" to="." method="_on_subscribe_topic_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/UnsubscribeTopicButton" to="." method="_on_unsubscribe_topic_pressed"] -[connection signal="pressed" from="VBoxContainer/ScrollContainer/ContentContainer/ClearLogButton" to="." method="_on_clear_log_pressed"] +[connection signal="pressed" from="VBoxContainer/LogGroup/MarginContainer/VBoxContainer/LogControls/ClearLogButton" to="." method="_on_clear_log_pressed"] +[connection signal="pressed" from="VBoxContainer/LogGroup/MarginContainer/VBoxContainer/LogControls/CopyLogButton" to="." method="_on_copy_log_pressed"] diff --git a/scenes/view_stack/AnalyticsView.tscn b/scenes/view_stack/AnalyticsView.tscn new file mode 100644 index 0000000..09c3cae --- /dev/null +++ b/scenes/view_stack/AnalyticsView.tscn @@ -0,0 +1,38 @@ +[gd_scene load_steps=2 format=3 uid="uid://c1v8n2r5p6q7y"] + +[ext_resource type="Script" path="res://scripts/TestButton.gd" id="1"] + +[node name="AnalyticsView" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 20 + +[node name="Title" type="Label" parent="."] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Analytics Module" +horizontal_alignment = 1 + +[node name="LogEventButton" type="Button" parent="."] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "📊 Log Test Event" +script = ExtResource("1") + +[node name="LogScreenButton" type="Button" parent="."] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "📱 Log Screen View" +script = ExtResource("1") + +[node name="UserPropsButton" type="Button" parent="."] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "👤 Set User Property" +script = ExtResource("1") diff --git a/scenes/view_stack/CrashlyticsView.tscn b/scenes/view_stack/CrashlyticsView.tscn new file mode 100644 index 0000000..111458d --- /dev/null +++ b/scenes/view_stack/CrashlyticsView.tscn @@ -0,0 +1,39 @@ +[gd_scene load_steps=2 format=3 uid="uid://e4t5n8m7p2r3y"] + +[ext_resource type="Script" path="res://scripts/TestButton.gd" id="1"] + +[node name="CrashlyticsView" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 20 + +[node name="Title" type="Label" parent="."] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Crashlytics Module" +horizontal_alignment = 1 + +[node name="FatalButton" type="Button" parent="."] +modulate = Color(1, 0.4, 0.4, 1) +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "💀 Force Fatal Crash" +script = ExtResource("1") + +[node name="NonFatalButton" type="Button" parent="."] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "⚠️ Log Non-Fatal Error" +script = ExtResource("1") + +[node name="CustomValueButton" type="Button" parent="."] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "🔑 Set Custom Value" +script = ExtResource("1") diff --git a/scenes/view_stack/MessagingView.tscn b/scenes/view_stack/MessagingView.tscn new file mode 100644 index 0000000..8a244c1 --- /dev/null +++ b/scenes/view_stack/MessagingView.tscn @@ -0,0 +1,38 @@ +[gd_scene load_steps=2 format=3 uid="uid://d3r5n7m6p1q2y"] + +[ext_resource type="Script" path="res://scripts/TestButton.gd" id="1"] + +[node name="MessagingView" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_constants/separation = 20 + +[node name="Title" type="Label" parent="."] +layout_mode = 2 +theme_override_font_sizes/font_size = 32 +text = "Cloud Messaging" +horizontal_alignment = 1 + +[node name="PermissionButton" type="Button" parent="."] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "🔔 Request Permission" +script = ExtResource("1") + +[node name="SubscribeButton" type="Button" parent="."] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "➕ Subscribe to Topic" +script = ExtResource("1") + +[node name="UnsubscribeButton" type="Button" parent="."] +custom_minimum_size = Vector2(0, 100) +layout_mode = 2 +theme_override_font_sizes/font_size = 28 +text = "➖ Unsubscribe from Topic" +script = ExtResource("1") diff --git a/scripts/Main.gd b/scripts/Main.gd index 15b1efa..f7bee2a 100644 --- a/scripts/Main.gd +++ b/scripts/Main.gd @@ -1,21 +1,73 @@ extends Control + + # Firebase Singletons var core: Object = null var analytics: Object = null var crashlytics: Object = null var messaging: Object = null -# UI Elements -@onready var status_label: Label = $VBoxContainer/StatusLabel -@onready var log_output: TextEdit = $VBoxContainer/ScrollContainer/ContentContainer/LogOutput +# Navigation Elements +@onready var back_button: Button = $VBoxContainer/HeaderGroup/MarginContainer/HBoxContainer/BackButton +@onready var view_title: Label = $VBoxContainer/HeaderGroup/MarginContainer/HBoxContainer/ViewTitle + +# Views +@onready var dashboard_view: ScrollContainer = $VBoxContainer/ContextGroup/Dashboard +@onready var module_container: Control = $VBoxContainer/ContextGroup/ModuleContainer + +# Dashboard Buttons +@onready var init_btn: Button = $VBoxContainer/ContextGroup/Dashboard/List/InitializeButton +@onready var analytics_btn: Button = $VBoxContainer/ContextGroup/Dashboard/List/AnalyticsButton +@onready var crashlytics_btn: Button = $VBoxContainer/ContextGroup/Dashboard/List/CrashlyticsButton +@onready var messaging_btn: Button = $VBoxContainer/ContextGroup/Dashboard/List/MessagingButton + +# Log Elements +@onready var log_output: TextEdit = $VBoxContainer/LogGroup/MarginContainer/VBoxContainer/LogOutput + +# Dashboard button paths (used by flash_status / update_btn_status) +const INIT_PATH := "VBoxContainer/ContextGroup/Dashboard/List/InitializeButton" +const ANALYTICS_PATH := "VBoxContainer/ContextGroup/Dashboard/List/AnalyticsButton" +const CRASHLYTICS_PATH := "VBoxContainer/ContextGroup/Dashboard/List/CrashlyticsButton" +const MESSAGING_PATH := "VBoxContainer/ContextGroup/Dashboard/List/MessagingButton" + +# Tracks the module-view button currently awaiting an async signal, per module. +# The harness only permits one in-flight call per module at a time. +var _pending_call: Dictionary = { + "Analytics": "", + "Crashlytics": "", + "Messaging": "", +} func _ready() -> void: - log_message("=== Firebase Plugin Test ===") + get_viewport().size_changed.connect(_apply_safe_area) + _apply_safe_area() + log_message("=== Firebase Test Harness ===") + show_dashboard() + enable_service_buttons(false) initialize_firebase_plugins() +func _apply_safe_area() -> void: + var os_name = OS.get_name() + if os_name != "iOS" and os_name != "Android": + return + var safe_area = DisplayServer.get_display_safe_area() + var window_size = DisplayServer.window_get_size() + if safe_area.size != Vector2i.ZERO and safe_area.size != window_size: + var top_margin = safe_area.position.y + var bottom_margin = window_size.y - (safe_area.position.y + safe_area.size.y) + var left_margin = safe_area.position.x + var right_margin = window_size.x - (safe_area.position.x + safe_area.size.x) + + if has_node("VBoxContainer"): + var vbox = $VBoxContainer + vbox.offset_top = top_margin + vbox.offset_bottom = -bottom_margin + vbox.offset_left = left_margin + vbox.offset_right = -right_margin + func initialize_firebase_plugins() -> void: - # Firebase Core + # Core if Engine.has_singleton("GodotxFirebaseCore"): core = Engine.get_singleton("GodotxFirebaseCore") core.core_initialized.connect(_on_core_initialized) @@ -24,237 +76,356 @@ func initialize_firebase_plugins() -> void: else: log_message("✗ Firebase Core plugin not found") - # Firebase Analytics + # Analytics if Engine.has_singleton("GodotxFirebaseAnalytics"): analytics = Engine.get_singleton("GodotxFirebaseAnalytics") - analytics.analytics_initialized.connect(_on_analytics_initialized) - analytics.analytics_event_logged.connect(_on_event_logged) - analytics.analytics_error.connect(_on_error.bind("Analytics")) + analytics.analytics_initialized.connect(_on_module_init_done.bind("Analytics")) + analytics.analytics_event_logged.connect(_on_analytics_event_logged) + analytics.analytics_screen_logged.connect(_on_analytics_screen_logged) + analytics.analytics_property_set.connect(_on_analytics_property_set) + analytics.analytics_error.connect(_on_module_error.bind("Analytics")) log_message("✓ Firebase Analytics plugin found") else: log_message("✗ Firebase Analytics plugin not found") - # Firebase Crashlytics + # Crashlytics if Engine.has_singleton("GodotxFirebaseCrashlytics"): crashlytics = Engine.get_singleton("GodotxFirebaseCrashlytics") - crashlytics.crashlytics_initialized.connect(_on_crashlytics_initialized) - crashlytics.crashlytics_error.connect(_on_error.bind("Crashlytics")) + crashlytics.crashlytics_initialized.connect(_on_module_init_done.bind("Crashlytics")) + crashlytics.crashlytics_non_fatal_logged.connect(_on_crashlytics_non_fatal_logged) + crashlytics.crashlytics_message_logged.connect(_on_crashlytics_message_logged) + crashlytics.crashlytics_value_set.connect(_on_crashlytics_value_set) + crashlytics.crashlytics_error.connect(_on_module_error.bind("Crashlytics")) log_message("✓ Firebase Crashlytics plugin found") else: log_message("✗ Firebase Crashlytics plugin not found") - # Firebase Messaging + # Messaging if Engine.has_singleton("GodotxFirebaseMessaging"): messaging = Engine.get_singleton("GodotxFirebaseMessaging") - messaging.messaging_permission_granted.connect(_on_permission_granted) - messaging.messaging_permission_denied.connect(_on_permission_denied) - messaging.messaging_token_received.connect(_on_token_received) - messaging.messaging_apn_token_received.connect(_on_apn_token_received) - messaging.messaging_message_received.connect(_on_message_received) - messaging.messaging_error.connect(_on_error.bind("Messaging")) + messaging.messaging_initialized.connect(_on_module_init_done.bind("Messaging")) + messaging.messaging_permission_granted.connect(_on_messaging_permission_granted) + messaging.messaging_permission_denied.connect(_on_messaging_permission_denied) + messaging.messaging_token_received.connect(_on_messaging_token_received) + if OS.get_name() == "iOS": + messaging.messaging_apn_token_received.connect(_on_messaging_apn_token_received) + messaging.messaging_message_received.connect(_on_messaging_message_received) + messaging.messaging_topic_subscribed.connect(_on_messaging_topic_subscribed) + messaging.messaging_topic_unsubscribed.connect(_on_messaging_topic_unsubscribed) + messaging.messaging_error.connect(_on_module_error.bind("Messaging")) log_message("✓ Firebase Messaging plugin found") else: log_message("✗ Firebase Messaging plugin not found") +# ============== NAVIGATION ============== + +func show_dashboard() -> void: + view_title.text = "Firebase Harness" + back_button.visible = false + dashboard_view.visible = true + module_container.visible = false + for module in module_container.get_children(): + module.visible = false + +func show_module(module_name: String) -> void: + dashboard_view.visible = false + module_container.visible = true + back_button.visible = true + view_title.text = "Firebase " + module_name + + for child in module_container.get_children(): + child.queue_free() + + var node_name = module_name.replace(" ", "") + "View" + var scene_path = "res://scenes/view_stack/" + node_name + ".tscn" + + if ResourceLoader.exists(scene_path): + var scene = load(scene_path) + var instance = scene.instantiate() + module_container.add_child(instance) + instance.name = node_name + _connect_module_buttons(module_name, instance) + else: + log_message("[System] Module view '" + node_name + "' not implemented") + +# ============== HELPERS ============== + func log_message(message: String) -> void: print(message) if log_output: log_output.text += message + "\n" log_output.scroll_vertical = log_output.get_line_count() -func update_status(text: String, color: Color = Color.WHITE) -> void: - if status_label: - status_label.text = text - status_label.modulate = color +func update_btn_status(path: String, status: int) -> void: + var btn = get_node_or_null(path) + if btn and btn.has_method("update_status"): + btn.update_status(status) + +func flash_status(path: String, status: int) -> void: + update_btn_status(path, status) + +func enable_service_buttons(enabled: bool) -> void: + analytics_btn.disabled = !enabled + crashlytics_btn.disabled = !enabled + messaging_btn.disabled = !enabled + +func _module_btn_path(module_name: String, btn_name: String) -> String: + return "VBoxContainer/ContextGroup/ModuleContainer/" + module_name + "View/" + btn_name + +func _connect_module_buttons(module_name: String, instance: Node) -> void: + if module_name == "Analytics": + _connect_btn(instance, "LogEventButton", _on_log_event_pressed) + _connect_btn(instance, "LogScreenButton", _on_log_screen_pressed) + _connect_btn(instance, "UserPropsButton", _on_set_user_property_pressed) + elif module_name == "Messaging": + _connect_btn(instance, "PermissionButton", _on_request_messaging_permission_pressed) + _connect_btn(instance, "SubscribeButton", _on_subscribe_topic_pressed) + _connect_btn(instance, "UnsubscribeButton", _on_unsubscribe_topic_pressed) + elif module_name == "Crashlytics": + _connect_btn(instance, "FatalButton", _on_crash_pressed) + _connect_btn(instance, "NonFatalButton", _on_non_fatal_pressed) + _connect_btn(instance, "CustomValueButton", _on_set_custom_value_pressed) + +func _connect_btn(instance: Node, btn_name: String, method: Callable) -> void: + var btn = instance.get_node_or_null(btn_name) + if btn: btn.pressed.connect(method) # ============== CORE ============== + func _on_initialize_pressed() -> void: - if core: - log_message("\n[Core] Initializing Firebase...") - update_status("Initializing...", Color.YELLOW) - core.initialize() - else: + if not core: log_message("[Core] Plugin not available") + flash_status(INIT_PATH, TestButton.Status.FAILURE) + return + log_message("\n[Core] Initializing Firebase...") + flash_status(INIT_PATH, TestButton.Status.PENDING) + init_btn.disabled = true + core.initialize() func _on_core_initialized(success: bool) -> void: - if success: - log_message("[Core] ✓ Firebase initialized successfully!") - - # Initialize dependent modules - if crashlytics: - log_message("[Crashlytics] Initializing...") - crashlytics.initialize() - if analytics: - log_message("[Analytics] Initializing...") - analytics.initialize() - if messaging: - log_message("[Messaging] Initializing...") - messaging.initialize() - else: + init_btn.disabled = false + if not success: log_message("[Core] ✗ Firebase initialization failed") - update_status("Initialization Failed", Color.RED) + flash_status(INIT_PATH, TestButton.Status.FAILURE) + enable_service_buttons(false) + return -func _on_crashlytics_initialized(success: bool) -> void: - if success: - log_message("[Crashlytics] ✓ Initialized") - else: - log_message("[Crashlytics] ✗ Initialization failed") + log_message("[Core] ✓ Firebase initialized successfully!") + flash_status(INIT_PATH, TestButton.Status.SUCCESS) + _start_module_init_cascade() + +func _start_module_init_cascade() -> void: + if analytics: + log_message("[Analytics] Initializing...") + analytics.initialize() + if crashlytics: + log_message("[Crashlytics] Initializing...") + crashlytics.initialize() + if messaging: + log_message("[Messaging] Initializing...") + messaging.initialize() + +func _on_module_init_done(success: bool, module_name: String) -> void: + var module_btn: Button = null + match module_name: + "Analytics": + module_btn = analytics_btn + "Crashlytics": + module_btn = crashlytics_btn + "Messaging": + module_btn = messaging_btn -func _on_analytics_initialized(success: bool) -> void: if success: - log_message("[Analytics] ✓ Initialized") - update_status("Firebase Ready", Color.GREEN) + log_message("[%s] ✓ Initialized" % module_name) + if module_btn: module_btn.disabled = false else: - log_message("[Analytics] ✗ Initialization failed") + log_message("[%s] ✗ Initialization failed" % module_name) + if module_btn: module_btn.disabled = true # ============== ANALYTICS ============== + func _on_log_event_pressed() -> void: - if analytics: - var event_name = "test_button_clicked" - var params = { - "timestamp": str(Time.get_unix_time_from_system()), - "screen": "main", - "test_value": "42" - } - log_message("\n[Analytics] Logging event: " + event_name) - log_message(" Params: " + str(params)) - analytics.log_event(event_name, params) - else: + var btn_path = _module_btn_path("Analytics", "LogEventButton") + if not analytics: log_message("[Analytics] Plugin not available") + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Analytics] Logging event: test_event") + flash_status(btn_path, TestButton.Status.PENDING) + _pending_call["Analytics"] = btn_path + analytics.log_event("test_event", {"p1": "v1", "p2": 123}) func _on_log_screen_pressed() -> void: - if analytics: - var params = { - "screen_name": "main_screen", - "screen_class": "MainScene" - } - log_message("\n[Analytics] Logging screen view") - log_message(" Params: " + str(params)) - analytics.log_event("screen_view", params) - else: + var btn_path = _module_btn_path("Analytics", "LogScreenButton") + if not analytics: log_message("[Analytics] Plugin not available") - -func _on_event_logged(event_name: String) -> void: + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Analytics] Logging screen: MainScene") + flash_status(btn_path, TestButton.Status.PENDING) + _pending_call["Analytics"] = btn_path + analytics.log_screen_view("MainScene", "GodotSampleActivity") + +func _on_analytics_event_logged(event_name: String) -> void: log_message("[Analytics] ✓ Event logged: " + event_name) + _clear_pending("Analytics") -# ============== CRASHLYTICS ============== -func _on_log_crashlytics_pressed() -> void: - if crashlytics: - var message = "Test log message from Godot - " + str(Time.get_datetime_string_from_system()) - log_message("\n[Crashlytics] Logging message: " + message) - crashlytics.log_message(message) - log_message("[Crashlytics] ✓ Message logged") - else: - log_message("[Crashlytics] Plugin not available") - -func _on_set_user_id_pressed() -> void: - if crashlytics: - var user_id = "test_user_" + str(randi() % 10000) - log_message("\n[Crashlytics] Setting user ID: " + user_id) - crashlytics.set_user_id(user_id) - log_message("[Crashlytics] ✓ User ID set") - else: - log_message("[Crashlytics] Plugin not available") - -func _on_set_custom_value_pressed() -> void: - if crashlytics: - log_message("\n[Crashlytics] Setting custom values (individual + auto)...") - - # individual typed calls - crashlytics.set_custom_value_string("demo_string", "demo_value_" + str(randi() % 10000)) - crashlytics.set_custom_value_int("demo_int", randi() % 1000) - crashlytics.set_custom_value_bool("demo_bool", randi() % 2 == 0) - crashlytics.set_custom_value_float("demo_float", randf() * 100.0) - - # auto-dispatch helper - FirebaseCrashlyticsHelper.set_custom_value("demo_auto_str", "auto_" + str(randi() % 1000)) - FirebaseCrashlyticsHelper.set_custom_value("demo_auto_int", randi() % 100) - FirebaseCrashlyticsHelper.set_custom_value("demo_auto_bool", randi() % 2 == 0) - FirebaseCrashlyticsHelper.set_custom_value("demo_auto_float", randf() * 100.0) +func _on_analytics_screen_logged(screen_name: String) -> void: + log_message("[Analytics] ✓ Screen logged: " + screen_name) + _clear_pending("Analytics") - log_message("[Crashlytics] ✓ Custom values set") - else: - log_message("[Crashlytics] Plugin not available") +func _on_set_user_property_pressed() -> void: + var btn_path = _module_btn_path("Analytics", "UserPropsButton") + if not analytics: + log_message("[Analytics] Plugin not available") + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Analytics] Setting user property: test_prop = test_value") + flash_status(btn_path, TestButton.Status.PENDING) + _pending_call["Analytics"] = btn_path + analytics.set_user_property("test_prop", "test_value") -func _on_force_crash_pressed() -> void: - if crashlytics: - log_message("\n[Crashlytics] ⚠ FORCING CRASH - App will close!") - update_status("Crashing...", Color.RED) - await get_tree().create_timer(0.5).timeout - crashlytics.crash() - else: - log_message("[Crashlytics] Plugin not available") +func _on_analytics_property_set(prop_name: String) -> void: + log_message("[Analytics] ✓ Property set: " + prop_name) + _clear_pending("Analytics") # ============== MESSAGING ============== -func _on_request_permission_pressed() -> void: - if messaging: - log_message("\n[Messaging] Requesting notification permission...") - messaging.request_permission() - log_message("[Messaging] Permission request sent") - else: - log_message("[Messaging] Plugin not available") -func _on_get_token_pressed() -> void: - if messaging: - log_message("\n[Messaging] Requesting FCM token...") - update_status("Getting Token...", Color.YELLOW) - messaging.get_token() - - # Also request APNs token (iOS only) - if OS.get_name() == "iOS": - messaging.get_apns_token() - else: +func _on_request_messaging_permission_pressed() -> void: + var btn_path = _module_btn_path("Messaging", "PermissionButton") + if not messaging: log_message("[Messaging] Plugin not available") + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Messaging] Requesting permissions...") + flash_status(btn_path, TestButton.Status.PENDING) + _pending_call["Messaging"] = btn_path + messaging.request_permission() func _on_subscribe_topic_pressed() -> void: - if messaging: - var topic = "test_topic" - log_message("\n[Messaging] Subscribing to topic: " + topic) - messaging.subscribe_to_topic(topic) - log_message("[Messaging] Subscribe request sent") - else: + var btn_path = _module_btn_path("Messaging", "SubscribeButton") + if not messaging: log_message("[Messaging] Plugin not available") + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Messaging] Subscribing to: test_topic") + flash_status(btn_path, TestButton.Status.PENDING) + _pending_call["Messaging"] = btn_path + messaging.subscribe_to_topic("test_topic") func _on_unsubscribe_topic_pressed() -> void: - if messaging: - var topic = "test_topic" - log_message("\n[Messaging] Unsubscribing from topic: " + topic) - messaging.unsubscribe_from_topic(topic) - log_message("[Messaging] Unsubscribe request sent") - else: + var btn_path = _module_btn_path("Messaging", "UnsubscribeButton") + if not messaging: log_message("[Messaging] Plugin not available") + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Messaging] Unsubscribing from: test_topic") + flash_status(btn_path, TestButton.Status.PENDING) + _pending_call["Messaging"] = btn_path + messaging.unsubscribe_from_topic("test_topic") + +func _on_messaging_permission_granted() -> void: + log_message("[Messaging] ✓ Permission granted") + _clear_pending("Messaging") -func _on_permission_granted() -> void: - log_message("[Messaging] ✓ Notification permission granted") - update_status("Permission Granted", Color.GREEN) +func _on_messaging_permission_denied() -> void: + log_message("[Messaging] ✗ Permission denied") + var path: String = _pending_call.get("Messaging", "") + if path != "": + flash_status(path, TestButton.Status.FAILURE) + _pending_call["Messaging"] = "" -func _on_permission_denied() -> void: - log_message("[Messaging] ⓘ Notification permission denied") - log_message(" User declined or disabled notifications in system settings") - update_status("Permission Denied", Color.ORANGE) +func _on_messaging_topic_subscribed(topic: String) -> void: + log_message("[Messaging] ✓ Subscribed to: " + topic) + _clear_pending("Messaging") -func _on_token_received(token: String) -> void: - log_message("[Messaging] ✓ FCM Token received:") - log_message(" " + token) - update_status("Token Received", Color.GREEN) +func _on_messaging_topic_unsubscribed(topic: String) -> void: + log_message("[Messaging] ✓ Unsubscribed from: " + topic) + _clear_pending("Messaging") -func _on_apn_token_received(token: String) -> void: - log_message("[Messaging] ✓ APN Device Token received:") - log_message(" " + token) - update_status("APN Token Received", Color.GREEN) +func _on_messaging_token_received(token: String) -> void: + log_message("[Messaging] Token: " + token) -func _on_message_received(title: String, body: String) -> void: - log_message("[Messaging] ✓ Message received:") - log_message(" Title: " + title) - log_message(" Body: " + body) +func _on_messaging_apn_token_received(token: String) -> void: + log_message("[Messaging] APNs Token: " + token) + +func _on_messaging_message_received(title: String, body: String) -> void: + log_message("[Messaging] Message received: " + title + " — " + body) + +# ============== CRASHLYTICS ============== + +func _on_crash_pressed() -> void: + var btn_path = _module_btn_path("Crashlytics", "FatalButton") + if not crashlytics: + log_message("[Crashlytics] Plugin not available") + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Crashlytics] !!! FORCING FATAL CRASH !!!") + flash_status(btn_path, TestButton.Status.PENDING) + # If the crash truly propagates, the app terminates and the yellow state is lost. + # If the exception is caught by Godot's dispatcher, the button stays yellow — a + # visible hint that the crash did not actually take down the process. + crashlytics.crash() + +func _on_non_fatal_pressed() -> void: + var btn_path = _module_btn_path("Crashlytics", "NonFatalButton") + if not crashlytics: + log_message("[Crashlytics] Plugin not available") + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Crashlytics] Logging non-fatal error") + flash_status(btn_path, TestButton.Status.PENDING) + _pending_call["Crashlytics"] = btn_path + crashlytics.log_non_fatal_exception("This is a test non-fatal error") + +func _on_set_custom_value_pressed() -> void: + var btn_path = _module_btn_path("Crashlytics", "CustomValueButton") + if not crashlytics: + log_message("[Crashlytics] Plugin not available") + flash_status(btn_path, TestButton.Status.FAILURE) + return + log_message("\n[Crashlytics] Setting custom value") + flash_status(btn_path, TestButton.Status.PENDING) + crashlytics.set_custom_value_string("test_key", "test_value") + _pending_call["Crashlytics"] = btn_path + +func _on_crashlytics_non_fatal_logged(message: String) -> void: + log_message("[Crashlytics] ✓ Non-fatal logged: " + message) + _clear_pending("Crashlytics") + +func _on_crashlytics_message_logged(message: String) -> void: + log_message("[Crashlytics] ✓ Message logged: " + message) + _clear_pending("Crashlytics") + +func _on_crashlytics_value_set(key: String) -> void: + log_message("[Crashlytics] ✓ Value set for: " + key) + _clear_pending("Crashlytics") + +# ============== ERRORS ============== -# ============== GENERAL ============== func _on_error(message: String, module: String) -> void: log_message("[" + module + "] ✗ Error: " + message) - update_status("Error: " + message, Color.RED) + +func _on_module_error(message: String, module_name: String) -> void: + log_message("[%s] ✗ Error: %s" % [module_name, message]) + var path: String = _pending_call.get(module_name, "") + if path != "": + flash_status(path, TestButton.Status.FAILURE) + _pending_call[module_name] = "" + +func _clear_pending(module_name: String) -> void: + var path: String = _pending_call.get(module_name, "") + if path != "": + flash_status(path, TestButton.Status.SUCCESS) + _pending_call[module_name] = "" + +# ============== LOG CONTROLS ============== func _on_clear_log_pressed() -> void: + if log_output: log_output.text = "" + log_message("=== Log Cleared ===") + +func _on_copy_log_pressed() -> void: if log_output: - log_output.text = "" - log_message("=== Log Cleared ===") - update_status("Ready", Color.WHITE) + DisplayServer.clipboard_set(log_output.text) + log_message("[System] Log copied to clipboard") diff --git a/scripts/TestButton.gd b/scripts/TestButton.gd new file mode 100644 index 0000000..1149a50 --- /dev/null +++ b/scripts/TestButton.gd @@ -0,0 +1,34 @@ +extends Button +class_name TestButton + +enum Status { + IDLE, + PENDING, + SUCCESS, + FAILURE +} + +@export var reset_time: float = 3.0 +var _timer: SceneTreeTimer = null + +func _ready() -> void: + update_status(Status.IDLE) + +func update_status(status: int) -> void: + match status: + Status.IDLE: + self_modulate = Color.WHITE + Status.PENDING: + self_modulate = Color.YELLOW + Status.SUCCESS: + self_modulate = Color.GREEN + Status.FAILURE: + self_modulate = Color.RED + _start_reset_timer() + +func _start_reset_timer() -> void: + if _timer: + _timer = null # Cancel previous timer by letting it die + + _timer = get_tree().create_timer(reset_time) + _timer.timeout.connect(func(): update_status(Status.IDLE)) diff --git a/source/android/firebase_analytics/src/main/java/com/godotx/firebase/analytics/FirebaseAnalyticsPlugin.kt b/source/android/firebase_analytics/src/main/java/com/godotx/firebase/analytics/FirebaseAnalyticsPlugin.kt index ab9965d..86e44d0 100644 --- a/source/android/firebase_analytics/src/main/java/com/godotx/firebase/analytics/FirebaseAnalyticsPlugin.kt +++ b/source/android/firebase_analytics/src/main/java/com/godotx/firebase/analytics/FirebaseAnalyticsPlugin.kt @@ -36,6 +36,14 @@ class FirebaseAnalyticsPlugin(godot: Godot) : GodotPlugin(godot) { "analytics_event_logged", String::class.java ), + SignalInfo( + "analytics_screen_logged", + String::class.java + ), + SignalInfo( + "analytics_property_set", + String::class.java + ), SignalInfo( "analytics_error", String::class.java @@ -63,6 +71,47 @@ class FirebaseAnalyticsPlugin(godot: Godot) : GodotPlugin(godot) { } } + @UsedByGodot + fun log_screen_view(screen_name: String, screen_class: String) { + val analytics = firebaseAnalytics + if (analytics == null) { + Log.e(TAG, "Firebase Analytics not initialized") + emitSignal("analytics_error", "analytics_not_initialized") + return + } + + try { + val bundle = Bundle() + bundle.putString(FirebaseAnalytics.Param.SCREEN_NAME, screen_name) + bundle.putString(FirebaseAnalytics.Param.SCREEN_CLASS, screen_class) + analytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, bundle) + Log.d(TAG, "Screen view logged: $screen_name ($screen_class)") + emitSignal("analytics_screen_logged", screen_name) + } catch (e: Exception) { + Log.e(TAG, "Failed to log screen view", e) + emitSignal("analytics_error", e.message ?: "screen_log_error") + } + } + + @UsedByGodot + fun set_user_property(name: String, value: String?) { + val analytics = firebaseAnalytics + if (analytics == null) { + Log.e(TAG, "Firebase Analytics not initialized") + emitSignal("analytics_error", "analytics_not_initialized") + return + } + + try { + analytics.setUserProperty(name, value) + Log.d(TAG, "User property set: $name = $value") + emitSignal("analytics_property_set", name) + } catch (e: Exception) { + Log.e(TAG, "Failed to set user property", e) + emitSignal("analytics_error", e.message ?: "property_set_error") + } + } + @UsedByGodot fun log_event(event_name: String, params: Dictionary) { val analytics = firebaseAnalytics diff --git a/source/android/firebase_crashlytics/src/main/java/com/godotx/firebase/crashlytics/FirebaseCrashlyticsPlugin.kt b/source/android/firebase_crashlytics/src/main/java/com/godotx/firebase/crashlytics/FirebaseCrashlyticsPlugin.kt index 443e207..f80060b 100644 --- a/source/android/firebase_crashlytics/src/main/java/com/godotx/firebase/crashlytics/FirebaseCrashlyticsPlugin.kt +++ b/source/android/firebase_crashlytics/src/main/java/com/godotx/firebase/crashlytics/FirebaseCrashlyticsPlugin.kt @@ -28,6 +28,15 @@ class FirebaseCrashlyticsPlugin(godot: Godot) : GodotPlugin(godot) { SignalInfo("crashlytics_initialized", Boolean::class.javaObjectType ), + SignalInfo("crashlytics_non_fatal_logged", + String::class.java + ), + SignalInfo("crashlytics_message_logged", + String::class.java + ), + SignalInfo("crashlytics_value_set", + String::class.java + ), SignalInfo("crashlytics_error", String::class.java ) @@ -54,6 +63,25 @@ class FirebaseCrashlyticsPlugin(godot: Godot) : GodotPlugin(godot) { crash!!.length } + @UsedByGodot + fun log_non_fatal_exception(message: String) { + val crashlyticsInstance = crashlytics + if (crashlyticsInstance == null) { + Log.e(TAG, "Firebase Crashlytics not initialized") + emitSignal("crashlytics_error", "crashlytics_not_initialized") + return + } + + try { + crashlyticsInstance.recordException(Exception(message)) + Log.d(TAG, "Recorded non-fatal exception: $message") + emitSignal("crashlytics_non_fatal_logged", message) + } catch (e: Exception) { + Log.e(TAG, "Failed to record non-fatal exception", e) + emitSignal("crashlytics_error", e.message ?: "non_fatal_log_error") + } + } + @UsedByGodot fun log_message(message: String) { val crashlyticsInstance = crashlytics @@ -66,6 +94,7 @@ class FirebaseCrashlyticsPlugin(godot: Godot) : GodotPlugin(godot) { try { crashlyticsInstance.log(message) Log.d(TAG, "Logged message to Crashlytics: $message") + emitSignal("crashlytics_message_logged", message) } catch (e: Exception) { Log.e(TAG, "Failed to log message", e) emitSignal("crashlytics_error", e.message ?: "log_error") @@ -102,6 +131,7 @@ class FirebaseCrashlyticsPlugin(godot: Godot) : GodotPlugin(godot) { try { crashlyticsInstance.setCustomKey(key, value) Log.d(TAG, "Set custom value: $key = $value") + emitSignal("crashlytics_value_set", key) } catch (e: Exception) { Log.e(TAG, "Failed to set custom value", e) emitSignal("crashlytics_error", e.message ?: "set_custom_value_error") @@ -120,6 +150,7 @@ class FirebaseCrashlyticsPlugin(godot: Godot) : GodotPlugin(godot) { try { crashlyticsInstance.setCustomKey(key, value.toLong()) Log.d(TAG, "Set custom value: $key = $value") + emitSignal("crashlytics_value_set", key) } catch (e: Exception) { Log.e(TAG, "Failed to set custom value", e) emitSignal("crashlytics_error", e.message ?: "set_custom_value_error") @@ -138,6 +169,7 @@ class FirebaseCrashlyticsPlugin(godot: Godot) : GodotPlugin(godot) { try { crashlyticsInstance.setCustomKey(key, value) Log.d(TAG, "Set custom value: $key = $value") + emitSignal("crashlytics_value_set", key) } catch (e: Exception) { Log.e(TAG, "Failed to set custom value", e) emitSignal("crashlytics_error", e.message ?: "set_custom_value_error") @@ -156,6 +188,7 @@ class FirebaseCrashlyticsPlugin(godot: Godot) : GodotPlugin(godot) { try { crashlyticsInstance.setCustomKey(key, value.toDouble()) Log.d(TAG, "Set custom value: $key = $value") + emitSignal("crashlytics_value_set", key) } catch (e: Exception) { Log.e(TAG, "Failed to set custom value", e) emitSignal("crashlytics_error", e.message ?: "set_custom_value_error") diff --git a/source/android/firebase_messaging/src/main/java/com/godotx/firebase/messaging/FirebaseMessagingPlugin.kt b/source/android/firebase_messaging/src/main/java/com/godotx/firebase/messaging/FirebaseMessagingPlugin.kt index c1fa6ff..f1c7379 100644 --- a/source/android/firebase_messaging/src/main/java/com/godotx/firebase/messaging/FirebaseMessagingPlugin.kt +++ b/source/android/firebase_messaging/src/main/java/com/godotx/firebase/messaging/FirebaseMessagingPlugin.kt @@ -88,6 +88,9 @@ class FirebaseMessagingPlugin(godot: Godot) : GodotPlugin(godot) { override fun getPluginSignals(): Set { return setOf( + SignalInfo("messaging_initialized", + Boolean::class.javaObjectType + ), SignalInfo("messaging_permission_granted"), SignalInfo("messaging_permission_denied"), SignalInfo("messaging_token_received", @@ -97,6 +100,12 @@ class FirebaseMessagingPlugin(godot: Godot) : GodotPlugin(godot) { String::class.java, String::class.java ), + SignalInfo("messaging_topic_subscribed", + String::class.java + ), + SignalInfo("messaging_topic_unsubscribed", + String::class.java + ), SignalInfo("messaging_error", String::class.java ) @@ -125,6 +134,7 @@ class FirebaseMessagingPlugin(godot: Godot) : GodotPlugin(godot) { if (ctx == null) { Log.e(TAG, "initialize: activity is null") + emitSignal("messaging_initialized", false) emitSignal("messaging_error", "activity_null") return } @@ -134,6 +144,7 @@ class FirebaseMessagingPlugin(godot: Godot) : GodotPlugin(godot) { if (apps.isEmpty()) { Log.e(TAG, "Firebase is NOT initialized") + emitSignal("messaging_initialized", false) emitSignal("messaging_error", "firebase_not_initialized") return } @@ -141,6 +152,7 @@ class FirebaseMessagingPlugin(godot: Godot) : GodotPlugin(godot) { Log.d(TAG, "Firebase Messaging initialized (${apps.size} Firebase app(s) found)") isInitialized = true + emitSignal("messaging_initialized", true) // Emit any notification that was received before initialization (cold start or early resume) coldStartIntent?.let { handleIntentMessage(it) } @@ -149,6 +161,7 @@ class FirebaseMessagingPlugin(godot: Godot) : GodotPlugin(godot) { deferredToken = null } catch (e: Exception) { Log.e(TAG, "Firebase initialization check failed", e) + emitSignal("messaging_initialized", false) emitSignal("messaging_error", e.message ?: "firebase_check_failed") } } @@ -206,14 +219,10 @@ class FirebaseMessagingPlugin(godot: Godot) : GodotPlugin(godot) { fun subscribe_to_topic(topic: String) { try { FirebaseMessaging.getInstance().subscribeToTopic(topic) - .addOnCompleteListener { task -> - if (task.isSuccessful) { - Log.d(TAG, "Subscribed to topic: $topic") - } else { - Log.e(TAG, "Failed to subscribe to topic", task.exception) - emitSignal("messaging_error", task.exception?.message ?: "subscribe_failed") - } - } + .addOnSuccessListener { Log.d(TAG, "Subscribed to topic (server confirmed): $topic") } + .addOnFailureListener { e -> Log.e(TAG, "Subscribe server sync failed for $topic", e) } + Log.d(TAG, "Subscribe to topic queued: $topic") + emitSignal("messaging_topic_subscribed", topic) } catch (e: Exception) { Log.e(TAG, "Error subscribing to topic", e) emitSignal("messaging_error", e.message ?: "subscribe_error") @@ -224,14 +233,10 @@ class FirebaseMessagingPlugin(godot: Godot) : GodotPlugin(godot) { fun unsubscribe_from_topic(topic: String) { try { FirebaseMessaging.getInstance().unsubscribeFromTopic(topic) - .addOnCompleteListener { task -> - if (task.isSuccessful) { - Log.d(TAG, "Unsubscribed from topic: $topic") - } else { - Log.e(TAG, "Failed to unsubscribe from topic", task.exception) - emitSignal("messaging_error", task.exception?.message ?: "unsubscribe_failed") - } - } + .addOnSuccessListener { Log.d(TAG, "Unsubscribed from topic (server confirmed): $topic") } + .addOnFailureListener { e -> Log.e(TAG, "Unsubscribe server sync failed for $topic", e) } + Log.d(TAG, "Unsubscribe from topic queued: $topic") + emitSignal("messaging_topic_unsubscribed", topic) } catch (e: Exception) { Log.e(TAG, "Error unsubscribing from topic", e) emitSignal("messaging_error", e.message ?: "unsubscribe_error") diff --git a/source/ios/firebase_analytics/Sources/godotx_firebase_analytics.h b/source/ios/firebase_analytics/Sources/godotx_firebase_analytics.h index fd7d916..1a24f34 100644 --- a/source/ios/firebase_analytics/Sources/godotx_firebase_analytics.h +++ b/source/ios/firebase_analytics/Sources/godotx_firebase_analytics.h @@ -17,6 +17,8 @@ class GodotxFirebaseAnalytics : public Object { void initialize(); void log_event(String event_name, Dictionary params); + void log_screen_view(String screen_name, String screen_class); + void set_user_property(String name, String value); GodotxFirebaseAnalytics(); ~GodotxFirebaseAnalytics(); diff --git a/source/ios/firebase_analytics/Sources/godotx_firebase_analytics.mm b/source/ios/firebase_analytics/Sources/godotx_firebase_analytics.mm index b7d5f68..4228b4c 100644 --- a/source/ios/firebase_analytics/Sources/godotx_firebase_analytics.mm +++ b/source/ios/firebase_analytics/Sources/godotx_firebase_analytics.mm @@ -10,9 +10,13 @@ void GodotxFirebaseAnalytics::_bind_methods() { ClassDB::bind_method(D_METHOD("initialize"), &GodotxFirebaseAnalytics::initialize); ClassDB::bind_method(D_METHOD("log_event", "event_name", "params"), &GodotxFirebaseAnalytics::log_event); + ClassDB::bind_method(D_METHOD("log_screen_view", "screen_name", "screen_class"), &GodotxFirebaseAnalytics::log_screen_view); + ClassDB::bind_method(D_METHOD("set_user_property", "name", "value"), &GodotxFirebaseAnalytics::set_user_property); ADD_SIGNAL(MethodInfo("analytics_initialized", PropertyInfo(Variant::BOOL, "success"))); ADD_SIGNAL(MethodInfo("analytics_event_logged", PropertyInfo(Variant::STRING, "event_name"))); + ADD_SIGNAL(MethodInfo("analytics_screen_logged", PropertyInfo(Variant::STRING, "screen_name"))); + ADD_SIGNAL(MethodInfo("analytics_property_set", PropertyInfo(Variant::STRING, "name"))); ADD_SIGNAL(MethodInfo("analytics_error", PropertyInfo(Variant::STRING, "message"))); } @@ -49,6 +53,44 @@ emit_signal("analytics_initialized", true); } +void GodotxFirebaseAnalytics::log_screen_view(String screen_name, String screen_class) { + NSLog(@"[GodotxFirebaseAnalytics] log_screen_view: %s (%s)", screen_name.utf8().get_data(), screen_class.utf8().get_data()); + + @try { + NSString* nsScreenName = [NSString stringWithUTF8String:screen_name.utf8().get_data()]; + NSString* nsScreenClass = [NSString stringWithUTF8String:screen_class.utf8().get_data()]; + + [FIRAnalytics logEventWithName:kFIREventScreenView + parameters:@{ + kFIRParameterScreenName: nsScreenName, + kFIRParameterScreenClass: nsScreenClass + }]; + + emit_signal("analytics_screen_logged", screen_name); + } + @catch (NSException *exception) { + NSLog(@"[GodotxFirebaseAnalytics] Failed to log screen view: %@", exception.reason); + emit_signal("analytics_error", String::utf8([exception.reason UTF8String])); + } +} + +void GodotxFirebaseAnalytics::set_user_property(String name, String value) { + NSLog(@"[GodotxFirebaseAnalytics] set_user_property: %s = %s", name.utf8().get_data(), value.utf8().get_data()); + + @try { + NSString* nsName = [NSString stringWithUTF8String:name.utf8().get_data()]; + NSString* nsValue = [NSString stringWithUTF8String:value.utf8().get_data()]; + + [FIRAnalytics setUserPropertyString:nsValue forName:nsName]; + + emit_signal("analytics_property_set", name); + } + @catch (NSException *exception) { + NSLog(@"[GodotxFirebaseAnalytics] Failed to set user property: %@", exception.reason); + emit_signal("analytics_error", String::utf8([exception.reason UTF8String])); + } +} + void GodotxFirebaseAnalytics::log_event(String event_name, Dictionary params) { NSLog(@"[GodotxFirebaseAnalytics] log_event: %s", event_name.utf8().get_data()); diff --git a/source/ios/firebase_crashlytics/Sources/godotx_firebase_crashlytics.h b/source/ios/firebase_crashlytics/Sources/godotx_firebase_crashlytics.h index 30eee57..e3f521d 100644 --- a/source/ios/firebase_crashlytics/Sources/godotx_firebase_crashlytics.h +++ b/source/ios/firebase_crashlytics/Sources/godotx_firebase_crashlytics.h @@ -17,6 +17,7 @@ class GodotxFirebaseCrashlytics : public Object { void initialize(); void crash(); + void log_non_fatal_exception(String message); void log_message(String message); void set_user_id(String user_id); void set_custom_value_string(String key, String value); diff --git a/source/ios/firebase_crashlytics/Sources/godotx_firebase_crashlytics.mm b/source/ios/firebase_crashlytics/Sources/godotx_firebase_crashlytics.mm index 87b4a07..630cc30 100644 --- a/source/ios/firebase_crashlytics/Sources/godotx_firebase_crashlytics.mm +++ b/source/ios/firebase_crashlytics/Sources/godotx_firebase_crashlytics.mm @@ -10,6 +10,7 @@ void GodotxFirebaseCrashlytics::_bind_methods() { ClassDB::bind_method(D_METHOD("initialize"), &GodotxFirebaseCrashlytics::initialize); ClassDB::bind_method(D_METHOD("crash"), &GodotxFirebaseCrashlytics::crash); + ClassDB::bind_method(D_METHOD("log_non_fatal_exception", "message"), &GodotxFirebaseCrashlytics::log_non_fatal_exception); ClassDB::bind_method(D_METHOD("log_message", "message"), &GodotxFirebaseCrashlytics::log_message); ClassDB::bind_method(D_METHOD("set_user_id", "user_id"), &GodotxFirebaseCrashlytics::set_user_id); ClassDB::bind_method(D_METHOD("set_custom_value_string", "key", "value"), &GodotxFirebaseCrashlytics::set_custom_value_string); @@ -18,6 +19,9 @@ ClassDB::bind_method(D_METHOD("set_custom_value_float", "key", "value"), &GodotxFirebaseCrashlytics::set_custom_value_float); ADD_SIGNAL(MethodInfo("crashlytics_initialized", PropertyInfo(Variant::BOOL, "success"))); + ADD_SIGNAL(MethodInfo("crashlytics_non_fatal_logged", PropertyInfo(Variant::STRING, "message"))); + ADD_SIGNAL(MethodInfo("crashlytics_message_logged", PropertyInfo(Variant::STRING, "message"))); + ADD_SIGNAL(MethodInfo("crashlytics_value_set", PropertyInfo(Variant::STRING, "key"))); ADD_SIGNAL(MethodInfo("crashlytics_error", PropertyInfo(Variant::STRING, "message"))); } @@ -34,11 +38,28 @@ @[][1]; } +void GodotxFirebaseCrashlytics::log_non_fatal_exception(String message) { + @try { + NSString* nsMessage = [NSString stringWithUTF8String:message.utf8().get_data()]; + NSError* error = [NSError errorWithDomain:@"GodotxFirebaseHarness" + code:0 + userInfo:@{NSLocalizedDescriptionKey: nsMessage}]; + [[FIRCrashlytics crashlytics] recordError:error]; + NSLog(@"[GodotxFirebaseCrashlytics] Recorded non-fatal exception: %@", nsMessage); + emit_signal("crashlytics_non_fatal_logged", message); + } + @catch (NSException *exception) { + NSLog(@"[GodotxFirebaseCrashlytics] Failed to record non-fatal: %@", exception.reason); + emit_signal("crashlytics_error", String::utf8([exception.reason UTF8String])); + } +} + void GodotxFirebaseCrashlytics::log_message(String message) { @try { NSString* nsMessage = [NSString stringWithUTF8String:message.utf8().get_data()]; [[FIRCrashlytics crashlytics] log:nsMessage]; NSLog(@"[GodotxFirebaseCrashlytics] Logged message: %@", nsMessage); + emit_signal("crashlytics_message_logged", message); } @catch (NSException *exception) { NSLog(@"[GodotxFirebaseCrashlytics] Failed to log message: %@", exception.reason); @@ -64,6 +85,7 @@ NSString* nsValue = [NSString stringWithUTF8String:value.utf8().get_data()]; [[FIRCrashlytics crashlytics] setCustomValue:nsValue forKey:nsKey]; NSLog(@"[GodotxFirebaseCrashlytics] Set custom value: %@ = %@", nsKey, nsValue); + emit_signal("crashlytics_value_set", key); } @catch (NSException *exception) { NSLog(@"[GodotxFirebaseCrashlytics] Failed to set custom value: %@", exception.reason); @@ -76,6 +98,7 @@ NSString* nsKey = [NSString stringWithUTF8String:key.utf8().get_data()]; [[FIRCrashlytics crashlytics] setCustomValue:@(value) forKey:nsKey]; NSLog(@"[GodotxFirebaseCrashlytics] Set custom value: %@ = %lld", nsKey, value); + emit_signal("crashlytics_value_set", key); } @catch (NSException *exception) { NSLog(@"[GodotxFirebaseCrashlytics] Failed to set custom value: %@", exception.reason); @@ -88,6 +111,7 @@ NSString* nsKey = [NSString stringWithUTF8String:key.utf8().get_data()]; [[FIRCrashlytics crashlytics] setCustomValue:@(value) forKey:nsKey]; NSLog(@"[GodotxFirebaseCrashlytics] Set custom value: %@ = %d", nsKey, value); + emit_signal("crashlytics_value_set", key); } @catch (NSException *exception) { NSLog(@"[GodotxFirebaseCrashlytics] Failed to set custom value: %@", exception.reason); @@ -100,6 +124,7 @@ NSString* nsKey = [NSString stringWithUTF8String:key.utf8().get_data()]; [[FIRCrashlytics crashlytics] setCustomValue:@(value) forKey:nsKey]; NSLog(@"[GodotxFirebaseCrashlytics] Set custom value: %@ = %f", nsKey, value); + emit_signal("crashlytics_value_set", key); } @catch (NSException *exception) { NSLog(@"[GodotxFirebaseCrashlytics] Failed to set custom value: %@", exception.reason); diff --git a/source/ios/firebase_messaging/Sources/godotx_firebase_messaging.mm b/source/ios/firebase_messaging/Sources/godotx_firebase_messaging.mm index 1fd3940..8018e30 100644 --- a/source/ios/firebase_messaging/Sources/godotx_firebase_messaging.mm +++ b/source/ios/firebase_messaging/Sources/godotx_firebase_messaging.mm @@ -40,11 +40,14 @@ - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSStrin ClassDB::bind_method(D_METHOD("subscribe_to_topic", "topic"), &GodotxFirebaseMessaging::subscribe_to_topic); ClassDB::bind_method(D_METHOD("unsubscribe_from_topic", "topic"), &GodotxFirebaseMessaging::unsubscribe_from_topic); + ADD_SIGNAL(MethodInfo("messaging_initialized", PropertyInfo(Variant::BOOL, "success"))); ADD_SIGNAL(MethodInfo("messaging_permission_granted")); ADD_SIGNAL(MethodInfo("messaging_permission_denied")); ADD_SIGNAL(MethodInfo("messaging_token_received", PropertyInfo(Variant::STRING, "token"))); ADD_SIGNAL(MethodInfo("messaging_apn_token_received", PropertyInfo(Variant::STRING, "token"))); ADD_SIGNAL(MethodInfo("messaging_message_received", PropertyInfo(Variant::STRING, "title"), PropertyInfo(Variant::STRING, "body"))); + ADD_SIGNAL(MethodInfo("messaging_topic_subscribed", PropertyInfo(Variant::STRING, "topic"))); + ADD_SIGNAL(MethodInfo("messaging_topic_unsubscribed", PropertyInfo(Variant::STRING, "topic"))); ADD_SIGNAL(MethodInfo("messaging_error", PropertyInfo(Variant::STRING, "message"))); } @@ -69,6 +72,8 @@ - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSStrin if (![FIRApp defaultApp]) { NSLog(@"[GodotxFirebaseMessaging] Firebase core not ready"); + emit_signal("messaging_initialized", false); + emit_signal("messaging_error", String("firebase_not_initialized")); return; } @@ -82,6 +87,7 @@ - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSStrin [[GodotxAPNDelegate shared] activateNotificationCenterDelegate]; NSLog(@"[GodotxFirebaseMessaging] Initialized"); + emit_signal("messaging_initialized", true); } void GodotxFirebaseMessaging::request_permission() { @@ -255,6 +261,11 @@ - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSStrin }); } else { NSLog(@"[GodotxFirebaseMessaging] Successfully subscribed to topic: %@", nsTopic); + dispatch_async(dispatch_get_main_queue(), ^{ + if (GodotxFirebaseMessaging::instance) { + GodotxFirebaseMessaging::instance->emit_signal("messaging_topic_subscribed", String::utf8([nsTopic UTF8String])); + } + }); } }]; } @@ -275,6 +286,11 @@ - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSStrin }); } else { NSLog(@"[GodotxFirebaseMessaging] Successfully unsubscribed from topic: %@", nsTopic); + dispatch_async(dispatch_get_main_queue(), ^{ + if (GodotxFirebaseMessaging::instance) { + GodotxFirebaseMessaging::instance->emit_signal("messaging_topic_unsubscribed", String::utf8([nsTopic UTF8String])); + } + }); } }]; }