Skip to content

New API Function from Theme & Improve Theme Model #3420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 8, 2025
Merged
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
31 changes: 13 additions & 18 deletions Flow.Launcher.Core/Resource/Theme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,6 @@ public Theme(IPublicAPI publicAPI, Settings settings)

#region Theme Resources

public string GetCurrentTheme()
{
return _settings.Theme;
}

private void MakeSureThemeDirectoriesExist()
{
foreach (var dir in _themeDirectories.Where(dir => !Directory.Exists(dir)))
Expand Down Expand Up @@ -127,7 +122,7 @@ public void UpdateFonts()
try
{
// Load a ResourceDictionary for the specified theme.
var themeName = GetCurrentTheme();
var themeName = _settings.Theme;
var dict = GetThemeResourceDictionary(themeName);

// Apply font settings to the theme resource.
Expand Down Expand Up @@ -330,7 +325,7 @@ private ResourceDictionary GetResourceDictionary(string theme)

private ResourceDictionary GetCurrentResourceDictionary()
{
return GetResourceDictionary(GetCurrentTheme());
return GetResourceDictionary(_settings.Theme);
}

private ThemeData GetThemeDataFromPath(string path)
Expand Down Expand Up @@ -383,9 +378,15 @@ private string GetThemePath(string themeName)

#endregion

#region Load & Change
#region Get & Change Theme

public ThemeData GetCurrentTheme()
{
var themes = GetAvailableThemes();
return themes.FirstOrDefault(t => t.FileNameWithoutExtension == _settings.Theme) ?? themes.FirstOrDefault();
}

public List<ThemeData> LoadAvailableThemes()
public List<ThemeData> GetAvailableThemes()
{
List<ThemeData> themes = new List<ThemeData>();
foreach (var themeDirectory in _themeDirectories)
Expand All @@ -403,7 +404,7 @@ public List<ThemeData> LoadAvailableThemes()
public bool ChangeTheme(string theme = null)
{
if (string.IsNullOrEmpty(theme))
theme = GetCurrentTheme();
theme = _settings.Theme;

string path = GetThemePath(theme);
try
Expand Down Expand Up @@ -591,7 +592,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
{
AutoDropShadow(useDropShadowEffect);
}
SetBlurForWindow(GetCurrentTheme(), backdropType);
SetBlurForWindow(_settings.Theme, backdropType);

if (!BlurEnabled)
{
Expand All @@ -610,7 +611,7 @@ await Application.Current.Dispatcher.InvokeAsync(() =>
// Get the actual backdrop type and drop shadow effect settings
var (backdropType, _) = GetActualValue();

SetBlurForWindow(GetCurrentTheme(), backdropType);
SetBlurForWindow(_settings.Theme, backdropType);
}, DispatcherPriority.Render);
}

Expand Down Expand Up @@ -898,11 +899,5 @@ private static bool IsBlurTheme()
}

#endregion

#region Classes

public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null);

#endregion
}
}
19 changes: 19 additions & 0 deletions Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -344,5 +344,24 @@ public interface IPublicAPI
/// Stop the loading bar in main window
/// </summary>
public void StopLoadingBar();

/// <summary>
/// Get all available themes
/// </summary>
/// <returns></returns>
public List<ThemeData> GetAvailableThemes();

/// <summary>
/// Get the current theme
/// </summary>
/// <returns></returns>
public ThemeData GetCurrentTheme();

/// <summary>
/// Set the current theme
/// </summary>
/// <param name="theme"></param>
/// <returns></returns>
public void SetCurrentTheme(ThemeData theme);
}
}
73 changes: 73 additions & 0 deletions Flow.Launcher.Plugin/ThemeData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using System;

namespace Flow.Launcher.Plugin;

/// <summary>
/// Theme data model
/// </summary>
public class ThemeData
Copy link
Member

Choose a reason for hiding this comment

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

Everything else LGTM, could you move this to SharedModel directory. Any that is just a model of an object class should go into that directory.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

{
/// <summary>
/// Theme file name without extension
/// </summary>
public string FileNameWithoutExtension { get; private init; }

/// <summary>
/// Theme name
/// </summary>
public string Name { get; private init; }

/// <summary>
/// Theme file path
/// </summary>
public bool? IsDark { get; private init; }

/// <summary>
/// Theme file path
/// </summary>
public bool? HasBlur { get; private init; }

/// <summary>
/// Theme data constructor
/// </summary>
public ThemeData(string fileNameWithoutExtension, string name, bool? isDark = null, bool? hasBlur = null)
{
FileNameWithoutExtension = fileNameWithoutExtension;
Name = name;
IsDark = isDark;
HasBlur = hasBlur;
}

/// <inheritdoc />
public static bool operator ==(ThemeData left, ThemeData right)
{
return left.Equals(right);
}

/// <inheritdoc />
public static bool operator !=(ThemeData left, ThemeData right)
{
return !(left == right);
}

/// <inheritdoc />
public override bool Equals(object obj)
{
if (obj is not ThemeData other)
return false;
return FileNameWithoutExtension == other.FileNameWithoutExtension &&
Name == other.Name;
}

/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(FileNameWithoutExtension, Name);
}

/// <inheritdoc />
public override string ToString()
{
return Name;
}
}
13 changes: 13 additions & 0 deletions Flow.Launcher/PublicAPIInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public class PublicAPIInstance : IPublicAPI
private readonly Internationalization _translater;
private readonly MainViewModel _mainVM;

private Theme _theme;
private Theme Theme => _theme ??= Ioc.Default.GetRequiredService<Theme>();

private readonly object _saveSettingsLock = new();

