Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7e633f1
preparation
ToobLac Aug 22, 2026
daaff93
impl
ToobLac Aug 22, 2026
93f5ed7
update
ToobLac Aug 22, 2026
5758cc9
avoid fetching version list twice
ToobLac Aug 22, 2026
b649c26
re-impl
ToobLac Aug 24, 2026
b3832b4
update
ToobLac Aug 27, 2026
769c438
update
ToobLac Aug 29, 2026
436ca08
Merge remote-tracking branch 'upstream/main' into mod-dep
ToobLac Sep 4, 2026
9a374e3
update
ToobLac Sep 4, 2026
0b131a7
update
ToobLac Sep 6, 2026
42603e8
update
ToobLac Sep 8, 2026
6dfdc82
Merge remote-tracking branch 'upstream/main' into mod-dep
ToobLac Sep 8, 2026
f0c14f1
do not verify dependency installation status for modpacks
ToobLac Sep 8, 2026
8977d7b
invalidate addon manager after downloading
ToobLac Sep 8, 2026
3836cc7
update
ToobLac Sep 8, 2026
dbc85ea
Merge remote-tracking branch 'upstream/main' into mod-dep
Glavo Sep 9, 2026
473bb2a
Merge remote-tracking branch 'upstream/main' into mod-dep
ToobLac Sep 10, 2026
3de8abd
using platform-provided sha-1 hash
ToobLac Sep 10, 2026
e5c7c3e
update
ToobLac Sep 11, 2026
2d03e2c
update
ToobLac Sep 11, 2026
588ff32
Merge remote-tracking branch 'upstream/main' into mod-dep
ToobLac Sep 12, 2026
1dc6911
update
ToobLac Sep 12, 2026
1b70b35
Merge remote-tracking branch 'upstream/main' into mod-dep
ToobLac Sep 13, 2026
4c173a0
update
ToobLac Sep 16, 2026
6b53808
update
ToobLac Sep 17, 2026
097f904
update
ToobLac Sep 17, 2026
08957f6
Merge remote-tracking branch 'upstream/main' into mod-dep
ToobLac Sep 17, 2026
c1da853
Merge remote-tracking branch 'upstream/main' into mod-dep
ToobLac Sep 19, 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
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@ public abstract class LocalizedRemoteAddonRepository implements RemoteAddonRepos

private static final int INITIAL_CAPACITY = 16;

protected abstract RemoteAddonRepository getBackedRemoteModRepository();
protected abstract RemoteAddonRepository getBackedRepository();

@Override
public String getApiBaseUrl() {
return getBackedRemoteModRepository().getApiBaseUrl();
return getBackedRepository().getApiBaseUrl();
}

@Override
public String getBaseUrl() {
return getBackedRemoteModRepository().getBaseUrl();
return getBackedRepository().getBaseUrl();
}

@Override
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
if (!StringUtils.containsChinese(searchFilter)) {
return getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder);
return getBackedRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder);
}

Set<String> englishSearchFiltersSet = new LinkedHashSet<>(INITIAL_CAPACITY);
Expand All @@ -69,7 +69,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion
}

if (englishSearchFiltersSet.isEmpty()) {
return getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder);
return getBackedRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, searchFilter, sort, sortOrder);
}

