3535import javafx .scene .control .Label ;
3636import javafx .scene .control .Skin ;
3737import javafx .scene .control .SkinBase ;
38+ import javafx .scene .image .Image ;
3839import javafx .scene .image .ImageView ;
3940import javafx .scene .input .KeyCode ;
4041import javafx .scene .input .KeyEvent ;
6061import org .jackhuang .hmcl .util .Lang ;
6162import org .jackhuang .hmcl .util .StringUtils ;
6263import org .jackhuang .hmcl .util .i18n .I18n ;
64+ import org .jackhuang .hmcl .util .io .NetworkUtils ;
6365import org .jackhuang .hmcl .util .javafx .BindingMapping ;
6466import org .jackhuang .hmcl .util .versioning .GameVersionNumber ;
67+ import org .jetbrains .annotations .NotNull ;
6568
69+ import java .lang .ref .WeakReference ;
70+ import java .net .URI ;
6671import java .util .*;
72+ import java .util .concurrent .CancellationException ;
73+ import java .util .concurrent .CompletableFuture ;
74+ import java .util .concurrent .CompletionException ;
6775import java .util .stream .Collectors ;
6876
6977import static org .jackhuang .hmcl .ui .FXUtils .ignoreEvent ;
7078import static org .jackhuang .hmcl .ui .FXUtils .stringConverter ;
7179import static org .jackhuang .hmcl .util .i18n .I18n .i18n ;
7280import static org .jackhuang .hmcl .util .javafx .ExtendedProperties .selectedItemPropertyFor ;
81+ import static org .jackhuang .hmcl .util .logging .Logger .LOG ;
7382
7483public class DownloadListPage extends Control implements DecoratorPage , VersionPage .VersionLoadable {
7584 protected final ReadOnlyObjectWrapper <State > state = new ReadOnlyObjectWrapper <>();
@@ -523,6 +532,7 @@ protected ModDownloadListPageSkin(DownloadListPage control) {
523532
524533 // ListViewBehavior would consume ESC pressed event, preventing us from handling it, so we ignore it here
525534 ignoreEvent (listView , KeyEvent .KEY_PRESSED , e -> e .getCode () == KeyCode .ESCAPE );
535+ var iconCache = new WeakHashMap <String , WeakReference <CompletableFuture <Image >>>();
526536 listView .setCellFactory (x -> new FloatListCell <>(listView ) {
527537 private final TwoLineListItem content = new TwoLineListItem ();
528538 private final ImageView imageView = new ImageView ();
@@ -549,8 +559,64 @@ protected void updateControl(RemoteMod dataItem, boolean empty) {
549559 dataItem .getCategories ().stream ()
550560 .map (category -> getSkinnable ().getLocalizedCategory (category ))
551561 .forEach (content ::addTag );
552- if (StringUtils .isNotBlank (dataItem .getIconUrl ())) {
553- imageView .imageProperty ().bind (FXUtils .newRemoteImage (dataItem .getIconUrl (), 80 , 80 , true , true ));
562+ loadIcon (dataItem );
563+ }
564+
565+ private void loadIcon (RemoteMod mod ) {
566+ if (StringUtils .isBlank (mod .getIconUrl ())) {
567+ imageView .setImage (null );
568+ return ;
569+ }
570+
571+ WeakReference <CompletableFuture <Image >> cacheRef = iconCache .get (mod .getIconUrl ());
572+ CompletableFuture <Image > cache ;
573+ if (cacheRef != null && (cache = cacheRef .get ()) != null ) {
574+ loadIcon (cache , mod .getIconUrl ());
575+ return ;
576+ }
577+
578+ URI iconUrl = NetworkUtils .toURIOrNull (mod .getIconUrl ());
579+ if (iconUrl == null ) {
580+ imageView .setImage (null );
581+ return ;
582+ }
583+
584+ CompletableFuture <Image > future = new CompletableFuture <>();
585+ WeakReference <CompletableFuture <Image >> futureRef = new WeakReference <>(future );
586+ iconCache .put (mod .getIconUrl (), futureRef );
587+
588+ FXUtils .getRemoteImageTask (iconUrl , 80 , 80 , true , true )
589+ .whenComplete (Schedulers .defaultScheduler (), (result , exception ) -> {
590+ if (exception == null ) {
591+ future .complete (result );
592+ } else {
593+ LOG .warning ("Failed to load image from " + iconUrl , exception );
594+ future .completeExceptionally (exception );
595+ }
596+ }).start ();
597+ loadIcon (future , mod .getIconUrl ());
598+ }
599+
600+ private void loadIcon (@ NotNull CompletableFuture <Image > future ,
601+ @ NotNull String iconUrl ) {
602+ Image image ;
603+ try {
604+ image = future .getNow (null );
605+ } catch (CancellationException | CompletionException ignored ) {
606+ imageView .setImage (null );
607+ return ;
608+ }
609+
610+ if (image != null ) {
611+ imageView .setImage (image );
612+ } else {
613+ imageView .setImage (null );
614+ future .thenAcceptAsync (result -> {
615+ RemoteMod item = getItem ();
616+ if (item != null && iconUrl .equals (item .getIconUrl ())) {
617+ this .imageView .setImage (result );
618+ }
619+ }, Schedulers .javafx ());
554620 }
555621 }
556622 });
0 commit comments