Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
1 change: 1 addition & 0 deletions Screenbox.Core/Services/ISettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public interface ISettingsService
bool PlayerTapGesture { get; set; }
bool PlayerShowControls { get; set; }
bool PlayerShowChapters { get; set; }
int PlayerControlsHideDelay { get; set; }
int PersistentVolume { get; set; }
string PersistentSubtitleLanguage { get; set; }
bool ShowRecent { get; set; }
Expand Down
8 changes: 8 additions & 0 deletions Screenbox.Core/Services/SettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public sealed class SettingsService : ISettingsService
private const string PlayerSeekGestureKey = "Player/Gesture/Seek";
private const string PlayerTapGestureKey = "Player/Gesture/Tap";
private const string PlayerShowControlsKey = "Player/ShowControls";
private const string PlayerControlsHideDelayKey = "Player/ControlsHideDelay";
private const string PlayerLivelyPathKey = "Player/Lively/Path";
private const string LibrariesUseIndexerKey = "Libraries/UseIndexer";
private const string LibrariesSearchRemovableStorageKey = "Libraries/SearchRemovableStorage";
Expand Down Expand Up @@ -114,6 +115,12 @@ public bool PlayerShowControls
set => SetValue(PlayerShowControlsKey, value);
}

public int PlayerControlsHideDelay
{
get => GetValue<int>(PlayerControlsHideDelayKey);
set => SetValue(PlayerControlsHideDelayKey, value);
}

public bool SearchRemovableStorage
{
get => GetValue<bool>(LibrariesSearchRemovableStorageKey);
Expand Down Expand Up @@ -169,6 +176,7 @@ public SettingsService()
SetDefault(PlayerSeekGestureKey, true);
SetDefault(PlayerTapGestureKey, true);
SetDefault(PlayerShowControlsKey, true);
SetDefault(PlayerControlsHideDelayKey, 3);
SetDefault(PersistentVolumeKey, 100);
SetDefault(MaxVolumeKey, 100);
SetDefault(LibrariesUseIndexerKey, true);
Expand Down
6 changes: 4 additions & 2 deletions Screenbox.Core/ViewModels/PlayerPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -576,9 +576,11 @@ public bool TryHideControls(bool skipFocusCheck = false)
return true;
}

private void DelayHideControls(int delayInSeconds = 3)
private void DelayHideControls()
{
if (PlayerVisibility != PlayerVisibilityState.Visible || AudioOnly) return;

int delayInSeconds = _settingsService.PlayerControlsHideDelay;
_controlsVisibilityTimer.Debounce(() => TryHideControls(), TimeSpan.FromSeconds(delayInSeconds));
}

