Please make the tray icon transform into a spinning animation while refreshing a new wallpaper. Please make sure the effect is fancy and seamless. Below is a example of the current code. Please explain any code changes needed.

```python
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
gi.require_version('GLib', '2.0')
from gi.repository import Gtk, GdkPixbuf, Gio, Gdk, AppIndicator3, Pango, GLib
import requests
import subprocess
import schedule
import time
import logging
import json
import os
import urllib.parse
from datetime import datetime, timedelta
import pytz
import threading
import socket
import webbrowser

# Set up logging
logging.basicConfig(level=logging.DEBUG)

__version__ = "0.1.007"  # Version number

SETTINGS_DIR = os.path.join(os.path.expanduser("~"), ".daily-backdrop")
SETTINGS_PATH = os.path.join(SETTINGS_DIR, "settings.json")

class SettingsDialog(Gtk.Dialog):
    def __init__(self, parent, selected_collection="Minimalist Black", selected_resolution="Full", 
                 refresh_time="00:05", custom_refresh_enabled=False):
        Gtk.Dialog.__init__(self, title="Daily Backdrop Settings", transient_for=parent, flags=0)
        self.add_buttons(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY,
                         Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                         Gtk.STOCK_OK, Gtk.ResponseType.OK)

        self.set_default_response(Gtk.ResponseType.CANCEL)

        # Create a ScrolledWindow to make the content scrollable
        scrolled_window = Gtk.ScrolledWindow()
        scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        scrolled_window.set_vexpand(True)

        content_area = self.get_content_area()
        content_area.pack_start(scrolled_window, True, True, 0)

        # Create vertical box for content
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, margin=10)
        scrolled_window.add(vbox)

        # Recent Wallpaper Section
        recent_wallpaper_frame = Gtk.Frame(label="Recent Wallpaper")
        vbox.pack_start(recent_wallpaper_frame, expand=False, fill=False, padding=10)

        self.recent_wallpaper_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, margin=10)
        recent_wallpaper_frame.add(self.recent_wallpaper_box)

        # Load and display the most recent wallpaper
        recent_wallpaper = self.get_most_recent_wallpaper()
        if recent_wallpaper:
            self.display_wallpaper_preview(recent_wallpaper)

        # Wallpaper Category (Collections)
        self.category_frame = Gtk.Frame(label="Collections")
        vbox.pack_start(self.category_frame, expand=False, fill=False, padding=5)

        # Horizontal scrollable box for categories
        category_scrolled_window = Gtk.ScrolledWindow()
        category_scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER)
        category_scrolled_window.set_hexpand(True)

        category_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10, margin=10)
        category_scrolled_window.add(category_box)
        self.category_frame.add(category_scrolled_window)

        # Create radio buttons for category selection
        self.category_radio_buttons = []
        categories = [
            {"name": "Minimalist Black", "image_path": "/usr/local/bin/minimalist_black_example.jpg"},
            {"name": "Minimalist Purple", "image_path": "/usr/local/bin/minimalist_purple_example.jpg"},
            {"name": "Minimalist White", "image_path": "/usr/local/bin/minimalist_white_example.jpg"},
            {"name": "Traditional Art", "image_path": "/usr/local/bin/traditional_art_example.jpg"}
        ]

        max_width = 200  # Maximum image width

        # Create radio buttons and images for each category
        for i, category in enumerate(categories):
            vbox_category = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
            event_box = Gtk.EventBox()
            event_box.add(vbox_category)
            category_box.pack_start(event_box, expand=False, fill=False, padding=0)

            # Create the image
            image = Gtk.Image.new_from_file(category["image_path"])
            pixbuf = image.get_pixbuf()
            if pixbuf.get_width() > max_width:  # Scale the image if it's too wide
                scaled_pixbuf = pixbuf.scale_simple(max_width, max_width * pixbuf.get_height() / pixbuf.get_width(), GdkPixbuf.InterpType.BILINEAR)
                image.set_from_pixbuf(scaled_pixbuf)

            vbox_category.pack_start(image, expand=False, fill=False, padding=0)

            # Create the radio button
            radio_button = Gtk.RadioButton(label=category["name"]) if i == 0 else Gtk.RadioButton(label=category["name"], group=self.category_radio_buttons[0])
            if category["name"] == selected_collection:
                radio_button.set_active(True)
            vbox_category.pack_start(radio_button, expand=False, fill=False, padding=0)
            self.category_radio_buttons.append(radio_button)

            # Connect event box to handle clicks
            event_box.connect("button-press-event", self.on_event_box_clicked, radio_button)

        # Advanced Settings Expander
        self.expander_advanced = CenteredLabelExpander("Advanced Settings")
        vbox.pack_start(self.expander_advanced, expand=False, fill=False, padding=10)

        frame_advanced = Gtk.Frame()
        vbox_advanced = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, margin=10)
        frame_advanced.add(vbox_advanced)
        self.expander_advanced.add(frame_advanced)

        # Refresh Time Settings
        frame_time = Gtk.Frame(label="Refresh Time Settings")
        vbox_advanced.pack_start(frame_time, expand=False, fill=True, padding=10)

        time_vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, margin=10)
        frame_time.add(time_vbox)

        # **New: Next Refresh Time Label**
        self.next_refresh_label = Gtk.Label()
        self.next_refresh_label.set_halign(Gtk.Align.START)  # Align to the left
        self.next_refresh_label.set_xalign(0)  # Ensure alignment to the left within the label
        time_vbox.pack_start(self.next_refresh_label, False, False, padding=10)

        # Checkbox for Custom Refresh Time
        self.custom_refresh_checkbox = Gtk.CheckButton(label="Enable Custom Refresh Time")
        time_vbox.pack_start(self.custom_refresh_checkbox, False, False, 0)
        self.custom_refresh_checkbox.set_active(custom_refresh_enabled)
        self.custom_refresh_checkbox.connect("toggled", self.on_custom_refresh_toggled)

        # Time Input (Hour, Minute, AM/PM)
        time_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)

        adjustment_hour = Gtk.Adjustment(lower=1, upper=12, step_increment=1)
        self.spin_hour = Gtk.SpinButton(adjustment=adjustment_hour, climb_rate=1, digits=0)
        self.spin_hour.set_value(int(refresh_time.split(":")[0]) % 12 or 12)
        self.spin_hour.set_sensitive(custom_refresh_enabled)
        time_box.pack_start(self.spin_hour, False, False, 0)

        label_colon = Gtk.Label(label=":")
        time_box.pack_start(label_colon, False, False, 0)

        self.spin_hour.connect("scroll-event", self.on_scroll_event_block)

        adjustment_minute = Gtk.Adjustment(lower=0, upper=59, step_increment=1)
        self.spin_minute = Gtk.SpinButton(adjustment=adjustment_minute, climb_rate=1, digits=0)
        self.spin_minute.set_value(int(refresh_time.split(":")[1]))
        self.spin_minute.set_sensitive(custom_refresh_enabled)
        time_box.pack_start(self.spin_minute, False, False, 0)
        self.spin_minute.connect("scroll-event", self.on_scroll_event_block)

        # AM/PM ComboBox
        self.combo_ampm = Gtk.ComboBoxText()
        self.combo_ampm.append_text("AM")
        self.combo_ampm.append_text("PM")
        current_hour = int(refresh_time.split(":")[0])
        self.combo_ampm.set_active(0 if current_hour < 12 else 1)
        self.combo_ampm.set_sensitive(custom_refresh_enabled)
        time_box.pack_start(self.combo_ampm, False, False, 0)
        self.combo_ampm.connect("scroll-event", self.on_scroll_event_block)

        time_vbox.pack_start(time_box, False, False, 0)

        # Reset Button
        reset_button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        self.reset_time_button = Gtk.Button(label="Reset to Default Time")
        self.reset_time_button.set_sensitive(custom_refresh_enabled)
        self.reset_time_button.connect("clicked", self.on_reset_time)
        reset_button_box.pack_start(self.reset_time_button, False, False, 0)
        time_vbox.pack_start(reset_button_box, False, False, 0)

        # Initialize the label with the next refresh time
        self.update_next_refresh_label()

        # Connect signals to update the next refresh time when settings change
        self.spin_hour.connect("value-changed", self.on_time_changed)
        self.spin_minute.connect("value-changed", self.on_time_changed)
        self.combo_ampm.connect("changed", self.on_time_changed)
        self.reset_time_button.connect("clicked", self.on_time_changed)

        # Resolution Settings
        frame_resolution = Gtk.Frame(label="Resolution Settings")
        hbox_resolution = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6, margin=10)
        self.combo_resolution = Gtk.ComboBoxText()
        self.combo_resolution.append_text("Full")
        self.combo_resolution.append_text("1920x1080")
        self.combo_resolution.append_text("1366x768")
        self.combo_resolution.set_active(self.get_index_from_text(self.combo_resolution, selected_resolution))

        hbox_resolution.pack_start(self.combo_resolution, expand=False, fill=False, padding=0)
        frame_resolution.add(hbox_resolution)
        vbox_advanced.pack_start(frame_resolution, expand=False, fill=False, padding=0)

        self.combo_resolution.connect("scroll-event", self.on_scroll_event_block)

        # Custom URL Mode
        self.check_custom_url = Gtk.CheckButton(label="Custom URL Mode")
        self.label_custom_url = Gtk.Label(label="Custom URL:")
        self.label_custom_url.set_sensitive(False)
        self.entry_custom_url = Gtk.Entry()
        self.entry_custom_url.set_sensitive(False)
        self.label_category_param = Gtk.Label(label="Category Parameter:")
        self.label_category_param.set_sensitive(False)
        self.entry_category_param = Gtk.Entry()
        self.entry_category_param.set_sensitive(False)

        # Layout for Custom URL fields
        hbox_custom_url = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6, margin=10)
        hbox_custom_url.pack_start(self.check_custom_url, expand=False, fill=False, padding=0)
        hbox_custom_url.pack_start(self.label_custom_url, expand=False, fill=False, padding=0)
        hbox_custom_url.pack_end(self.entry_custom_url, expand=True, fill=True, padding=0)
        vbox_advanced.pack_start(hbox_custom_url, expand=False, fill=False, padding=0)

        hbox_category_param = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6, margin=10)
        hbox_category_param.pack_start(self.label_category_param, expand=False, fill=False, padding=0)
        hbox_category_param.pack_end(self.entry_category_param, expand=True, fill=True, padding=0)
        vbox_advanced.pack_start(hbox_category_param, expand=False, fill=False, padding=0)

        self.check_custom_url.connect("toggled", self.on_check_custom_url_toggled)

        # Extra Settings Frame
        frame_extra_settings = Gtk.Frame(label="Extra Settings")
        vbox_extra_settings = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, margin=10)
        frame_extra_settings.add(vbox_extra_settings)

        # Checkboxes for extra settings
        self.check_always_redownload = Gtk.CheckButton(label="Always re-download image")
        vbox_extra_settings.pack_start(self.check_always_redownload, expand=False, fill=False, padding=0)

        self.check_startup = Gtk.CheckButton(label="Open app on startup")
        vbox_extra_settings.pack_start(self.check_startup, expand=False, fill=False, padding=0)

        vbox_advanced.pack_start(frame_extra_settings, expand=False, fill=False, padding=10)

        # Program Information Section
        program_info_label = Gtk.Label(label=f"Version: {__version__}")
        vbox.pack_start(program_info_label, expand=False, fill=False, padding=10)

        # Set dialog size
        self.set_default_size(800, 600)
        content_area.show_all()

    def update_next_refresh_label(self):
        """
        Computes and updates the next refresh time label.
        """
        # Determine the refresh time to use
        if self.custom_refresh_checkbox.get_active():
            refresh_time = self.get_custom_refresh_time()
        else:
            refresh_time = self.get_default_refresh_time()

        # Compute the next scheduled datetime
        now = datetime.now()
        refresh_hour, refresh_minute = map(int, refresh_time.split(":"))
        # Adjust for AM/PM if in 12-hour format
        if refresh_hour <= 12:
            am_pm = self.combo_ampm.get_active_text()
            if am_pm == "PM" and refresh_hour != 12:
                refresh_hour += 12
            elif am_pm == "AM" and refresh_hour == 12:
                refresh_hour = 0

        next_refresh = now.replace(hour=refresh_hour, minute=refresh_minute, second=0, microsecond=0)
        if next_refresh <= now:
            next_refresh += timedelta(days=1)

        # Format the date with ordinal suffix
        day = next_refresh.day
        if 10 <= day % 100 <= 20:
            suffix = 'th'
        else:
            suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(day % 10, 'th')
        formatted_day = f"{day}{suffix}"
        formatted_date = next_refresh.strftime(f"%d{suffix} %b %I:%M%p")

        # Convert to local timezone if not already
        local_timezone = datetime.now().astimezone().tzinfo
        next_refresh_local = next_refresh.astimezone(local_timezone)

        formatted_date = next_refresh_local.strftime(f"%d{suffix} %b %I:%M%p").lstrip('0')

        # Update the label text
        self.next_refresh_label.set_text(f"Next refresh time: {formatted_date}")

    def get_custom_refresh_time(self):
        """
        Returns the custom refresh time in 24-hour format as 'HH:MM'.
        """
        hour = self.spin_hour.get_value_as_int()
        minute = self.spin_minute.get_value_as_int()
        am_pm = self.combo_ampm.get_active_text()

        if am_pm == "PM" and hour != 12:
            hour += 12
        elif am_pm == "AM" and hour == 12:
            hour = 0

        return f"{hour:02d}:{minute:02d}"

    def get_default_refresh_time(self):
        """
        Returns the default refresh time. Modify this method if you have a default setting.
        """
        # Define default time in GMT (12:05 AM)
        gmt = pytz.timezone('GMT')
        default_time_gmt = gmt.localize(datetime.combine(datetime.today(), datetime.min.time()) + timedelta(hours=0, minutes=5))

        # Get the local timezone
        local_tz = datetime.now().astimezone().tzinfo

        # Convert 12:05 AM GMT to local time
        default_time_local = default_time_gmt.astimezone(local_tz)

        return default_time_local.strftime("%H:%M")

    def on_custom_refresh_toggled(self, checkbox):
        is_active = checkbox.get_active()
        self.spin_hour.set_sensitive(is_active)
        self.spin_minute.set_sensitive(is_active)
        self.combo_ampm.set_sensitive(is_active)
        self.reset_time_button.set_sensitive(is_active)

        # Update the next refresh label when toggled
        self.update_next_refresh_label()

    def get_custom_refresh_enabled(self):
        return self.custom_refresh_checkbox.get_active()

    def on_refresh_wallpaper_button_clicked(self, button):
        # Call the global tray_icon's on_refresh_wallpaper method
        global tray_icon
        tray_icon.on_refresh_wallpaper(None)
        self.refresh_wallpaper_preview()

    def on_scroll_event_block(self, widget, event):
        return True  # Prevent scrolling effects in spin buttons and comboboxes

    def get_most_recent_wallpaper(self):
        """Fetch the latest wallpaper from the saved directory."""
        save_dir = os.path.join(os.path.expanduser("~"), "Documents", "Daily Wallpapers")
        try:
            if not os.path.exists(save_dir):
                logging.error(f"Wallpaper directory does not exist: {save_dir}")
                return None

            files = [f for f in os.listdir(save_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))]
            if not files:
                logging.warning(f"No image files found in {save_dir}")
                return None
            
            # Sort files by modification time
            files.sort(key=lambda x: os.path.getmtime(os.path.join(save_dir, x)), reverse=True)
            most_recent = os.path.join(save_dir, files[0])
            
            # Verify file exists and is readable
            if os.path.isfile(most_recent) and os.access(most_recent, os.R_OK):
                logging.info(f"Found most recent wallpaper: {most_recent}")
                return most_recent
            logging.error(f"File not accessible: {most_recent}")
            return None
                
        except Exception as e:
            logging.error(f"Error getting recent wallpaper: {e}")
            return None
        
    def display_wallpaper_preview(self, file_path):
        """Display the wallpaper preview along with the title and relevant buttons."""
        try:
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                filename=file_path,
                width=300,  # Scale width
                height=-1,  # Maintain aspect ratio
                preserve_aspect_ratio=True
            )
            image = Gtk.Image.new_from_pixbuf(pixbuf)

            # Event box for displaying clickable image
            event_box = Gtk.EventBox()
            event_box.add(image)
            event_box.connect("button_press_event", self.on_image_clicked, file_path)

            self.recent_wallpaper_box.pack_start(event_box, expand=False, fill=False, padding=10)

            # Load and display wallpaper title
            wallpaper_title = self.get_wallpaper_title(file_path)
            if wallpaper_title:
                title_label = Gtk.Label()
                title_label.set_markup(f"<span size='xx-large'>{wallpaper_title}</span>")
                self.recent_wallpaper_box.pack_start(title_label, expand=False, fill=False, padding=0)

            # Buttons for refreshing and viewing wallpaper
            self.add_wallpaper_action_buttons(file_path)

            # Show all content for recent wallpapers
            self.recent_wallpaper_box.show_all()

        except Exception as e:
            logging.error(f"Error displaying wallpaper preview: {e}")

    def add_wallpaper_action_buttons(self, file_path):
        """Add action buttons for wallpaper management."""
        refresh_button = Gtk.Button(label="Refresh Wallpaper")
        refresh_button.connect("clicked", self.on_refresh_wallpaper_button_clicked)
        self.recent_wallpaper_box.pack_start(refresh_button, expand=False, fill=False, padding=0)

        show_in_folder_button = Gtk.Button(label="Show Image Folder")
        show_in_folder_button.connect("clicked", self.on_show_in_folder_clicked, file_path)
        self.recent_wallpaper_box.pack_start(show_in_folder_button, expand=False, fill=False, padding=0)

        view_wallpaper_website_button = Gtk.Button(label="View Wallpaper on Website")
        view_wallpaper_website_button.connect("clicked", self.on_view_wallpaper_website_clicked, file_path)
        self.recent_wallpaper_box.pack_start(view_wallpaper_website_button, expand=False, fill=False, padding=0)

    def on_image_clicked(self, widget, event, file_path):
        """Display a larger preview when the image is clicked."""
        if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 1:
            preview_image_window = Gtk.Window(title="Wallpaper Preview")
            preview_image_window.set_transient_for(widget.get_toplevel())
            preview_image_window.set_modal(True)

            # Load and scale the original image
            pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
            scaled_pixbuf = self.scale_image_to_fit_window(pixbuf, preview_image_window)

            preview_image = Gtk.Image.new_from_pixbuf(scaled_pixbuf)
            preview_image_window.add(preview_image)

            # Position window neatly in parent
            self.position_window_relative_to_parent(preview_image_window, widget)

            preview_image_window.show_all()

    def scale_image_to_fit_window(self, pixbuf, window):
        """Scale image based on the display dimensions."""
        screen_width, screen_height = self.get_scaled_max_size(window)
        new_width, new_height = self.calculate_new_dimensions(pixbuf, screen_width, screen_height)

        return pixbuf.scale_simple(new_width, new_height, GdkPixbuf.InterpType.BILINEAR)

    def get_scaled_max_size(self, window):
        """Get maximum dimensions based on screen size."""
        display = Gdk.Display.get_default()
        monitor = display.get_primary_monitor()
        geometry = monitor.get_geometry()
        return int(geometry.width * 0.8), int(geometry.height * 0.8)  # Set max width and height to 80% of screen

    def calculate_new_dimensions(self, pixbuf, max_width, max_height):
        """Calculate new dimensions to maintain aspect ratio."""
        width_ratio = max_width / pixbuf.get_width()
        height_ratio = max_height / pixbuf.get_height()
        scale_factor = min(width_ratio, height_ratio)

        return int(pixbuf.get_width() * scale_factor), int(pixbuf.get_height() * scale_factor)

    def position_window_relative_to_parent(self, preview_image_window, widget):
        """Position the preview window centered relative to parent window."""
        parent_x, parent_y = widget.get_toplevel().get_position()
        new_x_position = max(0, min(parent_x + (widget.get_toplevel().get_size()[0] - preview_image_window.get_size()[0]) // 2, 
                                        Gdk.Display.get_default().get_primary_monitor().get_geometry().width - preview_image_window.get_size()[0]))
        new_y_position = max(0, min(parent_y + (widget.get_toplevel().get_size()[1] - preview_image_window.get_size()[1]) // 2, 
                                        Gdk.Display.get_default().get_primary_monitor().get_geometry().height - preview_image_window.get_size()[1]))
        preview_image_window.move(new_x_position, new_y_position)

    def refresh_wallpaper_preview(self):
        """Refresh the wallpaper preview in the settings dialog."""
        recent_wallpaper = self.get_most_recent_wallpaper()
        if recent_wallpaper:
            # Clear current preview
            for child in self.recent_wallpaper_box.get_children():
                self.recent_wallpaper_box.remove(child)

            # Display new wallpaper preview
            self.display_wallpaper_preview(recent_wallpaper)

            # Show all content again
            self.recent_wallpaper_box.show_all()

    def get_wallpaper_title(self, wallpaper_path):
        """Retrieve wallpaper title from associated JSON file."""
        json_file_path = wallpaper_path.rsplit('.', 1)[0] + '.json'
        try:
            if os.path.exists(json_file_path):
                with open(json_file_path, 'r') as json_file:
                    data = json.load(json_file)
                    return data.get('title', 'Unknown Title')
            logging.warning(f"JSON file not found for wallpaper: {wallpaper_path}")
            return None
        except Exception as e:
            logging.error(f"Error reading wallpaper JSON file: {e}")
            return None

    def on_show_in_folder_clicked(self, button, file_path):
        """Open the folder containing the image."""
        folder_path = os.path.dirname(file_path)
        try:
            if os.name == 'nt':  # Windows
                os.startfile(folder_path)
            elif os.uname().sysname == 'Darwin':  # macOS
                subprocess.run(['open', '--', folder_path])
            else:  # Linux
                subprocess.run(['xdg-open', folder_path])
        except Exception as e:
            logging.error(f"Failed to open folder: {e}")

    def on_view_wallpaper_website_clicked(self, button, file_path):
        """Open the wallpaper URL in a web browser."""
        try:
            json_file_path = file_path.rsplit('.', 1)[0] + '.json'
            if os.path.exists(json_file_path):
                with open(json_file_path, 'r') as json_file:
                    data = json.load(json_file)
                    image_url = data.get('websiteurl')
                    if image_url:
                        webbrowser.open(image_url)
                    else:
                        logging.warning(f"URL not found in JSON file: {json_file_path}")
            else:
                logging.warning(f"JSON file not found for wallpaper: {file_path}")
        except Exception as e:
            logging.error(f"Error opening wallpaper URL: {e}")

    def on_event_box_clicked(self, widget, event, radio_button):
        radio_button.set_active(True)

    def on_reset_time(self, button):
        # Define default time in GMT (12:05 AM)
        gmt = pytz.timezone('GMT')
        default_time_gmt = gmt.localize(datetime.combine(datetime.today(), datetime.min.time()) + timedelta(hours=0, minutes=5))

        # Get the local timezone
        local_tz = datetime.now().astimezone().tzinfo

        # Convert 12:05 AM GMT to local time
        default_time_local = default_time_gmt.astimezone(local_tz)
        
        # Extract hour, minute, AM/PM from the local time
        local_hour = default_time_local.hour
        local_minute = default_time_local.minute
        am_pm = "AM" if local_hour < 12 else "PM"
        display_hour = local_hour % 12 or 12  # Convert to 12-hour format

        # Set values in time picker
        self.spin_hour.set_value(display_hour)
        self.spin_minute.set_value(local_minute)
        self.combo_ampm.set_active(0 if am_pm == "AM" else 1)  # Set AM/PM

        # After resetting, update the next refresh label
        self.update_next_refresh_label()

    def get_index_from_text(self, combo_box, text):
        """Returns the index of the text in the given combo box."""
        combo_model = combo_box.get_model()
        for i, row in enumerate(combo_model):
            if row[0] == text:
                return i
        return 0  # Default to the first item if not found

    def on_check_custom_url_toggled(self, widget):
        sensitive = widget.get_active()
        self.label_custom_url.set_sensitive(sensitive)
        self.entry_custom_url.set_sensitive(sensitive)
        self.label_category_param.set_sensitive(sensitive)
        self.entry_category_param.set_sensitive(sensitive)

    def get_selected_collection(self):
        for radio_button in self.category_radio_buttons:
            if radio_button.get_active():
                return radio_button.get_label()
        return None

    def get_selected_resolution(self):
        return self.combo_resolution.get_active_text()

    def get_always_redownload(self):
        return self.check_always_redownload.get_active() 

    def get_startup_enabled(self):
        return self.check_startup.get_active()

    def on_time_changed(self, widget):
        """
        Called whenever the refresh time settings are changed.
        """
        self.update_next_refresh_label()

    def get_refresh_time(self):
        """Get the selected time in 24-hour format as 'HH:MM'."""
        if self.custom_refresh_checkbox.get_active():
            return self.get_custom_refresh_time()
        else:
            return self.get_default_refresh_time()

    def set_refresh_time(self, time_str):
        """Set the time picker based on a 24-hour format time string 'HH:MM'."""
        hour, minute = map(int, time_str.split(":"))
        am_pm = "AM" if hour < 12 else "PM"
        display_hour = hour % 12 or 12
        self.spin_hour.set_value(display_hour)
        self.spin_minute.set_value(minute)
        self.combo_ampm.set_active(0 if am_pm == "AM" else 1)

class CenteredLabelExpander(Gtk.Box):
    def __init__(self, label_text):
        super().__init__(orientation=Gtk.Orientation.VERTICAL)
        
        # Create a button that looks like an expander
        self.toggle_button = Gtk.ToggleButton()
        self.toggle_button.connect("toggled", self.on_button_toggled)
        
        # Create a centered label
        label = Gtk.Label(label=label_text)
        label.set_halign(Gtk.Align.CENTER)
        self.toggle_button.add(label)
        
        # Create a revealer to show/hide content
        self.revealer = Gtk.Revealer()
        
        self.pack_start(self.toggle_button, False, False, 0)
        self.pack_start(self.revealer, True, True, 0)

    def on_button_toggled(self, button):
        self.revealer.set_reveal_child(button.get_active())

    def add(self, widget):
        self.revealer.add(widget)

class TrayIcon:
    def __init__(self):
        self.load_settings()

        icon_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "/usr/local/bin/daily-backdrop-icon.png"))

        # Create system tray icon
        self.indicator = AppIndicator3.Indicator.new(
            "daily-wallpaper-changer", icon_path,
            AppIndicator3.IndicatorCategory.APPLICATION_STATUS
        )
        self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
        self.indicator.set_menu(self.create_menu())

        self.settings_dialog = None  # Initialize settings_dialog to None

        self.schedule_wallpaper_change()
        self.scheduler_thread = threading.Thread(target=self.run_scheduler, daemon=True)
        self.scheduler_thread.start()

    def schedule_wallpaper_change(self):
        schedule.clear('wallpaper_change')
        
        # Determine the refresh time from settings
        refresh_time = self.refresh_time  # Assume 'refresh_time' is in 'HH:MM' 24-hour format
        
        # Schedule the task
        schedule.every().day.at(refresh_time).do(
            change_wallpaper, 
            self.selected_collection, 
            self.selected_resolution, 
            self.always_redownload
        ).tag('wallpaper_change')

    def load_settings(self):
        os.makedirs(SETTINGS_DIR, exist_ok=True)

        if os.path.exists(SETTINGS_PATH):
            with open(SETTINGS_PATH, "r") as file:
                settings = json.load(file)
                self.selected_collection = settings.get("selected_collection", "Minimalist Black")
                self.selected_resolution = settings.get("selected_resolution", "Full")
                self.always_redownload = settings.get("always_redownload", False)
                self.refresh_time = settings.get("refresh_time", "00:05")
                self.custom_refresh_enabled = settings.get("custom_refresh_enabled", False)
                self.startup_enabled = settings.get("startup_enabled", False)  # Load startup setting
                
                # Apply startup setting
                self.handle_startup_setting()
        else:
            self.set_default_settings()

    def set_default_settings(self):
        self.selected_collection = "Minimalist Black"
        self.selected_resolution = "Full"
        self.always_redownload = False
        self.refresh_time = "00:05"
        self.custom_refresh_enabled = False
        self.startup_enabled = False  # Default startup setting

    def handle_startup_setting(self):
        if self.startup_enabled and not os.path.exists(get_autostart_file_path()):
            create_autostart_entry()
        elif not self.startup_enabled and os.path.exists(get_autostart_file_path()):
            remove_autostart_entry()

    def save_settings(self):
        os.makedirs(SETTINGS_DIR, exist_ok=True)
        
        settings = {
            "selected_collection": self.selected_collection,
            "selected_resolution": self.selected_resolution,
            "always_redownload": self.always_redownload,
            "refresh_time": self.refresh_time,
            "custom_refresh_enabled": self.custom_refresh_enabled,
            "startup_enabled": self.startup_enabled  # Save startup setting
        }
        with open(SETTINGS_PATH, "w") as file:
            json.dump(settings, file, indent=4)
            
        # Handle autostart file
        self.handle_startup_setting()

    def create_menu(self):
        menu = Gtk.Menu()
        show_settings_item = Gtk.MenuItem(label='Show Settings')
        show_settings_item.connect('activate', self.on_show_settings)
        menu.append(show_settings_item)

        refresh_wallpaper_item = Gtk.MenuItem(label='Refresh Wallpaper')
        refresh_wallpaper_item.connect('activate', self.on_refresh_wallpaper)
        menu.append(refresh_wallpaper_item)

        separator = Gtk.SeparatorMenuItem()
        menu.append(separator)

        quit_item = Gtk.MenuItem(label='Quit')
        quit_item.connect('activate', self.on_quit)
        menu.append(quit_item)

        menu.show_all()
        return menu

    def on_show_settings(self, widget):
        logging.info("Show Settings clicked")
        if self.settings_dialog is None:
            self.settings_dialog = SettingsDialog(
                None,
                self.selected_collection,
                self.selected_resolution,
                self.refresh_time,
                self.custom_refresh_enabled  # Pass the checkbox state
            )
            self.settings_dialog.check_always_redownload.set_active(self.always_redownload)
            self.settings_dialog.check_startup.set_active(self.startup_enabled)
            self.settings_dialog.connect("response", self.on_settings_dialog_response)
            self.settings_dialog.connect("destroy", self.on_settings_dialog_closed)
            self.settings_dialog.show()
        else:
            self.settings_dialog.present()

    def on_settings_dialog_response(self, dialog, response):
        if response in (Gtk.ResponseType.OK, Gtk.ResponseType.APPLY):
            logging.info("Settings applied")
            self.selected_collection = dialog.get_selected_collection()
            self.selected_resolution = dialog.get_selected_resolution()
            self.always_redownload = dialog.get_always_redownload()
            self.refresh_time = dialog.get_refresh_time()
            self.custom_refresh_enabled = dialog.get_custom_refresh_enabled()
            self.startup_enabled = dialog.check_startup.get_active()  # Get startup checkbox state
            
            self.save_settings()  # Save the new settings
            
            logging.info(f"Updated settings - Collection: {self.selected_collection}, "
                        f"Resolution: {self.selected_resolution}, "
                        f"Always re-download: {self.always_redownload}, "
                        f"Refresh time: {self.refresh_time}, "
                        f"Custom refresh: {self.custom_refresh_enabled}, "
                        f"Startup enabled: {self.startup_enabled}")

            # Manage startup setting
            self.handle_startup_setting()

            # Reschedule wallpaper change
            self.schedule_wallpaper_change()
            
            if response == Gtk.ResponseType.OK:
                dialog.destroy()
        elif response == Gtk.ResponseType.CANCEL:
            logging.info("Settings canceled")
            dialog.destroy()

    def on_settings_dialog_closed(self, widget):
        self.settings_dialog = None

    def on_refresh_wallpaper(self, widget):
        logging.info("Refresh Wallpaper clicked")
        change_wallpaper(self.selected_collection, self.selected_resolution, self.always_redownload)

        # Refresh wallpaper preview if settings dialog is open
        if self.settings_dialog is not None:
            self.settings_dialog.refresh_wallpaper_preview()

    def on_quit(self, widget):
        Gtk.main_quit()
    
    def run_scheduler(self):
        while True:
            schedule.run_pending()
            time.sleep(1)

def get_autostart_file_path():
    """Returns the path to the autostart .desktop file"""
    autostart_dir = os.path.expanduser('~/.config/autostart')
    return os.path.join(autostart_dir, 'daily-backdrop.desktop')

def create_autostart_entry():
    """Creates the autostart .desktop file"""
    autostart_dir = os.path.expanduser('~/.config/autostart')
    os.makedirs(autostart_dir, exist_ok=True)
    
    desktop_entry = """[Desktop Entry]
Name=Daily Backdrop
Exec=/usr/local/bin/daily-backdrop
Type=Application
X-GNOME-Autostart-enabled=true
"""
    
    with open(get_autostart_file_path(), 'w') as f:
        f.write(desktop_entry)

def remove_autostart_entry():
    """Removes the autostart .desktop file"""
    try:
        os.remove(get_autostart_file_path())
    except FileNotFoundError:
        pass

def download_image(url, save_dir, always_redownload=False):
    parsed_url = urllib.parse.urlparse(url)
    filename = os.path.basename(parsed_url.path)
    save_path = os.path.join(save_dir, filename)
    
    if not always_redownload and os.path.exists(save_path):
        logging.info(f"Image already exists at: {save_path}")
        return save_path
    
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        with open(save_path, 'wb') as f:
            for chunk in response.iter_content(1024):
                f.write(chunk)
        logging.info(f"Image downloaded to: {save_path}")
        return save_path
    logging.error(f"Failed to download image from {url}")
    return None

def set_wallpaper(image_path):
    try:
        abs_image_path = os.path.abspath(image_path)
        # Set wallpaper using gsettings
        subprocess.run(['gsettings', 'set', 'org.gnome.desktop.background', 'picture-uri', f'file://{abs_image_path}'], check=True)
        subprocess.run(['gsettings', 'set', 'org.gnome.desktop.background', 'picture-uri-dark', f'file://{abs_image_path}'], check=True)
        
        # Force an update
        subprocess.run(['gsettings', 'set', 'org.gnome.desktop.background', 'picture-options', 'zoom'], check=True)
        
        logging.info(f"Wallpaper set to: {abs_image_path}")
    except subprocess.CalledProcessError as e:
        logging.error(f"Error setting wallpaper: {e}")
    except Exception as e:
        logging.error(f"Unexpected error setting wallpaper: {e}")

def is_connected(host="8.8.8.8", port=53, timeout=3):
    """
    Check internet connectivity by attempting to connect to a well-known DNS server.
    """
    try:
        socket.setdefaulttimeout(timeout)
        socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port))
        return True
    except socket.error:
        return False

def change_wallpaper(selected_collection, selected_resolution, always_redownload=False, max_retries=3, retry_delay=10):
    base_url = "https://wallpapers.simshauser.au/wp-json/api/v1/today/"  # Replace with actual API URL
    collection_param = f"?collection={selected_collection.replace(' ', '%20')}"
    resolution_param = f"-{selected_resolution}" if selected_resolution != "Full" else ""
    url = base_url + collection_param
    
    save_dir = os.path.join(os.path.expanduser("~"), "Documents", "Daily Wallpapers")
    os.makedirs(save_dir, exist_ok=True)
    
    attempt = 0
    while attempt < max_retries:
        if not is_connected():
            logging.warning("No internet connection. Retrying in {} seconds... (Attempt {}/{})".format(retry_delay, attempt + 1, max_retries))
            time.sleep(retry_delay)
            attempt += 1
            continue
        
        try:
            logging.info(f"Fetching wallpaper data from: {url}")
            response = requests.get(url)
            response.raise_for_status()
            data = response.json()
            logging.debug(f"Received JSON data: {data}")
            
            image_url = data.get('imageurl')
            
            if image_url:
                logging.info(f"Image URL found: {image_url}")
                modified_image_url = image_url if not resolution_param else f"{image_url.rsplit('.', 1)[0]}{resolution_param}.jpg"
                
                image_path = download_image(modified_image_url, save_dir, always_redownload)
                if image_path:
                    set_wallpaper(image_path)

                    # Save JSON metadata for wallpaper
                    image_filename = os.path.splitext(os.path.basename(image_path))[0]
                    json_save_path = os.path.join(save_dir, f"{image_filename}.json")
                    with open(json_save_path, 'w') as json_file:
                        json.dump(data, json_file, indent=4)
                    logging.info(f"JSON data saved to: {json_save_path}")
                    
                    logging.info("Wallpaper change process completed.")
                else:
                    logging.error("Failed to download wallpaper.")
            else:
                logging.error("No image URL found in the JSON response.")
            break  # Exit the loop if successful
        except requests.exceptions.RequestException as e:
            logging.error(f"Error fetching wallpaper data: {e}")
            attempt += 1
            if attempt < max_retries:
                logging.info(f"Retrying in {retry_delay} seconds... (Attempt {attempt + 1}/{max_retries})")
                time.sleep(retry_delay)
            else:
                logging.error("Max retries exceeded. Skipping wallpaper change.")
        except json.JSONDecodeError as e:
            logging.error(f"Error parsing JSON response: {e}")
            break
        except Exception as e:
            logging.error(f"Unexpected error: {e}", exc_info=True)
            break

def main():
    os.makedirs(SETTINGS_DIR, exist_ok=True)
    logging.basicConfig(
        filename=os.path.join(SETTINGS_DIR, 'daily-backdrop.log'),
        level=logging.DEBUG,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    global tray_icon
    tray_icon = TrayIcon()
    Gtk.main()

if __name__ == "__main__":
    main()
```