@@ -10,17 +10,57 @@ namespace Files.App.ViewModels.Dialogs
1010{
1111 public sealed partial class DecompressArchiveDialogViewModel : ObservableObject
1212 {
13+ // Services
1314 private ICommonDialogService CommonDialogService { get ; } = Ioc . Default . GetRequiredService < ICommonDialogService > ( ) ;
15+ private readonly IUserSettingsService UserSettingsService = Ioc . Default . GetRequiredService < IUserSettingsService > ( ) ;
1416
17+ // Fields
1518 private readonly IStorageFile archive ;
1619
20+ // Properties
1721 public BaseStorageFolder DestinationFolder { get ; private set ; }
1822
1923 private string destinationFolderPath ;
2024 public string DestinationFolderPath
2125 {
2226 get => destinationFolderPath ;
23- private set => SetProperty ( ref destinationFolderPath , value ) ;
27+ set
28+ {
29+ if ( SetProperty ( ref destinationFolderPath , value ) )
30+ {
31+ OnPropertyChanged ( nameof ( IsDestinationPathValid ) ) ;
32+ }
33+ }
34+ }
35+
36+ public bool IsDestinationPathValid
37+ {
38+ get
39+ {
40+ try
41+ {
42+ if ( string . IsNullOrWhiteSpace ( DestinationFolderPath ) )
43+ return false ;
44+
45+ string parentDir = Path . GetDirectoryName ( DestinationFolderPath ) ;
46+ string finalSegment = Path . GetFileName ( DestinationFolderPath ) ;
47+
48+ // Check parent directory exists
49+ if ( string . IsNullOrWhiteSpace ( parentDir ) || ! Directory . Exists ( parentDir ) )
50+ return false ;
51+
52+ // Check for invalid characters (IsValidForFilename already does this)
53+ if ( ! FilesystemHelpers . IsValidForFilename ( finalSegment ) )
54+ return false ;
55+
56+ return true ;
57+ }
58+ catch
59+ {
60+ // Catch any exception to prevent crashes
61+ return false ;
62+ }
63+ }
2464 }
2565
2666 private bool openDestinationFolderOnCompletion ;
@@ -65,31 +105,17 @@ public bool ShowPathSelection
65105 public DisposableArray ? Password { get ; private set ; }
66106
67107 public EncodingItem [ ] EncodingOptions { get ; set ; } = EncodingItem . Defaults ;
68- public EncodingItem SelectedEncoding { get ; set ; }
69- void RefreshEncodingOptions ( )
70- {
71- if ( detectedEncoding != null )
72- {
73- EncodingOptions = EncodingItem . Defaults
74- . Prepend ( new EncodingItem (
75- detectedEncoding ,
76- string . Format ( Strings . EncodingDetected . GetLocalizedResource ( ) , detectedEncoding . EncodingName )
77- ) )
78- . ToArray ( ) ;
79- }
80- else
81- {
82- EncodingOptions = EncodingItem . Defaults ;
83- }
84- SelectedEncoding = EncodingOptions . FirstOrDefault ( ) ;
85- }
86108
109+ public EncodingItem SelectedEncoding { get ; set ; }
87110
111+ public ObservableCollection < string > PreviousExtractionLocations { get ; } = [ ] ;
88112
113+ // Commands
89114 public IRelayCommand PrimaryButtonClickCommand { get ; private set ; }
90-
91115 public ICommand SelectDestinationCommand { get ; private set ; }
116+ public ICommand QuerySubmittedCommand { get ; private set ; }
92117
118+ // Constructor
93119 public DecompressArchiveDialogViewModel ( IStorageFile archive )
94120 {
95121 this . archive = archive ;
@@ -101,6 +127,12 @@ public DecompressArchiveDialogViewModel(IStorageFile archive)
101127 PrimaryButtonClickCommand = new RelayCommand < DisposableArray > ( password => Password = password ) ;
102128 }
103129
130+ // Private Methods
131+ private string DefaultDestinationFolderPath ( )
132+ {
133+ return Path . Combine ( Path . GetDirectoryName ( archive . Path ) , Path . GetFileNameWithoutExtension ( archive . Path ) ) ;
134+ }
135+
104136 private async Task SelectDestinationAsync ( )
105137 {
106138 bool result = CommonDialogService . Open_FileOpenDialog ( MainWindow . Instance . WindowHandle , true , [ ] , Environment . SpecialFolder . Desktop , out var filePath ) ;
@@ -111,9 +143,42 @@ private async Task SelectDestinationAsync()
111143 DestinationFolderPath = ( DestinationFolder is not null ) ? DestinationFolder . Path : DefaultDestinationFolderPath ( ) ;
112144 }
113145
114- private string DefaultDestinationFolderPath ( )
146+ private void RefreshEncodingOptions ( )
115147 {
116- return Path . Combine ( Path . GetDirectoryName ( archive . Path ) , Path . GetFileNameWithoutExtension ( archive . Path ) ) ;
148+ if ( detectedEncoding != null )
149+ {
150+ EncodingOptions = EncodingItem . Defaults
151+ . Prepend ( new EncodingItem (
152+ detectedEncoding ,
153+ string . Format ( Strings . EncodingDetected . GetLocalizedResource ( ) , detectedEncoding . EncodingName )
154+ ) )
155+ . ToArray ( ) ;
156+ }
157+ else
158+ {
159+ EncodingOptions = EncodingItem . Defaults ;
160+ }
161+ SelectedEncoding = EncodingOptions . FirstOrDefault ( ) ;
162+ }
163+
164+ // Public Methods
165+ public void UpdateSuggestions ( string query )
166+ {
167+ var allItems = UserSettingsService . GeneralSettingsService . PreviousArchiveExtractionLocations ;
168+ if ( allItems is null )
169+ return ;
170+
171+ var filtered = allItems
172+ . Where ( item => item . StartsWith ( query , StringComparison . OrdinalIgnoreCase ) )
173+ . ToList ( ) ;
174+
175+ // Only update if results changed to prevent flickering
176+ if ( ! filtered . SequenceEqual ( PreviousExtractionLocations ) )
177+ {
178+ PreviousExtractionLocations . Clear ( ) ;
179+ foreach ( var item in filtered )
180+ PreviousExtractionLocations . Add ( item ) ;
181+ }
117182 }
118183 }
119- }
184+ }
0 commit comments