Skip to content

简化列表式管理页面相关代码 [Gemini 3.1 Pro]#6470

Open
CiiLu wants to merge 2 commits into
HMCL-dev:mainfrom
CiiLu:abc
Open

简化列表式管理页面相关代码 [Gemini 3.1 Pro]#6470
CiiLu wants to merge 2 commits into
HMCL-dev:mainfrom
CiiLu:abc

Conversation

@CiiLu

@CiiLu CiiLu commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
  • 迁移重复逻辑到 ToolbarListPageSkin
  • 同时为世界管理、原理图管理添加搜索功能

@github-actions github-actions Bot added the 1000+ label Jul 21, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors several list page skins (such as Java, Theme Pack, Data Pack, Game, Installer, Mod, Resource Pack, Schematics, World Backups, and World list pages) to inherit from a newly introduced common base class ToolbarListPageSkin. This centralizes toolbar, search, and selection logic, reducing code duplication. Feedback on the changes highlights a logic bug in createSelectAllButton where the button is incorrectly enabled when the list is empty, potential NullPointerException risks in the search predicates of DataPackListPageSkin and WorldListPage due to missing null checks, and performance inefficiencies in WorldListPage, SchematicsPage, and GameListPage where toLowerCase is repeatedly called inside lambda filters.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +212 to +221
protected JFXButton createSelectAllButton() {
JFXButton selectAll = createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () -> listView.getSelectionModel().selectRange(0, listView.getItems().size()));
ListChangeListener<Object> listener = change -> {
selectAll.setDisable(!listView.getItems().isEmpty()
&& listView.getSelectionModel().getSelectedItems().size() == listView.getItems().size());
};
listView.getSelectionModel().getSelectedItems().addListener(listener);
listView.getItems().addListener(listener);
return selectAll;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

createSelectAllButton 中,存在两个可以改进的地方:\n1. 逻辑漏洞:当列表为空时,!listView.getItems().isEmpty()false,导致整个表达式计算为 false,从而使 selectAll 按钮被启用setDisable(false))。实际上,当列表为空时,应该禁用“全选”按钮。建议将逻辑改为 listView.getItems().isEmpty() || ...。\n2. 初始状态未更新:在创建按钮并添加监听器后,没有立即触发一次状态更新。如果列表初始为空或已全选,按钮的禁用状态不会正确反映,直到下一次选择或列表内容发生变化。\n\n建议重构该方法,提取更新逻辑并进行初始调用。

    protected JFXButton createSelectAllButton() {\n        JFXButton selectAll = createToolbarButton2(i18n("button.select_all"), SVG.SELECT_ALL, () -> listView.getSelectionModel().selectRange(0, listView.getItems().size()));\n        Runnable updateDisableState = () -> {\n            selectAll.setDisable(listView.getItems().isEmpty()\n                    || listView.getSelectionModel().getSelectedItems().size() == listView.getItems().size());\n        };\n        ListChangeListener<Object> listener = change -> updateDisableState.run();\n        listView.getSelectionModel().getSelectedItems().addListener(listener);\n        listView.getItems().addListener(listener);\n        updateDisableState.run();\n        return selectAll;\n    }

Comment on lines +119 to 130
final Predicate<String> stringPredicate;
if (queryString.startsWith("regex:")) {
try {
Pattern pattern = Pattern.compile(StringUtils.substringAfter(queryString, "regex:"));
stringPredicate = s -> pattern.matcher(s).find();
} catch (Exception e) {
return dataPack -> false;
}
} else {
String lowerCaseFilter = queryString.toLowerCase(Locale.ROOT);
stringPredicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerCaseFilter);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

在重构 updateSearchPredicate 时,删除了原先对 s != null 的防御性检查。如果 iddescriptionnull,在执行 stringPredicate.test(...) 时会抛出 NullPointerException。为了保证代码的健壮性,建议恢复 s != null 的空值检查。

        final Predicate<String> stringPredicate;\n        if (queryString.startsWith("regex:")) {\n            try {\n                Pattern pattern = Pattern.compile(StringUtils.substringAfter(queryString, "regex:"));\n                stringPredicate = s -> s != null && pattern.matcher(s).find();\n            } catch (Exception e) {\n                return dataPack -> false;\n            }\n        } else {\n            String lowerCaseFilter = queryString.toLowerCase(Locale.ROOT);\n            stringPredicate = s -> s != null && s.toLowerCase(Locale.ROOT).contains(lowerCaseFilter);\n        }