RemoteAddon[] searchResultArray = new RemoteAddon[pageSize];
Expand All @@ -78,7 +78,7 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion
SearchResult searchResult = null;
List<RemoteAddon> remoteAddons = List.of();
for (String englishSearchFilter : englishSearchFiltersSet) {
searchResult = getBackedRemoteModRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, englishSearchFilter, SortType.RELEVANCY, sortOrder);
searchResult = getBackedRepository().search(downloadProvider, gameVersion, category, pageOffset, pageSize, englishSearchFilter, SortType.RELEVANCY, sortOrder);
remoteAddons = searchResult.results().toList();
if (!remoteAddons.isEmpty()) {
break;
Expand Down Expand Up @@ -123,41 +123,41 @@ public SearchResult search(DownloadProvider downloadProvider, String gameVersion

@Override
public Stream<Category> getCategories() throws IOException {
return getBackedRemoteModRepository().getCategories();
return getBackedRepository().getCategories();
}

@Override
public Optional<RemoteAddon.Version> getRemoteVersionByLocalFile(Path file) throws IOException {
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(file);
return getBackedRepository().getRemoteVersionByLocalFile(file);
}

@Override
public RemoteAddon getAddonById(DownloadProvider downloadProvider, String id) throws IOException {
return getBackedRemoteModRepository().getAddonById(downloadProvider, id);
return getBackedRepository().getAddonById(downloadProvider, id);
}

@Override
public RemoteAddon resolveDependency(DownloadProvider downloadProvider, String id) throws IOException {
return getBackedRemoteModRepository().resolveDependency(downloadProvider, id);
return getBackedRepository().resolveDependency(downloadProvider, id);
}

@Override
public RemoteAddon.File getAddonFile(String projectId, String fileId) throws IOException {
return getBackedRemoteModRepository().getAddonFile(projectId, fileId);
return getBackedRepository().getAddonFile(projectId, fileId);
}

@Override
public Stream<RemoteAddon.Version> getRemoteVersionsById(DownloadProvider downloadProvider, String id) throws IOException {
return getBackedRemoteModRepository().getRemoteVersionsById(downloadProvider, id);
return getBackedRepository().getRemoteVersionsById(downloadProvider, id);
}

@Override
public String getAddonChangelog(DownloadProvider downloadProvider, String addonId, String versionId) throws IOException {
return getBackedRemoteModRepository().getAddonChangelog(downloadProvider, addonId, versionId);
return getBackedRepository().getAddonChangelog(downloadProvider, addonId, versionId);
}

@Override
public @NotNull String getVersionPageUrl(RemoteAddon.Version version) throws IOException {
return getBackedRemoteModRepository().getVersionPageUrl(version);
return getBackedRepository().getVersionPageUrl(version);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ public Label getSubtitleLabel() {
return lblSubtitle;
}

private static Label createTag(String tag, PseudoClass pseudoClass) {
/// Creates a tag label and activates the pseudo-class when it is non-null.
private static Label createTag(@Nullable String tag, @Nullable PseudoClass pseudoClass) {
var tagLabel = new Label(tag);
tagLabel.getStyleClass().add("tag");
tagLabel.setMinWidth(Label.USE_PREF_SIZE);
Expand Down Expand Up @@ -239,18 +240,26 @@ private double computeNaturalPrefWidth(double height) {
}
}

public void addTag(String tag, PseudoClass pseudoClass) {
/// Appends a tag and activates the pseudo-class when it is non-null.
public void addTag(@Nullable String tag, @Nullable PseudoClass pseudoClass) {
getTags().add(createTag(tag, pseudoClass));
}

public void addTag(String tag) {
addTag(tag, null);
}

/// Prepends a tag and activates the pseudo-class when it is non-null.
public void addTagFirst(@Nullable String tag, @Nullable PseudoClass pseudoClass) {
getTags().add(0, createTag(tag, pseudoClass));
}

/// Appends tags in iteration order, retaining duplicate text.
public void addTags(Collection<String> tags) {
getTags().addAll(tags.stream().map(tag -> createTag(tag, null)).toList());
}

/// Appends each distinct tag text absent from the current labels, in iteration order.
public void addTagsIfNotExist(Collection<String> tags) {
var current = getTags().stream().map(Label::getText).collect(Collectors.toSet());
var target = new LinkedHashSet<>(tags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@

public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
public static final org.jackhuang.hmcl.ui.instances.DownloadPage.DownloadCallback FOR_MOD =
(downloadProvider, repository, version, mod, file) -> download(downloadProvider, repository, version, file, "mods");
(downloadProvider, repository, version, mod, file) -> download(downloadProvider, repository, version, file, "mods", RemoteAddon.Type.MOD);
public static final org.jackhuang.hmcl.ui.instances.DownloadPage.DownloadCallback FOR_RESOURCE_PACK =
(downloadProvider, repository, version, pack, file) -> download(downloadProvider, repository, version, file, "resourcepacks");
(downloadProvider, repository, version, pack, file) -> download(downloadProvider, repository, version, file, "resourcepacks", RemoteAddon.Type.RESOURCE_PACK);
public static final org.jackhuang.hmcl.ui.instances.DownloadPage.DownloadCallback FOR_SHADER =
(downloadProvider, repository, version, shader, file) -> download(downloadProvider, repository, version, file, "shaderpacks");
(downloadProvider, repository, version, shader, file) -> download(downloadProvider, repository, version, file, "shaderpacks", RemoteAddon.Type.SHADER_PACK);

private final ReadOnlyObjectWrapper<DecoratorPage.State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1));
private final TabHeader tab;
Expand Down Expand Up @@ -142,7 +142,7 @@ private static <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSuppl
};
}

public static void download(DownloadProvider downloadProvider, HMCLGameRepository repository, @Nullable GameInstanceID instanceId, RemoteAddon.Version file, String subdirectoryName) {
public static void download(DownloadProvider downloadProvider, HMCLGameRepository repository, @Nullable GameInstanceID instanceId, RemoteAddon.Version file, String subdirectoryName, RemoteAddon.Type type) {
@Nullable HMCLGameInstance instance = instanceId != null
? repository.findInstance(instanceId)
: repository.getSelectedInstance();
Expand All @@ -161,10 +161,13 @@ public static void download(DownloadProvider downloadProvider, HMCLGameRepositor
Controllers.prompt(i18n("archive.file.name"), (result, handler) -> {
Path dest = targetPath.resolve(result);

Controllers.taskDialog(Task.composeAsync(() -> {
var task = new FileDownloadTask(downloadProvider.injectURLWithCandidates(file.file().url()), dest);
task.setName(file.name());
return task;
Controllers.taskDialog(Task.composeAsync(() ->
new FileDownloadTask(downloadProvider.injectURLWithCandidates(file.file().url()), dest).setName(file.name())
).whenComplete(Schedulers.io(), exception -> {
if (exception == null && instance != null) {
var manager = instance.getManagerForType(type);
if (manager != null) manager.invalidate();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh the installed badge after a successful download

When an addon is downloaded from its detail page, that page remains open and its installed property is only assigned by the initial loadAddonVersions() call. Even with this new manager invalidation, a successful download never rechecks or updates that property, so the page continues to show the addon as not installed until the user leaves and reopens it; notify the detail page of completion or trigger a new installed check after success.

Useful? React with 👍 / 👎.

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.

我觉得这是小问题

}
}).whenComplete(Schedulers.javafx(), exception -> {
if (exception != null) {
if (!(exception instanceof CancellationException)) {
Expand Down
Loading
Loading