Expand All @@ -592,7 +594,7 @@ private void FocusManagerOnFocusChanged(object sender, FocusManagerGotFocusEvent
{
if (_visibilityOverride) return;
ControlsHidden = false;
DelayHideControls(4);
DelayHideControls();
}

private async void ProcessOpeningMedia(MediaViewModel? current)
Expand Down
10 changes: 10 additions & 0 deletions Screenbox.Core/ViewModels/SettingsPageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public sealed partial class SettingsPageViewModel : ObservableRecipient
[ObservableProperty] private bool _playerTapGesture;
[ObservableProperty] private bool _playerShowControls;
[ObservableProperty] private bool _playerShowChapters;
[ObservableProperty] private int _playerControlsHideDelay;
[ObservableProperty] private int _volumeBoost;
[ObservableProperty] private bool _useIndexer;
[ObservableProperty] private bool _showRecent;
Expand All @@ -51,6 +52,8 @@ public sealed partial class SettingsPageViewModel : ObservableRecipient

public List<Models.Language> AvailableLanguages { get; }

public int[] PlayerControlsHideDelayOptions { get; } = { 1, 2, 3, 4, 5 };
Copy link
Preview

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

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

The hardcoded array of options should be moved to a configuration file or made configurable. This makes it difficult to modify the available delay options without code changes.

Copilot uses AI. Check for mistakes.


private readonly ISettingsService _settingsService;
private readonly ILibraryService _libraryService;
private readonly DispatcherQueue _dispatcherQueue;
Expand Down Expand Up @@ -99,6 +102,7 @@ public SettingsPageViewModel(ISettingsService settingsService, ILibraryService l
_playerTapGesture = _settingsService.PlayerTapGesture;
_playerShowControls = _settingsService.PlayerShowControls;
_playerShowChapters = _settingsService.PlayerShowChapters;
_playerControlsHideDelay = _settingsService.PlayerControlsHideDelay;
_useIndexer = _settingsService.UseIndexer;
_showRecent = _settingsService.ShowRecent;
_theme = ((int)_settingsService.Theme + 2) % 3;
Expand Down Expand Up @@ -187,6 +191,12 @@ partial void OnPlayerShowChaptersChanged(bool value)
Messenger.Send(new SettingsChangedMessage(nameof(PlayerShowChapters), typeof(SettingsPageViewModel)));
}

partial void OnPlayerControlsHideDelayChanged(int value)
{
_settingsService.PlayerControlsHideDelay = value;
Messenger.Send(new SettingsChangedMessage(nameof(PlayerControlsHideDelay), typeof(SettingsPageViewModel)));
}

partial void OnUseIndexerChanged(bool value)
{
_settingsService.UseIndexer = value;
Expand Down
76 changes: 76 additions & 0 deletions Screenbox/Converters/IntToUnitStringConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#nullable enable

using System;
using Windows.UI.Xaml.Data;

namespace Screenbox.Converters;

/// <summary>
/// Defines the unit types for formatting numeric values with localized and pluralized resource strings.
/// </summary>
/// <remarks>This enumeration is used by the <see cref="IntToUnitStringConverter.Unit"/> property.</remarks>
public enum UnitType
{
None = 0,
Albums = 1,
Songs = 2,
Seconds = 4,
Items = 11,
Comment on lines +17 to +18
Copy link
Preview

Copilot AI Sep 17, 2025

Choose a reason for hiding this comment

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

The enum values use non-sequential numbers (1, 2, 4, 11) which creates magic numbers without clear purpose. Consider using sequential values starting from 0 or add comments explaining the significance of these specific numbers.

Suggested change
Seconds = 4,
Items = 11,
Seconds = 3,
Items = 4,

Copilot uses AI. Check for mistakes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, the gaps are there for time related units, like Millisecond, Minutes, Hours, etc.

}

/// <summary>
/// Converts an integer representing a quantity into a localized string representation for a specified unit,
/// automatically adjusting for singular or plural forms based on the value.
/// </summary>
public sealed class IntToUnitStringConverter : IValueConverter
{
/// <summary>
/// Gets or sets the unit type used to represent the count.
/// </summary>
public UnitType Unit { get; set; } = UnitType.None;

/// <summary>
/// Converts an <see cref="int"/> value to a localized <see cref="string"/>,
/// using the appropriate singular or plural resource for the specified unit type.
/// </summary>
/// <param name="value">The <see cref="int"/> being passed to the target.</param>
/// <param name="targetType">The type of the target property. Not used.</param>
/// <param name="parameter">An optional parameter to be used in the converter logic. Not used.</param>
/// <param name="language">The language of the conversion. Not used.</param>
/// <returns>The <see cref="string"/> representing the amount and its unit; otherwise, the value as a <see cref="string"/>.</returns>
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is int quantity && Unit is not UnitType.None)
{
return GetLocalizedCountAndUnit(quantity, Unit);
}

return value?.ToString() ?? string.Empty;
}

/// <summary>
/// Not implemented.
/// </summary>
/// <param name="value">The target data being passed to the source.</param>
/// <param name="targetType">The type of the target property.</param>
/// <param name="parameter">An optional parameter to be used in the converter logic.</param>
/// <param name="language">The language of the conversion.</param>
/// <returns>The value to be passed to the source object.</returns>
/// <exception cref="NotImplementedException"></exception>
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}

private string GetLocalizedCountAndUnit(int value, UnitType unit)
{
return unit switch
{
UnitType.Albums => Strings.Resources.AlbumsCount(value),
UnitType.Songs => Strings.Resources.SongsCount(value),
UnitType.Seconds => Strings.Resources.SecondsCount(value),
UnitType.Items => Strings.Resources.ItemsCount(value),
_ => $"{value} {unit.ToString().ToLowerInvariant()}"
};
}
Copy link
Owner

Choose a reason for hiding this comment

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

I'm confused. How would we use this code? I only see the seconds unit being used.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It was made for the seconds unit, but I chose to expand it to make it future-proof. If you think it's unnecessary or detrimental, I can scale it back to its original focus.

}
37 changes: 26 additions & 11 deletions Screenbox/Pages/SettingsPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

<converters:ResourceNameToResourceStringConverter x:Key="ResourceNameToResourceStringConverter" />
<converters:LanguageLayoutDirectionToFlowDirectionConverter x:Key="LanguageLayoutDirectionToFlowDirectionConverter" />
<converters:IntToUnitStringConverter x:Key="HideDelayOptionsToUnitStringConverter" Unit="Seconds" />