Comment on lines 238 to 252
@Override
protected List<Node> initializeToolbar(WorldListPage skinnable) {
JFXCheckBox chkShowAll = new JFXCheckBox(i18n("world.show_all"));
chkShowAll.selectedProperty().bindBidirectional(skinnable.showAllProperty());

return Arrays.asList(
chkShowAll,
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh),
createToolbarButton2(i18n("world.add"), SVG.ADD, skinnable::add),
createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD, skinnable::download)
);
protected Predicate<World> updateSearchPredicate(String searchText) {
if (searchText == null || searchText.isEmpty()) return item -> true;
if (searchText.startsWith("regex:")) {
String regex = searchText.substring("regex:".length());
try {
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
return item -> pattern.matcher(item.getWorldName()).find();
} catch (PatternSyntaxException e) {
return item -> false;
}
} else {
return item -> item.getWorldName().toLowerCase(Locale.ROOT).contains(searchText.toLowerCase(Locale.ROOT));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

updateSearchPredicate 中存在两个问题:\n1. 空指针异常风险item.getWorldName() 可能返回 null(如第 355 行代码所示,存在对 world.getWorldName() != null 的显式判断)。如果为 null,调用 item.getWorldName().toLowerCase(...)pattern.matcher(...) 将抛出 NullPointerException。\n2. 性能开销:在非正则搜索分支中,searchText.toLowerCase(Locale.ROOT) 在过滤每个元素时都会被重复调用。应当将其提取到 Lambda 表达式外部,只计算一次。

        @Override\n        protected Predicate<World> updateSearchPredicate(String searchText) {\n            if (searchText == null || searchText.isEmpty()) return item -> true;\n            if (searchText.startsWith("regex:")) {\n                String regex = searchText.substring("regex:".length());\n                try {\n                    Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);\n                    return item -> {\n                        String name = item.getWorldName();\n                        return name != null && pattern.matcher(name).find();\n                    };\n                } catch (PatternSyntaxException e) {\n                    return item -> false;\n                }\n            } else {\n                String lowerCaseFilter = searchText.toLowerCase(Locale.ROOT);\n                return item -> {\n                    String name = item.getWorldName();\n                    return name != null && name.toLowerCase(Locale.ROOT).contains(lowerCaseFilter);\n                };\n            }\n        }

Comment on lines 660 to 674
@Override
protected ListCell<Item> createListCell(JFXListView<Item> listView) {
return new Cell();
protected Predicate<SchematicsPage.Item> updateSearchPredicate(String searchText) {
if (searchText == null || searchText.isEmpty()) return item -> true;
if (searchText.startsWith("regex:")) {
String regex = searchText.substring("regex:".length());
try {
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
return item -> pattern.matcher(item.getName() + item.getDescription()).find();
} catch (PatternSyntaxException e) {
return item -> false;
}
} else {
return item -> (item.getName() + item.getDescription()).toLowerCase(Locale.ROOT).contains(searchText.toLowerCase(Locale.ROOT));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

在非正则搜索分支中,searchText.toLowerCase(Locale.ROOT) 在过滤每个元素时都会被重复调用。建议将其提取到 Lambda 表达式外部计算,以提升过滤大列表时的性能。

        @Override\n        protected Predicate<SchematicsPage.Item> updateSearchPredicate(String searchText) {\n            if (searchText == null || searchText.isEmpty()) return item -> true;\n            if (searchText.startsWith("regex:")) {\n                String regex = searchText.substring("regex:".length());\n                try {\n                    Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);\n                    return item -> pattern.matcher(item.getName() + item.getDescription()).find();\n                } catch (PatternSyntaxException e) {\n                    return item -> false;\n                }\n            } else {\n                String lowerCaseFilter = searchText.toLowerCase(Locale.ROOT);\n                return item -> (item.getName() + item.getDescription()).toLowerCase(Locale.ROOT).contains(lowerCaseFilter);\n            }\n        }

Comment on lines +165 to 179
@Override
protected Predicate<GameListItem> updateSearchPredicate(String searchText) {
if (searchText == null || searchText.isEmpty()) return item -> true;
if (searchText.startsWith("regex:")) {
String regex = searchText.substring("regex:".length());
try {
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
return item -> pattern.matcher(item.id).find();
} catch (PatternSyntaxException e) {
return item -> false;
}
} else {
return item -> item.id.toLowerCase(Locale.ROOT).contains(searchText.toLowerCase(Locale.ROOT));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

在非正则搜索分支中,searchText.toLowerCase(Locale.ROOT) 在过滤每个元素时都会被重复调用。建议将其提取到 Lambda 表达式外部计算,以提升过滤大列表时的性能。

            @Override\n            protected Predicate<GameListItem> updateSearchPredicate(String searchText) {\n                if (searchText == null || searchText.isEmpty()) return item -> true;\n                if (searchText.startsWith("regex:")) {\n                    String regex = searchText.substring("regex:".length());\n                    try {\n                        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);\n                        return item -> pattern.matcher(item.id).find();\n                    } catch (PatternSyntaxException e) {\n                        return item -> false;\n                    }\n                } else {\n                    String lowerCaseFilter = searchText.toLowerCase(Locale.ROOT);\n                    return item -> item.id.toLowerCase(Locale.ROOT).contains(lowerCaseFilter);\n                }\n            }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant