Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fc3d2c6
实现打开zip文件选择编码。目前问题:不显示文件夹,目录结构被展平
oxygen-dioxide May 18, 2026
d31bcaf
正常显示文件夹
oxygen-dioxide May 29, 2026
722fb3c
可复制、打开文件,仍然无法打开子文件夹的文件
oxygen-dioxide May 29, 2026
50cef74
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide May 31, 2026
acaaa1a
编码选择框移动到右下角
oxygen-dioxide Jun 1, 2026
851da52
formatting
oxygen-dioxide Jun 1, 2026
6ca109c
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 3, 2026
d0cb35f
Improved styling
yair100 Jun 4, 2026
18a2bb2
回退src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs
oxygen-dioxide Jun 5, 2026
b6881b0
ascii文件不显示编码选择框,处理非内置编码
oxygen-dioxide Jun 6, 2026
af1d4d8
Change CurrentEncoding property to non-static
oxygen-dioxide Jun 7, 2026
814c90e
多zip编码管理。bug:切换标签页时变成null
oxygen-dioxide Jun 8, 2026
8f1018a
修复切换页面时zip编码变为null
oxygen-dioxide Jun 22, 2026
3d0b867
formatting
oxygen-dioxide Jun 22, 2026
f527168
修复打开zip文件时编码菜单编码不显示选中项
oxygen-dioxide Jun 23, 2026
df1ec06
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 24, 2026
add308e
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 25, 2026
53f5dc4
GetBasicProperties: 支持不同编码
oxygen-dioxide Jun 25, 2026
d4dfc84
支持加密压缩包
oxygen-dioxide Jun 25, 2026
d234cfa
Merge branch 'files-community:main' into zip-encoding-oc
oxygen-dioxide Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Files.App.Launcher/Files.App.Launcher.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions);_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
</ItemDefinitionGroup>
Expand All @@ -103,7 +103,7 @@
<ClCompile>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions);_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
Expand Down
2 changes: 1 addition & 1 deletion src/Files.App/Data/Items/EncodingItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public EncodingItem(Encoding encoding, string name)
//reference: https://en.wikipedia.org/wiki/Windows_code_page
//East Asian
"shift_jis", //Japanese
"gb2312", //Simplified Chinese
"gb18030", //Simplified Chinese
"big5", //Traditional Chinese
"ks_c_5601-1987", //Korean

Expand Down
14 changes: 14 additions & 0 deletions src/Files.App/Data/Models/CurrentInstanceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ public bool IsPageTypeLibrary
}
}

private string? zipEncodingName;
public string? ZipEncodingName
{
get => zipEncodingName;
set => SetProperty(ref zipEncodingName, value);
}

private bool isZipEncodingUndetermined;
public bool IsZipEncodingUndetermined
{
get => isZipEncodingUndetermined;
set => SetProperty(ref isZipEncodingUndetermined, value);
}

public bool CanCopyPathInPage
{
get => !isPageTypeMtpDevice && !isPageTypeRecycleBin && isPageTypeNotHome && !isPageTypeSearchResults && !IsPageTypeReleaseNotes && !IsPageTypeSettings;
Expand Down
187 changes: 187 additions & 0 deletions src/Files.App/Data/Models/DirectoryPropertiesViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// Copyright (c) Files Community
// Licensed under the MIT License.

using Files.App.Data.Items;
using Files.App.Utils.Storage;
using Files.Shared.Helpers;
using Microsoft.Extensions.Logging;
using System.ComponentModel;
using System.Text;
using System.Windows.Input;

namespace Files.App.ViewModels.UserControls
Expand All @@ -9,6 +15,8 @@ public sealed partial class StatusBarViewModel : ObservableObject
{
private IContentPageContext ContentPageContext { get; } = Ioc.Default.GetRequiredService<IContentPageContext>();
private IDevToolsSettingsService DevToolsSettingsService = Ioc.Default.GetRequiredService<IDevToolsSettingsService>();
private readonly IStorageArchiveService StorageArchiveService = Ioc.Default.GetRequiredService<IStorageArchiveService>();
private CurrentInstanceViewModel? InstanceViewModel => ContentPageContext.ShellPage?.InstanceViewModel;

// The first branch will always be the active one.
public const int ACTIVE_BRANCH_INDEX = 0;
Expand Down Expand Up @@ -81,6 +89,26 @@ public string ExtendedStatusInfo
set => SetProperty(ref _ExtendedStatusInfo, value);
}

private bool _IsZipEncodingSelectorVisible;
public bool IsZipEncodingSelectorVisible
{
get => _IsZipEncodingSelectorVisible;
set => SetProperty(ref _IsZipEncodingSelectorVisible, value);
}

public List<EncodingItem> ZipEncodingOptions { get; } = EncodingItem.Defaults.ToList();

private EncodingItem? _SelectedZipEncoding;
public EncodingItem? SelectedZipEncoding
{
get => _SelectedZipEncoding;
set
{
if (SetProperty(ref _SelectedZipEncoding, value) && value is not null)
_ = OnZipEncodingChangedAsync(value);
}
}

public bool ShowOpenInIDEButton
{
get
Expand Down Expand Up @@ -112,6 +140,24 @@ public StatusBarViewModel()
break;
}
};

SubscribeToShellPage();
ContentPageContext.PropertyChanged += OnContentPageContextPropertyChanged;
}

private void OnContentPageContextPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ContentPageContext.ShellPage))
{
UnsubscribeFromInstanceViewModel();
SubscribeToShellPage();
}
}

private void SubscribeToShellPage()
{
SubscribeToInstanceViewModel();
_ = UpdateZipEncodingStateAsync();
}

public void UpdateGitInfo(bool isGitRepository, string? repositoryPath, BranchItem? head)
Expand Down Expand Up @@ -164,5 +210,146 @@ public Task ExecuteDeleteBranch(string? branchName)
{
return GitHelpers.DeleteBranchAsync(_gitRepositoryPath, GitBranchDisplayName, branchName);
}

public async Task UpdateZipEncodingStateAsync()
{
if (ContentPageContext.ShellPage?.SlimContentPage?.StatusBarViewModel != this)
return;

var instanceVM = InstanceViewModel;
if (instanceVM is null)
return;

if (!instanceVM.IsPageTypeZipFolder)
{
IsZipEncodingSelectorVisible = false;
return;
}

var workingDir = ContentPageContext.ShellPage?.ShellViewModel.WorkingDirectory;
if (string.IsNullOrEmpty(workingDir) || !ZipStorageFolder.IsZipPath(workingDir))
return;

if (TryRestoreZipEncodingFromContainerPath(workingDir))
return;

try
{
var isUndetermined = await StorageArchiveService.IsEncodingUndeterminedAsync(workingDir);
instanceVM.IsZipEncodingUndetermined = isUndetermined;

if (!isUndetermined)
{
IsZipEncodingSelectorVisible = false;
return;
}

var detected = await StorageArchiveService.DetectEncodingAsync(workingDir);
if (detected is not null)
{
instanceVM.ZipEncodingName = detected.WebName;
EncodingItem? ZipEncodingItem = ZipEncodingOptions.FirstOrDefault(e =>
e.Encoding?.WebName.Equals(detected.WebName, StringComparison.OrdinalIgnoreCase) == true);
if(ZipEncodingItem == null)
{
ZipEncodingItem = new EncodingItem(detected, detected.EncodingName);
}
ZipEncodingOptions.Add(ZipEncodingItem);
SelectedZipEncoding = ZipEncodingItem;
}
else
{
instanceVM.ZipEncodingName = null;
SelectedZipEncoding = ZipEncodingOptions.FirstOrDefault(e => e.Encoding is null);
}

IsZipEncodingSelectorVisible = true;
}
catch (Exception ex)
{
App.Logger.LogError(ex, "Error checking zip encoding.");
IsZipEncodingSelectorVisible = false;
}
}