<!-- Section header text style -->
<Style
Expand Down Expand Up @@ -401,19 +402,33 @@
</ComboBox>
</ctc:SettingsCard>

<ctc:SettingsCard
Margin="{StaticResource SettingsCardMargin}"
Header="{strings:Resources Key=SettingsShowControlsHeader}"
HeaderIcon="{ui:FontIcon Glyph=&#xE75B;}">
<ToggleSwitch x:Name="ShowControlsToggleSwitch" IsOn="{x:Bind ViewModel.PlayerShowControls, Mode=TwoWay}" />
</ctc:SettingsCard>

<ctc:SettingsCard
<ctc:SettingsExpander
Margin="{StaticResource SettingsCardMargin}"
Header="{strings:Resources Key=SettingsShowChaptersHeader}"
Description="{x:Bind strings:Resources.SettingsOverlayControlsDescription}"
Header="{x:Bind strings:Resources.SettingsOverlayControlsHeader}"
HeaderIcon="{ui:FontIcon Glyph=&#xE75B;}">
<ToggleSwitch x:Name="ShowChaptersToggleSwitch" IsOn="{x:Bind ViewModel.PlayerShowChapters, Mode=TwoWay}" />
</ctc:SettingsCard>
<ctc:SettingsExpander.Items>
<ctc:SettingsCard Header="{x:Bind strings:Resources.SettingsShowControlsHeader}">
<ToggleSwitch IsOn="{x:Bind ViewModel.PlayerShowControls, Mode=TwoWay}" />
</ctc:SettingsCard>
<ctc:SettingsCard Header="{x:Bind strings:Resources.SettingsShowChaptersHeader}">
<ToggleSwitch IsOn="{x:Bind ViewModel.PlayerShowChapters, Mode=TwoWay}" />
</ctc:SettingsCard>
<ctc:SettingsCard Header="{x:Bind strings:Resources.SettingsControlsHideDelayHeader}">
<ComboBox
MinWidth="150"
AutomationProperties.HelpText="{x:Bind strings:Resources.SettingsControlsHideDelayHeader}"
ItemsSource="{x:Bind ViewModel.PlayerControlsHideDelayOptions, Mode=OneWay}"
SelectedItem="{x:Bind ViewModel.PlayerControlsHideDelay, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="x:Int32">
<TextBlock Text="{x:Bind Converter={StaticResource HideDelayOptionsToUnitStringConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ctc:SettingsCard>
</ctc:SettingsExpander.Items>
</ctc:SettingsExpander>

<ctc:SettingsExpander
Margin="{StaticResource SettingsCardMargin}"
Expand Down
1 change: 1 addition & 0 deletions Screenbox/Screenbox.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@
<Compile Include="Converters\DefaultStringConverter.cs" />
<Compile Include="Converters\FirstOrDefaultConverter.cs" />
<Compile Include="Converters\HumanizedDurationConverter.cs" />
<Compile Include="Converters\IntToUnitStringConverter.cs" />
<Compile Include="Converters\LanguageLayoutDirectionToFlowDirectionConverter.cs" />
<Compile Include="Converters\MediaGlyphConverter.cs" />
<Compile Include="Converters\PlayPauseGlyphConverter.cs" />
Expand Down
17 changes: 17 additions & 0 deletions Screenbox/Strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,12 @@
<data name="SettingsVolumeBoostDescription" xml:space="preserve">
<value>Allow the maximum volume to go above 100%</value>
</data>
<data name="SettingsOverlayControlsHeader" xml:space="preserve">
<value>Overlay controls</value>
</data>
<data name="SettingsOverlayControlsDescription" xml:space="preserve">
<value>Change the appearance and behavior of the on-screen player controls</value>
</data>
<data name="SettingsGesturesHeader" xml:space="preserve">
<value>Gestures</value>
</data>
Expand Down Expand Up @@ -938,6 +944,17 @@
<data name="SettingsShowChaptersHeader" xml:space="preserve">
<value>Display chapter name when available</value>
</data>
<data name="SettingsControlsHideDelayHeader" xml:space="preserve">
<value>Dismiss controls after this amount of time without interaction</value>
</data>
<data name="SecondsCount_One" xml:space="preserve">
<value>{0} second</value>
<comment>#Format[Plural Int]</comment>
</data>
<data name="SecondsCount_Other" xml:space="preserve">
<value>{0} seconds</value>
<comment>#Format[Plural Int]</comment>
</data>
<data name="SettingsLanguageSelectionHeader" xml:space="preserve">
<value>App language</value>
</data>
Expand Down
Loading