diff --git a/README.md b/README.md index d5ad227..80a7f8b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ***PushupProgression*** ![Made with Godot](https://img.shields.io/badge/Made%20with-Godot-3776AB.svg?style=plastic&logo=godot-engine&logoColor=white) -![Version](https://img.shields.io/badge/version-1.2.0-00BB1A.svg?style=plastic) +![Version](https://img.shields.io/badge/version-1.3.0-00BB1A.svg?style=plastic) An app for tracking pushup progress and reaching daily goals. Designed for smartphones. @@ -14,10 +14,10 @@ Created with [Godot Game Engine [4.3.dev5]](https://godotengine.org/). * ~~Implement pop-up confirmation for resetting saved progression~~ * ~~Provide reset options for saved progression~~ * ~~Custom daily goals & pushups per session option~~ -* Visual theme update * ~~Integrate logging system for saving and loading processes~~ -* Add notification for saved progression status +* ~~Add notification for saved progression status~~ * Show previous progression +* Visual theme update ## Usage diff --git a/app_screenshot.png b/app_screenshot.png index 0a2b4a7..3b352eb 100644 Binary files a/app_screenshot.png and b/app_screenshot.png differ diff --git a/global/global_variables.gd b/global/global_variables.gd index 5e2d597..114c10a 100644 --- a/global/global_variables.gd +++ b/global/global_variables.gd @@ -76,7 +76,7 @@ const MAIN_SCENE_PATH: String = "res://src/main/main.tscn" ## Logging menu scene file path. const LOGGING_MENU_SCENE_PATH: String = "res://src/ui/options_menu/logging_menu/logging_menu.tscn" -## App running flag [bool]. +## App running flag. var app_running: bool = false @@ -86,13 +86,13 @@ var daily_pushups_goal: int = 100 ## Number of pushups in each session. var pushups_per_session: int = 10 -## Total pushups. +## Total pushups today. var total_pushups_today: int = 0 -## Total pushups sessions. +## Total pushups sessions today. var total_pushups_sessions: int = 0 -## Remaining pushup to reach daily goal. +## Remaining pushups to reach daily goal. var remaining_pushups: int = 0 ## Save data [Dictionary] to store settings and progression data. @@ -106,5 +106,52 @@ var save_data_dict: Dictionary = { "calendar": {} } -## Logs [Array]. +## Stores log messages. var logs_array: Array = [] + + +## Create log message. [br] +## +## [br] +## +## See [method SaveSystem.create_log] for more details. +func create_log(log_message: String) -> void: + # Create log + get_tree().call_group("save_system", "create_log", log_message) + + +## Create notification. [br] +## +## [br] +## +## See [method NotificationSystem.create_notification] for more details. +func create_notification(notification_text: String, extended_duration: bool = false) -> void: + # Create notification + get_tree().call_group("notification_system", "create_notification", notification_text, extended_duration) + + +## Update UI. [br] +## +## [br] +## +## See [method UIManager.update_ui] for more details. +func update_ui() -> void: + get_tree().call_group("ui_manager", "update_ui") + + +## Save data. [br] +## +## [br] +## +## See [method SaveSystem.save_data] for more details. +func save_data() -> void: + get_tree().call_group("save_system", "save_data") + + +## Load data. [br] +## +## [br] +## +## See [method SaveSystem.load_data] for more details. +func load_data() -> void: + get_tree().call_group("save_system", "load_data") diff --git a/src/main/main.gd b/src/main/main.gd index e11bcb2..667568f 100644 --- a/src/main/main.gd +++ b/src/main/main.gd @@ -1,4 +1,4 @@ -## Main Scene Script. +## Main Scene. ## ## Path: [code]res://src/main/main.gd[/code] @@ -14,24 +14,35 @@ func _ready(): # Load save file if game is not running if GlobalVariables.app_running == false: - get_tree().call_group("save_system", "load_data") + GlobalVariables.load_data() # Update UI - get_tree().call_group("ui_manager", "update_ui") - + GlobalVariables.update_ui() + ## Change window size to 480x800 if screen height is less than 1280 pixels -## (Used for 1920x1080 monitors) [br] -## Position window in middle of screen after resizing. [br][br] +## (Used for 1920x1080 monitors). [br] +## Position window in center of screen after resizing. [br] +## +## [br] +## ## [b]Note:[/b] Used for PC. func change_window_size(): - var current_screen_size: Vector2i = DisplayServer.screen_get_size(0) - var window_size_hdpi = Vector2i(480, 800) + # Get primary screen id/index + var primary_monitor_id: int = DisplayServer.get_primary_screen() + # Get primary screen size + var screen_size: Vector2i = DisplayServer.screen_get_size(primary_monitor_id) + # Define desired window size + var window_size: Vector2i = Vector2i(480, 800) - # Change resolution if necessary - if current_screen_size.y < 1280: - DisplayServer.window_set_size(window_size_hdpi) - # Position window in center of screen - var new_x_pos = (current_screen_size.x * 1.5) - (DisplayServer.window_get_size().x / 2) - var new_y_pos = (current_screen_size.y / 2) - (DisplayServer.window_get_size().y / 2) - DisplayServer.window_set_position(Vector2i(new_x_pos, new_y_pos)) + # Change resolution if primary screen height is less than 1280 pixels + if screen_size.y < 1280: + # Set desired window size + DisplayServer.window_set_size(window_size) + # Calculate window center position + #var center_x: int = int((screen_size.x - window_size.x) / 2.0) + var center_x: int = int((screen_size.x - window_size.x) * 1.833) + var center_y: int = int((screen_size.y - window_size.y) / 2.0) + + # Position window in center + DisplayServer.window_set_position(Vector2i(center_x, center_y)) diff --git a/src/main/main.tscn b/src/main/main.tscn index 389d8b3..ac5a97c 100644 --- a/src/main/main.tscn +++ b/src/main/main.tscn @@ -1,17 +1,34 @@ -[gd_scene load_steps=4 format=3 uid="uid://b8m5sfw05s6ty"] +[gd_scene load_steps=7 format=3 uid="uid://b8m5sfw05s6ty"] [ext_resource type="Script" path="res://src/main/main.gd" id="1_ui10i"] [ext_resource type="PackedScene" uid="uid://bv2v88irqyogj" path="res://src/ui/ui.tscn" id="2_06dc6"] [ext_resource type="Script" path="res://src/utilities/save_system.gd" id="3_vaodl"] +[ext_resource type="Script" path="res://src/utilities/notification_system.gd" id="4_caoxb"] + +[sub_resource type="Animation" id="Animation_pjprj"] +resource_name = "show_notification" +length = 0.2 + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_ac6sq"] +_data = { +"show_notification": SubResource("Animation_pjprj") +} [node name="Main" type="Node"] script = ExtResource("1_ui10i") [node name="UI" parent="." instance=ExtResource("2_06dc6")] -[node name="ProgressionController" type="Node" parent="."] - [node name="Utilities" type="Node" parent="."] [node name="SaveSystem" type="Node" parent="Utilities" groups=["save_system"]] script = ExtResource("3_vaodl") + +[node name="NotificationSystem" type="Node" parent="Utilities" groups=["notification_system"]] +script = ExtResource("4_caoxb") + +[node name="NotificationAnimationPlayer" type="AnimationPlayer" parent="Utilities/NotificationSystem"] +unique_name_in_owner = true +libraries = { +"": SubResource("AnimationLibrary_ac6sq") +} diff --git a/src/ui/notification/notification.tscn b/src/ui/notification/notification.tscn new file mode 100644 index 0000000..417b11f --- /dev/null +++ b/src/ui/notification/notification.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=4 format=3 uid="uid://fjatxhusdrmj"] + +[ext_resource type="Script" path="res://src/ui/notification/notification_manager.gd" id="1_bopgf"] + +[sub_resource type="Animation" id="Animation_7vkrs"] +resource_name = "show_notification" +length = 1.1 +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath(".:visible") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0), +"transitions": PackedFloat32Array(1), +"update": 1, +"values": [true] +} +tracks/1/type = "method" +tracks/1/imported = false +tracks/1/enabled = true +tracks/1/path = NodePath(".") +tracks/1/interp = 1 +tracks/1/loop_wrap = true +tracks/1/keys = { +"times": PackedFloat32Array(0.9, 1.1), +"transitions": PackedFloat32Array(1, 1), +"values": [{ +"args": [], +"method": &"hide_ui" +}, { +"args": [], +"method": &"remove_ui" +}] +} + +[sub_resource type="AnimationLibrary" id="AnimationLibrary_0m8h3"] +_data = { +"show_notification": SubResource("Animation_7vkrs") +} + +[node name="Notification" type="CanvasLayer"] +script = ExtResource("1_bopgf") + +[node name="PanelContainer" type="PanelContainer" parent="."] +anchors_preset = -1 +anchor_left = 0.4 +anchor_top = 0.94 +anchor_right = 0.6 +anchor_bottom = 0.975 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 7 +theme_override_constants/margin_top = 5 +theme_override_constants/margin_right = 7 +theme_override_constants/margin_bottom = 5 + +[node name="NotificationLabel" type="Label" parent="PanelContainer/MarginContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "Notification Text..." +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] +unique_name_in_owner = true +libraries = { +"": SubResource("AnimationLibrary_0m8h3") +} diff --git a/src/ui/notification/notification_manager.gd b/src/ui/notification/notification_manager.gd new file mode 100644 index 0000000..4595be5 --- /dev/null +++ b/src/ui/notification/notification_manager.gd @@ -0,0 +1,52 @@ +## Notification Manager. +## +## Controls the behavior of notification. [br] +## +## [br] +## +## Path: [code]res://src/ui/notification/notification_manager.gd[/code] + + +class_name NotificationManager +extends CanvasLayer + + +## Notification animation player. +@onready var animation_player: AnimationPlayer = %AnimationPlayer + +## Signal for removing notification. +signal notification_removed + +## Extend notificatio duration. +var extend_duration: bool = false + + +## Show notification. +func show_ui() -> void: + # Play notification animation + animation_player.play("show_notification") + + +## Hide notification. +func hide_ui() -> void: + # Change visibility + visible = false + + +## Remove notification. (Removes scene from tree). +func remove_ui() -> void: + # Emit notification removed signal + notification_removed.emit() + # Delete scene from tree + get_parent().remove_child(self) + queue_free() + + +# Called when the node enters the scene tree for the first time. +func _ready(): + # Hide notification UI as default + hide_ui() + # Check if extend duration is true + if extend_duration == true: + # Extend animation play speed + animation_player.speed_scale = 0.5 diff --git a/src/ui/options_menu/logging_menu/logging_menu_controller.gd b/src/ui/options_menu/logging_menu/logging_menu_controller.gd index cde2330..299681a 100644 --- a/src/ui/options_menu/logging_menu/logging_menu_controller.gd +++ b/src/ui/options_menu/logging_menu/logging_menu_controller.gd @@ -1,6 +1,6 @@ ## Logging Menu Controller. ## -## Creates logs and controls the behavior of logging menu. [br] +## Creates log messages and controls the behavior of logging menu. [br] ## ## [br] ## @@ -49,9 +49,6 @@ func create_logs_ui() -> void: logs_container.remove_child(base_log_label) base_log_label.queue_free() - print("Ready") - print(GlobalVariables.logs_array) - # Called when the node enters the scene tree for the first time. func _ready() -> void: diff --git a/src/ui/options_menu/settings_menu/settings_menu_manager.gd b/src/ui/options_menu/settings_menu/settings_menu_manager.gd index 443afde..acf789c 100644 --- a/src/ui/options_menu/settings_menu/settings_menu_manager.gd +++ b/src/ui/options_menu/settings_menu/settings_menu_manager.gd @@ -36,32 +36,64 @@ func close_menu() -> void: ## On [member SettingsMenuManager.close_menu_button] pressed. func _on_close_menu_button_pressed() -> void: + #region: Daily goal input # Search daily goal input text for digits var daily_goal_input_text = regex.search(daily_goal_input.text) + # Update daily goal, if digits if daily_goal_input_text: GlobalVariables.daily_pushups_goal = int(daily_goal_input.text) - print("Daily goal updated to: %s" % daily_goal_input.text) + # Create log message + var log_message: String = "Daily goal updated to: %s" % daily_goal_input.text + # Create log + GlobalVariables.create_log(log_message) + # Create notification with extended duration + GlobalVariables.create_notification(log_message, true) + # If no input, pass elif daily_goal_input.text.is_empty(): - print("No changes to daily goal were made.") + pass + # Else, create notification else: - print("Invalid value for daily goal: %s. Only digits are allowed."\ - % str(daily_goal_input.text)) + # Create notification text + var notification_text: String = "Invalid value for daily goal: %s\n\ + Only digits allowed." % str(daily_goal_input.text) + # Create notification with extended duration + GlobalVariables.create_notification(notification_text, true) + #endregion + #region: Pushups per session input # Search pushups per session input text for digits var pushups_per_session_input_text = regex.search(pushups_per_session_input.text) + # Update pushups per session, if digits if pushups_per_session_input_text: GlobalVariables.pushups_per_session = int(pushups_per_session_input.text) - print("Pushups per session updated to: %s" % pushups_per_session_input.text) + # Create log message + var log_message: String = "Pushups per session updated to: %s" % pushups_per_session_input.text + # Create log + GlobalVariables.create_log(log_message) + # Create notification with extended duration + GlobalVariables.create_notification(log_message, true) + # If no input, pass elif pushups_per_session_input.text.is_empty(): - print("No changes to pushups per session were made.") + pass + # Else, create notification else: - print("Invalid value for pushups per session: %s. Only digits allowed."\ - % str(pushups_per_session_input.text)) + # Create notification text + var notification_text: String = "Invalid value for pushups per session: %s\n\ + Only digits allowed." % str(pushups_per_session_input.text) + # Create notification with extended duration + GlobalVariables.create_notification(notification_text, true) + #endregion - # Save progression data - get_tree().call_group("save_system", "save_data") - # Update UI - get_tree().call_group("ui_manager", "update_ui") + # If no input, create notification with extended duration + if daily_goal_input.text.is_empty() and pushups_per_session_input.text.is_empty(): + GlobalVariables.create_notification("No changes to settings were made.", true) + + # If valid input, save data and update UI + elif daily_goal_input_text or pushups_per_session_input_text: + # Save data + GlobalVariables.save_data() + # Update UI + GlobalVariables.update_ui() # Close settings menu close_menu() diff --git a/src/ui/ui_manager.gd b/src/ui/ui_manager.gd index 1624889..641488e 100644 --- a/src/ui/ui_manager.gd +++ b/src/ui/ui_manager.gd @@ -176,9 +176,9 @@ func _on_add_pushups_button_pressed() -> void: # Update total sessions GlobalVariables.total_pushups_sessions += 1 # Create log message - get_tree().call_group("save_system", "create_log", "Added new pushup session.") + GlobalVariables.create_log("Added new pushup session.") # Save data - get_tree().call_group("save_system", "save_data") + GlobalVariables.save_data() # Update UI update_ui() diff --git a/src/utilities/notification_system.gd b/src/utilities/notification_system.gd new file mode 100644 index 0000000..e7d45a4 --- /dev/null +++ b/src/utilities/notification_system.gd @@ -0,0 +1,69 @@ +## Notification System. +## +## Displays notifications. [br] +## +## [br] +## +## Path: [code]res://src/utilities/notification_system.gd[/code] + + +class_name NotificationSystem +extends Node + + +## Notification scene. +const NOTIFICATION_SCENE: PackedScene = preload("res://src/ui/notification/notification.tscn") + +## Notification animation player. +@onready var notification_animation_player: AnimationPlayer = %NotificationAnimationPlayer + +## Queue for storing pending notifications. +var notifications_queue: Array = [] + +## Current notification. +var current_notification: CanvasLayer = null + + +## Create notification. [br] +## Extends duration if [param extend_duration] is [code]true[/code]. +func create_notification(notification_text: String, extend_duration: bool = false) -> void: + # Instantiate notification scene + var notification_node: CanvasLayer = NOTIFICATION_SCENE.instantiate() + # Set notification text label + notification_node.get_node("%NotificationLabel").text = notification_text + # Apply extended duration if requested + if extend_duration == true: + notification_node.extend_duration = true + + # Set and show current notification, if none + if current_notification == null: + current_notification = notification_node + show_notification() + # Else, add notification to queue + else: + notifications_queue.append(notification_node) + + +## Show notification. +func show_notification() -> void: + # Add notification to tree + add_child(current_notification) + # Connect to notification removed signal + current_notification.notification_removed.connect(show_next_notification) + # Show notification ui + current_notification.show_ui() + + +## Show next notification. +func show_next_notification() -> void: + # Reset current notification + current_notification = null + + # Check if any notifications in queue + if notifications_queue.size() > 0: + # Get next notification and remove from queue + var next_notification: CanvasLayer = notifications_queue.pop_front() + # Set new current notification + current_notification = next_notification + # Show notification + show_notification() diff --git a/src/utilities/save_system.gd b/src/utilities/save_system.gd index 1636bbe..0fe2a80 100644 --- a/src/utilities/save_system.gd +++ b/src/utilities/save_system.gd @@ -57,46 +57,12 @@ func create_log(log_message: String) -> void: # Create timestamp var timestamp: String = "[%s:%s:%s] " % [hour, minute, second] - # Cretae full log message + # Create full log message var full_log_message: String = timestamp + log_message # Add to logs array GlobalVariables.logs_array.append(full_log_message) -## Create save file. [br] -## -## [br] -## -## Save file path: [constant SaveSystem.SAVE_GAME_PATH] [br] -func create_save_file() -> void: - create_log("No existing save file. \ - Creating new save file, " + SAVE_FILE + ", at " + SAVE_GAME_PATH) - - # Create new save data dictionary - create_save_data_dict() - - # Create new save file - var new_save_file = FileAccess.open(SAVE_GAME_PATH + SAVE_FILE, FileAccess.WRITE) - - # Check for file errors - if new_save_file == null: - # Get error text - var error_text: String = get_file_error_message(FileAccess.get_open_error()) - create_log("Error creating new save file. " + error_text) - return - - create_log("Converting data to JSON.") - # Convert save data dictionary to JSON - var json_string = JSON.stringify(GlobalVariables.save_data_dict, "\t") - - create_log("Writing data to " + SAVE_FILE + " at " + SAVE_GAME_PATH) - # Store the save data dictionary as a new line in the save file. - new_save_file.store_line(json_string) - - # File saved successfully - create_log("New save file created and data saved successfully.") - - ## Create [Dictionary] for the current day's data. func create_current_day_data_dict() -> Dictionary: # Initialize dictionary for current day @@ -142,7 +108,6 @@ func create_save_data_dict() -> void: # Save calendar dictionary to save data dictionary GlobalVariables.save_data_dict["calendar"] = calendar_dict - # Save daily pushups goal GlobalVariables.save_data_dict["settings"]["daily_pushups_goal"] = GlobalVariables.daily_pushups_goal # Save pushups per session @@ -151,6 +116,50 @@ func create_save_data_dict() -> void: create_log("Data dictionary created successfully.") +## Create save file. [br] +## +## [br] +## +## Save file path: [constant SaveSystem.SAVE_GAME_PATH] +func create_save_file() -> void: + # Create log + create_log("No existing save file. \ + Creating new save file, " + SAVE_FILE + ", at " + SAVE_GAME_PATH) + # Create notification with extended duration + GlobalVariables.create_notification("No existing save file...", true) + + # Create new save data dictionary + create_save_data_dict() + + # Create new save file + var new_save_file = FileAccess.open(SAVE_GAME_PATH + SAVE_FILE, FileAccess.WRITE) + + # Check for file errors + if new_save_file == null: + # Get error text + var error_text: String = get_file_error_message(FileAccess.get_open_error()) + # Create log + create_log("Error creating new save file. " + error_text) + # Create notification text + var notification_text: String = "Error creating new save file\n" + "See logs for more details." + # Create notification with extended duration + GlobalVariables.create_notification(notification_text, true) + return + + create_log("Converting data to JSON.") + # Convert save data dictionary to JSON + var json_string = JSON.stringify(GlobalVariables.save_data_dict, "\t") + + create_log("Writing data to " + SAVE_FILE + " at " + SAVE_GAME_PATH) + # Store the save data dictionary as a new line in the save file. + new_save_file.store_line(json_string) + + # Create log + create_log("New save file created and data saved successfully.") + # Create notification with extended duration + GlobalVariables.create_notification("New save file created successfully!", true) + + ## Return an error message as [String] based on [param file_error] value. func get_file_error_message(file_error: Error) -> String: # Create error info text @@ -215,7 +224,12 @@ func save_data() -> void: if save_file == null: # Get error text var error_text: String = get_file_error_message(FileAccess.get_open_error()) + # Create log create_log("Error opening save file. " + error_text) + # Create notification text + var notification_text: String = "Error opening save file\n" + "See logs for more details." + # Create notification with extended duration + GlobalVariables.create_notification(notification_text, true) return # Get datetime dictionary from system @@ -276,7 +290,10 @@ func save_data() -> void: # Store the save data dictionary as a new line in the save file save_file.store_line(json_string) + # Create log create_log("Data saved successfully.") + # Create notification + GlobalVariables.create_notification("Saved successfully!") ## Load settings. [br][br] @@ -324,7 +341,12 @@ func load_data() -> void: if save_file == null: # Get error text var error_text: String = get_file_error_message(FileAccess.get_open_error()) + # Create log create_log("Error opening save file. " + error_text) + # Create notification text + var notification_text: String = "Error opening save file.\n" + "See logs for more details." + # Create notification with extended duration + GlobalVariables.create_notification(notification_text, true) return # Create JSON helper @@ -336,15 +358,19 @@ func load_data() -> void: create_log("Converting data from save file.") # Parse (convert) JSON text - var save_file_data = json.parse_string(json_string) + var save_file_data = JSON.parse_string(json_string) # If parse (convert) error occurs if save_file_data == null: # Create error message var error_message: String = "JSON Parse Error: " + json.get_error_message()\ + " in " + json_string + " at line " + json.get_error_line() - + # Create log create_log("Error converting data from save file. " + error_message) + # Create notification text + var notification_text: String = "Error converting data from save file\n" + "See logs for more details." + # Create notification with extended duration + GlobalVariables.create_notification(notification_text, true) return # If save data is dictionary, create copy @@ -354,12 +380,8 @@ func load_data() -> void: # Load settings load_settings(settings_dict) - # Create new years dictionary + # Create years dictionary var years_dict: Dictionary = {} - # Create new months dictionary - var months_dict: Dictionary = {} - # Create new days dictionary - var days_dict: Dictionary = {} # Create new dictionary for each year. for year in save_file_data["calendar"]: @@ -455,7 +477,11 @@ func load_data() -> void: # Save created dictionary to save_data_dict GlobalVariables.save_data_dict["calendar"] = years_dict + + # Create log create_log("Loaded data from save file successfully.") + # Create notification + GlobalVariables.create_notification("Loaded successfully!") ## Reset global values. [br] @@ -531,5 +557,7 @@ func reset_data(reset_option: String) -> void: _: create_log("Unsupported reset option: " + reset_option) + # Create notification + GlobalVariables.create_notification("Reset successfully!") # Save data save_data()