@@ -124,22 +124,34 @@ def _safe_update(self, control=None, *args):
124124 logger .debug (f"Safe update failed (non-critical): { e } " )
125125
126126 def _show_snack (self , msg , color = "GREEN" ):
127- """Show a snackbar message on the page using modern API ."""
127+ """Show a snackbar message on the page with robust compatibility ."""
128128 try :
129129 page = getattr (self , "app_page" , None )
130130 if not page :
131131 try :
132132 page = self .page
133133 except (RuntimeError , AttributeError ):
134- logger .warning ("Failed to show snackbar: page not available (RuntimeError/AttributeError) " )
134+ logger .warning ("Failed to show snackbar: page not available" )
135135 return
136136 if not page :
137- logger .warning ("Failed to show snackbar: page is None" )
138137 return
139138
140- page .snack_bar = ft .SnackBar (ft .Text (msg ), bgcolor = color )
141- # Use newer API for showing snackbar
142- page .open (page .snack_bar )
139+ # Create snackbar
140+ sb = ft .SnackBar (ft .Text (msg ), bgcolor = color )
141+ page .snack_bar = sb
142+
143+ # Try modern API
144+ if hasattr (page , 'open' ):
145+ try :
146+ page .open (sb )
147+ page .update ()
148+ return
149+ except Exception as e_modern :
150+ logger .debug (f"Modern snackbar open failed: { e_modern } " )
151+
152+ # Fallback for older or specific builds
153+ sb .open = True
154+ page .update ()
143155 except Exception as e :
144156 logger .warning (f"Failed to show snackbar: { e } " , exc_info = True )
145157
@@ -404,7 +416,7 @@ async def async_wrapper():
404416 def _open_dialog_safe (self , dlg ):
405417 """
406418 Open a dialog safely across Flet versions and environments.
407- Prioritizes modern 'page.open(dlg)' API, falls back to legacy 'page.dialog = dlg'.
419+ Handles both modern 'page.open(dlg)' and legacy 'page.dialog = dlg'.
408420 """
409421 page = getattr (self , "app_page" , None )
410422 if not page :
@@ -418,28 +430,24 @@ def _open_dialog_safe(self, dlg):
418430 return False
419431
420432 try :
421- # Method 1: Modern API (Preferred)
433+ # 1. Try modern API (Preferred)
422434 if hasattr (page , 'open' ):
423435 try :
424- logger .debug ("Attempting to open dialog using page.open()..." )
425436 page .open (dlg )
426437 page .update ()
427- logger .info ("Dialog opened successfully using page.open()" )
428438 return True
429- except Exception as e :
430- logger .warning (f"page. open(dlg) failed: { e } . Falling back to legacy mode. " )
439+ except Exception as e_modern :
440+ logger .debug (f"Modern dialog open failed: { e_modern } " )
431441
432- # Method 2: Legacy API (Fallback)
433- logger .debug ("Attempting to open dialog using legacy page.dialog..." )
442+ # 2. Legacy Fallback
434443 page .dialog = dlg
435444 if hasattr (dlg , 'open' ):
436445 dlg .open = True
437446 page .update ()
438- logger .info ("Dialog opened using legacy page.dialog mode" )
439447 return True
440448
441449 except Exception as e :
442- logger .error (f"Failed to open dialog in any mode: { e } " , exc_info = True )
450+ logger .error (f"Failed to open dialog in any mode: { e } " )
443451 return False
444452
445453 def _close_dialog (self , dialog = None ):
@@ -633,4 +641,51 @@ def handle_task_exception(task):
633641 logger .exception (f"Error in direct execution: { ex } " )
634642 if error_msg :
635643 self ._show_snack (error_msg , "RED" )
636- return False
644+ return False
645+
646+ def _copy_to_clipboard (self , text : str ):
647+ """Centralized robust clipboard copy with multiple fallbacks."""
648+ if not text :
649+ from switchcraft .utils .i18n import i18n
650+ self ._show_snack (i18n .get ("nothing_to_copy" ) or "Nothing to copy" , "ORANGE" )
651+ return
652+
653+ from switchcraft .utils .i18n import i18n
654+ page = getattr (self , "app_page" , None ) or getattr (self , "page" , None )
655+
656+ # 1. Try Flet Page (Best for Web/Standard Desktop)
657+ if page :
658+ try :
659+ # set_clipboard is usually sync, but some async environments might need set_clipboard_async
660+ if hasattr (page , 'set_clipboard_async' ):
661+ async def _clip (): await page .set_clipboard_async (text )
662+ self ._run_task_safe (_clip )
663+ else :
664+ page .set_clipboard (text )
665+
666+ self ._show_snack (i18n .get ("copied_to_clipboard" ) or "Copied to clipboard!" , "GREEN_700" )
667+ return
668+ except Exception as e :
669+ logger .debug (f"Flet clipboard failed: { e } " )
670+
671+ # 2. Try Pyperclip (Desktop standard)
672+ try :
673+ import pyperclip
674+ pyperclip .copy (text )
675+ self ._show_snack (i18n .get ("copied_to_clipboard" ) or "Copied to clipboard!" , "GREEN_700" )
676+ return
677+ except Exception as e :
678+ logger .debug (f"Pyperclip failed: { e } " )
679+
680+ # 3. Fallback to Shell (Windows 'clip' or 'xdg-copy' etc via ShellUtils)
681+ try :
682+ from switchcraft .utils .shell_utils import ShellUtils
683+ import sys
684+ if sys .platform == "win32" :
685+ ShellUtils .run_command (['clip' ], input = text .encode ('utf-8' ), check = False , silent = True )
686+ self ._show_snack (i18n .get ("copied_to_clipboard" ) or "Copied to clipboard!" , "GREEN_700" )
687+ return
688+ except Exception as e :
689+ logger .debug (f"Shell clipboard fallback failed: { e } " )
690+
691+ self ._show_snack ("Failed to copy to clipboard" , "RED" )
0 commit comments