private bool TryRestoreZipEncodingFromContainerPath(string workingDir)
{
if (!FileExtensionHelpers.IsBrowsableZipFile(workingDir, out var ext))
return false;

var marker = workingDir.IndexOf(ext, StringComparison.OrdinalIgnoreCase);
if (marker is -1)
return false;

var containerPath = workingDir.Substring(0, marker + ext.Length);
if (!ZipStorageFolder.TryGetEncodingForContainerPath(containerPath, out var encoding))
return false;

EncodingItem? match = encoding is not null
? ZipEncodingOptions.FirstOrDefault(e => e.Encoding == encoding)
: ZipEncodingOptions.FirstOrDefault(e => e.Encoding is null);

if (match is null && encoding is not null)
{
match = new EncodingItem(encoding, encoding.EncodingName);
ZipEncodingOptions.Add(match);
}

if (match is not null)
{
SelectedZipEncoding = match;
IsZipEncodingSelectorVisible = true;
return true;
}

return false;
}

private async Task OnZipEncodingChangedAsync(EncodingItem encodingItem)
{
if (ContentPageContext.ShellPage is null)
return;

if (ContentPageContext.ShellPage?.SlimContentPage?.StatusBarViewModel != this)
return;

var workingDir = ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory;
if (string.IsNullOrEmpty(workingDir))
return;

if (FileExtensionHelpers.IsBrowsableZipFile(workingDir, out var ext))
{
var marker = workingDir.IndexOf(ext, StringComparison.OrdinalIgnoreCase);
if (marker is not -1)
{
var containerPath = workingDir.Substring(0, marker + ext.Length);
ZipStorageFolder.SetEncodingForContainerPath(containerPath, encodingItem.Encoding);
}
}

ContentPageContext.ShellPage.ShellViewModel.RefreshItems(null);
}

internal void SubscribeToInstanceViewModel()
{
var instanceVM = InstanceViewModel;
if (instanceVM is not null)
instanceVM.PropertyChanged += OnInstanceViewModelPropertyChanged;
}

internal void UnsubscribeFromInstanceViewModel()
{
var instanceVM = InstanceViewModel;
if (instanceVM is not null)
instanceVM.PropertyChanged -= OnInstanceViewModelPropertyChanged;
}

private void OnInstanceViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(CurrentInstanceViewModel.IsPageTypeZipFolder))
{
_ = UpdateZipEncodingStateAsync();
}
}
}
}
4 changes: 3 additions & 1 deletion src/Files.App/Services/Storage/StorageArchiveService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,9 @@ public async Task<bool> IsEncodingUndeterminedAsync(string archiveFilePath)
{
using (ZipFile zipFile = new ZipFile(archiveFilePath))
{
return !zipFile.Cast<ZipEntry>().All(entry => entry.IsUnicodeText);
return !zipFile.Cast<ZipEntry>().All(
entry => entry.IsUnicodeText || entry.Name.All(c => char.IsAscii(c))
);
}
}
catch (Exception ex)
Expand Down
50 changes: 49 additions & 1 deletion src/Files.App/UserControls/StatusBar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<!-- Folder and selection info -->
Expand Down Expand Up @@ -193,10 +194,57 @@
Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
</StackPanel>

<!-- ZIP Encoding Selector -->
<Button
x:Name="ZipEncodingSelector"
Grid.Column="2"
Height="24"
Padding="8,0,8,0"
Background="Transparent"
BorderBrush="Transparent"
ToolTipService.ToolTip="{helpers:ResourceString Name=Encoding}"
Visibility="{x:Bind StatusBarViewModel.IsZipEncodingSelectorVisible, Mode=OneWay}">

<!-- Encoding Name -->
<TextBlock Text="{x:Bind StatusBarViewModel.SelectedZipEncoding.Name, Mode=OneWay, FallbackValue='', TargetNullValue=''}" />

<Button.Flyout>
<Flyout x:Name="ZipEncodingFlyout" Opening="ZipEncodingFlyout_Opening">
<Grid
Width="240"
Height="300"
Margin="-16">

<!-- Encodings List -->
<ListView
x:Name="ZipEncodingList"
Padding="4"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
IsItemClickEnabled="True"
ItemClick="ZipEncodingList_ItemClick"
ItemsSource="{x:Bind StatusBarViewModel.ZipEncodingOptions, Mode=OneWay}"
SelectedItem="{x:Bind StatusBarViewModel.SelectedZipEncoding, Mode=OneWay}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be TwoWay?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a ListView's ItemsSource is replaced (which happens every time StatusBarViewModel is reassigned during a tab switch), the ListView internally resets SelectedItem = null. With a TwoWay binding, this null propagates back to the ViewModel property, overwriting the previously selected encoding:

