Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
Expand Down Expand Up @@ -64,6 +66,7 @@ public class DownloadPage extends Control implements DecoratorPage {
private final BooleanProperty loaded = new SimpleBooleanProperty(false);
private final BooleanProperty loading = new SimpleBooleanProperty(false);
private final BooleanProperty failed = new SimpleBooleanProperty(false);
private final BooleanProperty installed = new SimpleBooleanProperty(false);
private final RemoteAddonRepository repository;
private final ModTranslations translations;
private final RemoteAddon addon;
Expand Down Expand Up @@ -94,11 +97,12 @@ private void loadAddonVersions() {
setFailed(false);

Task.supplyAsync(() -> {
Stream<RemoteAddon.Version> versions = addon.data().loadVersions(repository, page.getDownloadProvider());
return sortVersions(versions);
List<RemoteAddon.Version> versions = repository.getRemoteVersionsById(getDownloadProvider(), addon.id()).toList();
return Pair.pair(sortVersions(versions.stream()), addon.checkInstalled(versions.stream(), instanceReference.instance()));
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
this.versions = result;
this.versions = result.key();
this.installed.set(result.value());

loaded.set(true);
setFailed(false);
Expand Down Expand Up @@ -129,6 +133,10 @@ public RemoteAddon getAddon() {
return addon;
}

public DownloadProvider getDownloadProvider() {
return page.getDownloadProvider();
}

public HMCLGameInstance.Optional getInstanceOptional() {
return instanceReference;
}
Expand Down Expand Up @@ -231,6 +239,20 @@ protected DownloadPageSkin(DownloadPage control) {
content.getFirstLine().setMinWidth(0);
descriptionPane.getChildren().add(content);

if (control.installed.get()) {
content.addTagFirst(i18n("addon.installed"), null);
} else {
control.installed.addListener(new ChangeListener<>() {
@Override
public void changed(ObservableValue<? extends Boolean> o, Boolean __, Boolean newValue) {
if (newValue) {
content.addTagFirst(i18n("addon.installed"), null);
control.installed.removeListener(this);
}
}
});
}

if (getSkinnable().mod != null) {
JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod"));
openMcmodButton.setExternalLink(getSkinnable().translations.getMcmodUrl(getSkinnable().mod));
Expand Down Expand Up @@ -373,7 +395,7 @@ private static final class DependencyAddonItem extends LineButton {

public final RemoteAddon addon;

DependencyAddonItem(DownloadListPage page, RemoteAddon addon, HMCLGameInstance.Optional instanceReference) {
DependencyAddonItem(DownloadListPage page, RemoteAddon addon, HMCLGameInstance.Optional instanceReference, boolean installed) {
this.addon = addon;

HBox pane = new HBox(8);
Expand Down Expand Up @@ -402,6 +424,8 @@ private static final class DependencyAddonItem extends LineButton {
ModTranslations.Mod mod = ModTranslations.getTranslationsByAddonType(type).getModByCurseForgeId(addon.slug());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.title());
content.setSubtitle(addon.description());
if (installed)
content.addTag(i18n("addon.installed"));
for (String category : addon.categories()) {
if (page.shouldDisplayCategory(category))
content.addTag(page.getLocalizedCategory(category, null));
Expand Down Expand Up @@ -483,7 +507,7 @@ private static final class AddonItem extends StackPane {
}

private static final class AddonVersion extends JFXDialogLayout {
public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage selfPage) {
public AddonVersion(RemoteAddon addon, RemoteAddon.Version version, DownloadPage selfPage) {
RemoteAddon.Type type = selfPage.type;

String title = switch (type) {
Expand All @@ -497,12 +521,12 @@ public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage s

VBox box = new VBox(8);
box.setPadding(new Insets(8));
var addonItem = new AddonItem(mod, version, selfPage);
var addonItem = new AddonItem(addon, version, selfPage);
addonItem.setMouseTransparent(true); // Item is displayed for info, clicking shouldn't open the dialog again
box.getChildren().setAll(addonItem);

JFXHyperlink changelogButton = new JFXHyperlink(i18n("addon.changelog"));
changelogButton.setOnAction(__ -> Controllers.dialog(new AddonChangelog(version, selfPage.repository, selfPage.page.getDownloadProvider())));
changelogButton.setOnAction(__ -> Controllers.dialog(new AddonChangelog(version, selfPage.repository, selfPage.getDownloadProvider())));

JFXHyperlink versionPageBtn = new JFXHyperlink(i18n("mods.url"));
versionPageBtn.setDisable(true);
Expand All @@ -514,8 +538,8 @@ public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage s
SpinnerPane spinnerPane = new SpinnerPane();
ScrollPane scrollPane = new ScrollPane();
ComponentList dependenciesList = new ComponentList();
loadDependencies(version, selfPage, spinnerPane, dependenciesList);
spinnerPane.setOnFailedAction(e -> loadDependencies(version, selfPage, spinnerPane, dependenciesList));
loadDependencies(type, version, selfPage, spinnerPane, dependenciesList);
spinnerPane.setOnFailedAction(e -> loadDependencies(type, version, selfPage, spinnerPane, dependenciesList));

scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
scrollPane.setContent(dependenciesList);
Expand All @@ -538,7 +562,7 @@ public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage s
if (type == RemoteAddon.Type.MODPACK || !spinnerPane.isLoading() && spinnerPane.getFailedReason() == null) {
fireEvent(new DialogCloseEvent());
}
selfPage.download(mod, version);
selfPage.download(addon, version);
});
}

Expand Down Expand Up @@ -567,7 +591,7 @@ public AddonVersion(RemoteAddon mod, RemoteAddon.Version version, DownloadPage s
onEscPressed(this, cancelButton::fire);
}

private void loadDependencies(RemoteAddon.Version version, DownloadPage selfPage, SpinnerPane spinnerPane, ComponentList dependenciesList) {
private void loadDependencies(RemoteAddon.Type thisAddonType, RemoteAddon.Version version, DownloadPage selfPage, SpinnerPane spinnerPane, ComponentList dependenciesList) {
spinnerPane.setLoading(true);
Task.composeAsync(() -> {
// TODO: Massive tasks may cause OOM.
Expand All @@ -588,14 +612,21 @@ private void loadDependencies(RemoteAddon.Version version, DownloadPage selfPage
dependencies.put(dependency.getType(), Pair.pair(title, list));
}

queue.add(Task.supplyAsync(Schedulers.io(), () -> dependency.load(selfPage.page.getDownloadProvider()))
queue.add(Task.supplyAsync(Schedulers.io(), () -> {
var depAddon = dependency.load(selfPage.getDownloadProvider());
if (depAddon == RemoteAddon.BROKEN || thisAddonType == RemoteAddon.Type.MODPACK) return Pair.pair(depAddon, false);
return Pair.pair(depAddon, depAddon.checkInstalled(
selfPage.repository.getRemoteVersionsById(selfPage.getDownloadProvider(), depAddon.id()),
selfPage.getInstanceOptional().instance()
));
Comment thread
ToobLac marked this conversation as resolved.
})
.setSignificance(Task.TaskSignificance.MINOR)
.thenAcceptAsync(Schedulers.javafx(), dep -> {
if (dep == RemoteAddon.BROKEN) {
if (dep.key() == RemoteAddon.BROKEN) {
hasBroken.set(true);
return;
}
DependencyAddonItem dependencyAddonItem = new DependencyAddonItem(selfPage.page, dep, selfPage.instanceReference);
DependencyAddonItem dependencyAddonItem = new DependencyAddonItem(selfPage.page, dep.key(), selfPage.instanceReference, dep.value());
var listener = FXUtils.onWeakChangeAndOperate(dependenciesList.widthProperty(), d -> FXUtils.setLimitWidth(dependencyAddonItem, d.doubleValue()));
dependencyAddonItem.getProperties().put("DependencyAddonItem.width", listener);
dependencies.get(dependency.getType()).value().add(dependencyAddonItem);
Expand Down
Loading