1818 TextField ,
1919 colors ,
2020 icons ,
21+ Container ,
2122)
2223
2324import rclone_decrypt .decrypt as decrypt
2425
25- # Configure logging (similar to original)
26+ # Configure logging
2627logging .basicConfig (
2728 filename = "/tmp/rclone-decrypt-warning.log" , level = logging .DEBUG
2829)
@@ -41,16 +42,35 @@ def main(page: Page):
4142 config_file_path = decrypt .default_rclone_conf_dir
4243 output_dir_path = decrypt .default_output_dir
4344
44- # UI Elements
45-
46- # Config File Selection
47- config_file_field = TextField (
48- label = "Config File" ,
49- value = config_file_path ,
50- expand = True ,
51- read_only = True ,
52- )
45+ # --- Event Handlers & Pickers ---
5346
47+ def update_files_list ():
48+ files_list_view .controls .clear ()
49+ if not files_to_decrypt :
50+ files_list_view .controls .append (Text ("No files selected." ))
51+ else :
52+ for f in files_to_decrypt :
53+ files_list_view .controls .append (
54+ Row (
55+ controls = [
56+ IconButton (
57+ icon = icons .CLOSE ,
58+ icon_color = colors .GREY_700 ,
59+ on_click = lambda e , path = f : remove_file (path ),
60+ tooltip = "Remove from list" ,
61+ ),
62+ Text (f , expand = True ),
63+ ]
64+ )
65+ )
66+ page .update ()
67+
68+ def remove_file (path_to_remove ):
69+ if path_to_remove in files_to_decrypt :
70+ files_to_decrypt .remove (path_to_remove )
71+ update_files_list ()
72+
73+ # 1. Config Picker
5474 def pick_config_result (e : FilePickerResultEvent ):
5575 nonlocal config_file_path
5676 if e .files :
@@ -61,6 +81,52 @@ def pick_config_result(e: FilePickerResultEvent):
6181 config_picker = FilePicker (on_result = pick_config_result )
6282 page .overlay .append (config_picker )
6383
84+ # 2. Output Picker
85+ def pick_output_result (e : FilePickerResultEvent ):
86+ nonlocal output_dir_path
87+ if e .path :
88+ output_dir_path = e .path
89+ output_dir_field .value = output_dir_path
90+ page .update ()
91+
92+ output_picker = FilePicker (on_result = pick_output_result )
93+ page .overlay .append (output_picker )
94+
95+ # 3. Add Folder Picker
96+ def add_folder_result (e : FilePickerResultEvent ):
97+ if e .path :
98+ # Walk the directory and add all files
99+ for root , dirs , files in os .walk (e .path ):
100+ for file in files :
101+ full_path = os .path .join (root , file )
102+ if full_path not in files_to_decrypt :
103+ files_to_decrypt .append (full_path )
104+ update_files_list ()
105+
106+ add_folder_picker = FilePicker (on_result = add_folder_result )
107+ page .overlay .append (add_folder_picker )
108+
109+ # 4. Add Files Picker (Multiple)
110+ def add_files_result (e : FilePickerResultEvent ):
111+ if e .files :
112+ for f in e .files :
113+ if f .path not in files_to_decrypt :
114+ files_to_decrypt .append (f .path )
115+ update_files_list ()
116+
117+ add_files_picker = FilePicker (on_result = add_files_result )
118+ page .overlay .append (add_files_picker )
119+
120+ # --- UI Components ---
121+
122+ # Config Row
123+ config_file_field = TextField (
124+ label = "Config File" ,
125+ value = config_file_path ,
126+ expand = True ,
127+ read_only = True ,
128+ )
129+
64130 config_row = Row (
65131 controls = [
66132 config_file_field ,
@@ -74,24 +140,14 @@ def pick_config_result(e: FilePickerResultEvent):
74140 ],
75141 )
76142
77- # Output Directory Selection
143+ # Output Row
78144 output_dir_field = TextField (
79145 label = "Output Directory" ,
80146 value = output_dir_path ,
81147 expand = True ,
82148 read_only = True ,
83149 )
84150
85- def pick_output_result (e : FilePickerResultEvent ):
86- nonlocal output_dir_path
87- if e .path :
88- output_dir_path = e .path
89- output_dir_field .value = output_dir_path
90- page .update ()
91-
92- output_picker = FilePicker (on_result = pick_output_result )
93- page .overlay .append (output_picker )
94-
95151 output_row = Row (
96152 controls = [
97153 output_dir_field ,
@@ -103,54 +159,71 @@ def pick_output_result(e: FilePickerResultEvent):
103159 ],
104160 )
105161
106- # Files List
107- files_list_view = ListView (expand = True , spacing = 10 , padding = 10 )
108-
109- def remove_file (path_to_remove ):
110- if path_to_remove in files_to_decrypt :
111- files_to_decrypt .remove (path_to_remove )
112- update_files_list ()
162+ # Files List Area
163+ files_list_view = ListView (expand = True , spacing = 5 , padding = 5 )
164+
165+ # Initialize list
166+ update_files_list ()
113167
114- def update_files_list ():
115- files_list_view .controls .clear ()
116- for f in files_to_decrypt :
117- files_list_view .controls .append (
118- Row (
119- controls = [
120- Text (f , expand = True ),
121- IconButton (
122- icon = icons .DELETE ,
123- icon_color = colors .RED ,
124- on_click = lambda e , path = f : remove_file (path ),
125- ),
126- ]
127- )
168+ files_area = Column (
169+ controls = [
170+ Row (
171+ controls = [
172+ Text ("Files to Decrypt:" , style = ft .TextThemeStyle .TITLE_MEDIUM , expand = True ),
173+ Row (
174+ controls = [
175+ ElevatedButton (
176+ "Add Files" ,
177+ icon = icons .ADD ,
178+ on_click = lambda _ : add_files_picker .pick_files (
179+ allow_multiple = True ,
180+ dialog_title = "Select Files to Decrypt"
181+ ),
182+ ),
183+ ElevatedButton (
184+ "Add Folder" ,
185+ icon = icons .CREATE_NEW_FOLDER ,
186+ on_click = lambda _ : add_folder_picker .get_directory_path (
187+ dialog_title = "Select Folder to Decrypt"
188+ ),
189+ ),
190+ ],
191+ spacing = 10 ,
192+ )
193+ ],
194+ alignment = ft .MainAxisAlignment .SPACE_BETWEEN
195+ ),
196+ Container (
197+ content = files_list_view ,
198+ border = ft .border .all (1 , colors .OUTLINE ),
199+ border_radius = 5 ,
200+ height = 200 ,
128201 )
129- page .update ()
130-
131- # Drag and Drop Handler
132- def file_picker_result (e : FilePickerResultEvent ):
133- if e .files :
134- for f in e .files :
135- if f .path not in files_to_decrypt :
136- files_to_decrypt .append (f .path )
137- update_files_list ()
138-
139- # Native OS Drag and Drop (if supported by Flet on the platform)
140- # Note: Flet's `on_file_drop` handles this.
202+ ]
203+ )
141204
142205 # Decrypt Action
143206 status_text = Text ("" )
144207
145208 def decrypt_click (e ):
146209 status_text .value = "Decrypting..."
210+ status_text .color = colors .BLUE
147211 page .update ()
148212
149213 try :
214+ if not files_to_decrypt :
215+ status_text .value = "No files to decrypt!"
216+ status_text .color = colors .RED
217+ page .update ()
218+ return
219+
150220 for f in files_to_decrypt :
151- # Strip potential extra quotes if dragged/dropped might add them
152- # though Flet usually handles paths cleanly.
153221 clean_path = f .strip ('"\' ' )
222+ # Verify file exists
223+ if not os .path .exists (clean_path ):
224+ print (f"Skipping missing file: { clean_path } " )
225+ continue
226+
154227 decrypt .decrypt (clean_path , config_file_path , output_dir_path )
155228
156229 status_text .value = "Decryption Complete!"
@@ -160,7 +233,6 @@ def decrypt_click(e):
160233 status_text .value = err_msg
161234 status_text .color = colors .RED
162235
163- # Log error
164236 err_logger = logging .getLogger (__name__ )
165237 now = datetime .now ()
166238 trace = traceback .format_exc ()
@@ -170,9 +242,8 @@ def decrypt_click(e):
170242
171243 page .update ()
172244
173-
174245 decrypt_button = ElevatedButton (
175- "Decrypt Files" ,
246+ "Decrypt All Files" ,
176247 icon = icons .LOCK_OPEN ,
177248 on_click = decrypt_click ,
178249 style = ft .ButtonStyle (
@@ -181,42 +252,19 @@ def decrypt_click(e):
181252 )
182253 )
183254
184- # Layout
255+ # Main Layout
185256 page .add (
186257 Text ("Rclone Decrypt" , style = ft .TextThemeStyle .HEADLINE_MEDIUM ),
187258 config_row ,
188259 output_row ,
189- Text ("Files to Decrypt (Drag and drop files here):" ),
190- ft .Container (
191- content = files_list_view ,
192- border = ft .border .all (1 , colors .OUTLINE ),
193- border_radius = 5 ,
194- height = 200 , # Fixed height for the list area
195- ),
260+ ft .Divider (),
261+ files_area ,
262+ ft .Divider (),
196263 Row ([decrypt_button ], alignment = ft .MainAxisAlignment .CENTER ),
197264 status_text ,
198265 )
199266
200- # Handle file drop
201- def on_drop (e : ft .FilePickerResultEvent ):
202- # Flet on_file_drop event returns a FilePickerResultEvent-like object
203- # but strictly speaking it's a specific event type, however we might need to parse it.
204- # Actually, page.on_file_drop passes a FileDropEvent
205- pass
206-
207- # Since Flet 0.21.0, page.on_file_drop is the way for drag and drop
208- def page_on_drop (e : ft .FileDropEvent ):
209- # e.files is a list of FileDropEventFile
210- for f in e .files :
211- path = f .path
212- if path not in files_to_decrypt :
213- files_to_decrypt .append (path )
214- update_files_list ()
215-
216- page .on_file_drop = page_on_drop
217-
218-
219267 ft .app (target = main )
220268
221269if __name__ == "__main__" :
222- start_gui ()
270+ start_gui ()
0 commit comments