UpdateStatusBarProperties()
  → StatusBarViewModel DP set → DataContext changes
    → ItemsSource binding evaluates → ListView resets SelectedItem = null
    → SelectedItem TwoWay binding writes null → SelectedZipEncoding = null  ← bug

This happens despite UpdateZipEncodingStateAsync() already having restored the correct encoding synchronously earlier in the same UI cycle.

Changing to Mode=OneWay breaks the ListView → ViewModel propagation path, preventing the spurious null write. The user's selection is still synced to the ViewModel via the existing ItemClick handler (called when the user picks an encoding from the flyout):

https://github.com/oxygen-dioxide/Files/blob/3d0b867e34da179111a34691291fe28cf962d0d1/src/Files.App/UserControls/StatusBar.xaml.cs#L71-L77

ItemClickSelectedZipEncoding.set → triggers OnZipEncodingChangedAsync() → stores the encoding and refreshes the view, just as before.

The ViewModel → ListView direction (OneWay) still works, so on tab switch or encoding restore, the ListView correctly highlights the matching item from the ViewModel's SelectedZipEncoding value.

There is still a problem: when a zip file is opened, the detected zip encoding is shown on the button, but isn't shown as selected in the flyout menu. Let me fix it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should find the root cause and fix this to use the TwoWay binding.

SelectionMode="Single">

<ListView.ItemTemplate>
<DataTemplate x:DataType="data:EncodingItem">
<TextBlock
VerticalAlignment="Center"
Text="{x:Bind Name}"
TextTrimming="CharacterEllipsis" />
</DataTemplate>
</ListView.ItemTemplate>

</ListView>
</Grid>
</Flyout>
</Button.Flyout>
</Button>

<!-- Use visibility because it causes a crash to use a TwoWay x:Bind on an element that is inside an element with x:Load (#12589, #12599) -->
<Button
x:Name="GitBranch"
Grid.Column="2"
Grid.Column="3"
Height="24"
Margin="4,0,0,0"
Padding="8,0,8,0"
Expand Down
17 changes: 17 additions & 0 deletions src/Files.App/UserControls/StatusBar.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,28 @@ private async void BranchesFlyout_Opening(object _, object e)
StatusBarViewModel.SelectedBranchIndex = StatusBarViewModel.ACTIVE_BRANCH_INDEX;
}

private async void ZipEncodingFlyout_Opening(object _, object e)
{
if (StatusBarViewModel is null)
return;

await StatusBarViewModel.UpdateZipEncodingStateAsync();
if (StatusBarViewModel.SelectedZipEncoding is not null)
ZipEncodingList.SelectedItem = StatusBarViewModel.SelectedZipEncoding;
}

private void BranchesList_ItemClick(object sender, ItemClickEventArgs e)
{
BranchesFlyout.Hide();
}

private void ZipEncodingList_ItemClick(object sender, ItemClickEventArgs e)
{
ZipEncodingFlyout.Hide();
if (e.ClickedItem is Files.App.Data.Items.EncodingItem item && StatusBarViewModel is not null)
StatusBarViewModel.SelectedZipEncoding = item;
}

private void BranchesFlyout_Closing(object _, object e)
{
if (StatusBarViewModel is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ async Task<TOut> RetryWithCredentialsAsync<TOut>(Func<Task<TOut>> func, Exceptio
{
var handled = exception is SevenZipOpenFailedException szofex && szofex.Result is OperationResult.WrongPassword ||
exception is ExtractionFailedException efex && efex.Result is OperationResult.WrongPassword ||
exception is FtpAuthenticationException;
exception is FtpAuthenticationException ||
exception is ICSharpCode.SharpZipLib.Zip.ZipException szlzex && szlzex.Message.Contains("encrypted");

if (!handled || PasswordRequestedCallback is null)
throw exception;
Expand Down
Loading
Loading