#region Constructor
Expand Down Expand Up @@ -354,6 +357,16 @@ public MessageBoxResult ShowMsgBox(string messageBoxText, string caption = "", M

public Task ShowProgressBoxAsync(string caption, Func<Action<double>, Task> reportProgressAsync, Action cancelProgress = null) => ProgressBoxEx.ShowAsync(caption, reportProgressAsync, cancelProgress);

public List<ThemeData> GetAvailableThemes() => Theme.GetAvailableThemes();

public ThemeData GetCurrentTheme() => Theme.GetCurrentTheme();

public void SetCurrentTheme(ThemeData theme)
{
Theme.ChangeTheme(theme.FileNameWithoutExtension);
_ = _theme.RefreshFrameAsync();
Copy link
Preview

Copilot AI Apr 8, 2025

Choose a reason for hiding this comment

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

Consider awaiting RefreshFrameAsync() or properly handling its exceptions to prevent silent failures in theme refresh.

Suggested change
_ = _theme.RefreshFrameAsync();
try
{
await _theme.RefreshFrameAsync();
}
catch (Exception ex)
{
// Log the exception or handle it as needed
Log.Error($"Failed to refresh theme: {ex.Message}");
}

Copilot uses AI. Check for mistakes.

Copy link
Member

Choose a reason for hiding this comment

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

@Jack251970 is this applicable?

Copy link
Member Author

Choose a reason for hiding this comment

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

@jjw24 I think we can remove _ = _theme.RefreshFrameAsync(); here since ChangeTheme already calls this function.

Copy link
Member Author

@Jack251970 Jack251970 Apr 8, 2025

Choose a reason for hiding this comment

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

For me, changing theme from Appreance Page and Sys plugin still work well

}

#endregion

#region Private Methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel
public static string LinkHowToCreateTheme => @"https://www.flowlauncher.com/theme-builder/";
public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438";

private List<Theme.ThemeData> _themes;
public List<Theme.ThemeData> Themes => _themes ??= _theme.LoadAvailableThemes();
private List<ThemeData> _themes;
public List<ThemeData> Themes => _themes ??= App.API.GetAvailableThemes();

private Theme.ThemeData _selectedTheme;
public Theme.ThemeData SelectedTheme
private ThemeData _selectedTheme;
public ThemeData SelectedTheme
{
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme());
get => _selectedTheme ??= Themes.Find(v => v == App.API.GetCurrentTheme());
set
{
_selectedTheme = value;
_theme.ChangeTheme(value.FileNameWithoutExtension);
App.API.SetCurrentTheme(value);

// Update UI state
OnPropertyChanged(nameof(BackdropType));
OnPropertyChanged(nameof(IsBackdropEnabled));
OnPropertyChanged(nameof(IsDropShadowEnabled));
OnPropertyChanged(nameof(DropShadowEffect));

_ = _theme.RefreshFrameAsync();
}
}

Expand Down
47 changes: 11 additions & 36 deletions Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.Resource;

namespace Flow.Launcher.Plugin.Sys
{
Expand All @@ -11,61 +9,38 @@ public class ThemeSelector

private readonly PluginInitContext _context;

// Do not initialize it in the constructor, because it will cause null reference in
// var dicts = Application.Current.Resources.MergedDictionaries; line of Theme
private Theme theme = null;
private Theme Theme => theme ??= Ioc.Default.GetRequiredService<Theme>();

#region Theme Selection

// Theme select codes simplified from SettingsPaneThemeViewModel.cs

private Theme.ThemeData _selectedTheme;
public Theme.ThemeData SelectedTheme
{
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Theme.GetCurrentTheme());
set
{
_selectedTheme = value;
Theme.ChangeTheme(value.FileNameWithoutExtension);

_ = Theme.RefreshFrameAsync();
}
}

private List<Theme.ThemeData> Themes => Theme.LoadAvailableThemes();

#endregion

public ThemeSelector(PluginInitContext context)
{
_context = context;
}

public List<Result> Query(Query query)
{
var themes = _context.API.GetAvailableThemes();
var selectedTheme = _context.API.GetCurrentTheme();

var search = query.SecondToEndSearch;
if (string.IsNullOrWhiteSpace(search))
{
return Themes.Select(CreateThemeResult)
return themes.Select(x => CreateThemeResult(x, selectedTheme))
.OrderBy(x => x.Title)
.ToList();
}

return Themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name)))
return themes.Select(theme => (theme, matchResult: _context.API.FuzzySearch(search, theme.Name)))
.Where(x => x.matchResult.IsSearchPrecisionScoreMet())
.Select(x => CreateThemeResult(x.theme, x.matchResult.Score, x.matchResult.MatchData))
.Select(x => CreateThemeResult(x.theme, selectedTheme, x.matchResult.Score, x.matchResult.MatchData))
.OrderBy(x => x.Title)
.ToList();
}

private Result CreateThemeResult(Theme.ThemeData theme) => CreateThemeResult(theme, 0, null);
private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme) => CreateThemeResult(theme, selectedTheme, 0, null);

private Result CreateThemeResult(Theme.ThemeData theme, int score, IList<int> highlightData)
private Result CreateThemeResult(ThemeData theme, ThemeData selectedTheme, int score, IList<int> highlightData)
{
string themeName = theme.Name;
var themeName = theme.FileNameWithoutExtension;
string title;
if (theme == SelectedTheme)
if (theme == selectedTheme)
{
title = $"{theme.Name} ★";
// Set current theme to the top
Expand Down Expand Up @@ -101,7 +76,7 @@ private Result CreateThemeResult(Theme.ThemeData theme, int score, IList<int> hi
Score = score,
Action = c =>
{
SelectedTheme = theme;
_context.API.SetCurrentTheme(theme);
_context.API.ReQuery();
return false;
}
Expand Down
Loading