package haystack; import com.intellij.ide.plugins.PluginManager; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.DataKeys; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.FileIndexFacade; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.Navigatable; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFileFactory; import com.intellij.psi.PsiManager; import com.intellij.psi.impl.file.PsiDirectoryFactory; import com.intellij.util.ui.UIUtil; import freemarker.template.*; import haystack.core.FileSaver; import haystack.core.LanguageResolver; import haystack.core.models.ClassModel; import haystack.core.models.PageModel; import haystack.resolver.DartFileType; import haystack.resolver.DartResolver; import haystack.ui.JSONEditDialog; import haystack.ui.ModelTableDialog; import haystack.ui.TextResources; import haystack.util.FileUtil; import java.awt.*; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.io.*; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import static haystack.core.models.PageType.CUSTOMSCROLLVIEW; public class FlutterReduxGen extends AnAction implements JSONEditDialog.JSONEditCallbacks, ModelTableDialog.ModelTableCallbacks { private PsiDirectory directory; private Point lastDialogLocation; private LanguageResolver languageResolver; private VirtualFile selectGroup; private String sourcePath; private TextResources textResources; private Configuration cfg; private Project project; public FlutterReduxGen() { super(); } private void configFreemarker(String basePath) { /* Create and adjust the configuration singleton */ cfg = new Configuration(Configuration.VERSION_2_3_27); try { cfg.setDirectoryForTemplateLoading(new File(basePath)); } catch (IOException e) { e.printStackTrace(); } cfg.setObjectWrapper(new DefaultObjectWrapper(Configuration.VERSION_2_3_27)); cfg.setDefaultEncoding("UTF-8"); } @Override public void actionPerformed(AnActionEvent event) { languageResolver = new DartResolver(); textResources = new TextResources(); project = event.getProject(); if (project == null) return; sourcePath = project.getBasePath() + "/lib"; DataContext dataContext = event.getDataContext(); selectGroup = DataKeys.VIRTUAL_FILE.getData(dataContext); final Module module = DataKeys.MODULE.getData(dataContext); if (module == null) return; final Navigatable navigatable = DataKeys.NAVIGATABLE.getData(dataContext); if (navigatable != null) { if (navigatable instanceof PsiDirectory) { directory = (PsiDirectory) navigatable; } } if (directory == null) { ModuleRootManager root = ModuleRootManager.getInstance(module); for (VirtualFile file : root.getSourceRoots()) { directory = PsiManager.getInstance(project).findDirectory(file); } } String resources = System.getProperty("user.home") + "/.haystack_template_cache/"; FileUtil.createDir(resources); String[] fileNames = {"/popmenu", "/actions.dart.ftl", "/app_reducer.dart.ftl", "/app_state.dart.ftl", "/database_client.dart.ftl", "/date_picker_widget.dart.ftl", "/i18n_en.json.ftl", "/i18n_zh.json.ftl", "/action_report.dart.ftl", "/main.dart.ftl", "/middleware" + ".dart.ftl", "/model_entry_data.dart.ftl", "/network_common.dart.ftl", "/page_data.dart.ftl", "/pubspec.yaml.ftl", "/reducer.dart.ftl", "/remote_wrap.dart.ftl", "/repository.dart.ftl", "/repository_db.dart.ftl", "/settings_option.dart.ftl", "/settings_option_page.dart.ftl", "/spannable_grid" + ".dart.ftl", "/state.dart.ftl", "/store.dart.ftl", "/swipe_list_item.dart.ftl", "/test_view.dart.ftl", "/text_scale.dart.ftl", "/theme.dart.ftl", "/toast_utils.dart.ftl", "/translations.dart.ftl", "/view.dart" + ".ftl", "/view_model.dart.ftl", "/progress_dialog.dart.ftl", "/choice_data.dart.ftl", "/action_callback.dart.ftl" }; String version = PluginManager.getPlugin(PluginId.getId("com.github.hayoi.haystack")).getVersion(); if (new File(resources + "version").exists()) { String cacheVersion = FileUtil.usingBufferedReader(resources + "version").replace("\n" , ""); if (!version.equals(cacheVersion)) { FileUtil.mkFile(new File(resources + "version"), version); for (String name : fileNames) { cacheResources(resources, name); } } } else { FileUtil.mkFile(new File(resources + "version"), version); for (String name : fileNames) { cacheResources(resources, name); } } configFreemarker(resources); JSONEditDialog dialog = new JSONEditDialog(this, textResources); dialog.addComponentListener(new ComponentAdapter() { public void componentMoved(ComponentEvent e) { lastDialogLocation = dialog.getLocation(); } }); dialog.pack(); dialog.setLocationRelativeTo(null); dialog.setVisible(true); } private void cacheResources(String resources, String fileName) { InputStream in = null; OutputStream out = null; try { byte[] tempbytes = new byte[100]; int byteread = 0; in = this.getClass().getResourceAsStream(fileName); URL p = this.getClass().getResource(fileName); System.out.println("url: " + p.getPath()); out = new FileOutputStream(resources + fileName); while ((byteread = in.read(tempbytes)) != -1) { out.write(tempbytes, 0, byteread); } } catch (Exception e1) { e1.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e1) { } } if (out != null) { try { out.close(); } catch (IOException e1) { } } } } @Override public void onJsonParsed(PageModel pageModel) { if (pageModel.isUIOnly) { checkProjectStructure(pageModel); } else { ModelTableDialog tableDialog = new ModelTableDialog(pageModel, languageResolver, textResources, this); if (lastDialogLocation != null) { tableDialog.setLocation(lastDialogLocation); } tableDialog.addComponentListener(new ComponentAdapter() { public void componentMoved(ComponentEvent e) { lastDialogLocation = tableDialog.getLocation(); } }); tableDialog.pack(); tableDialog.setVisible(true); } } private void checkProjectStructure(PageModel pageModel) { if (!new File(sourcePath + "/redux/").exists() || !new File(sourcePath + "/features/").exists() || !new File(sourcePath + "/trans/").exists() || !new File(sourcePath + "/data/").exists()) { int result = Messages.showOkCancelDialog(project, "You must init the project first!" , "Init Project", "OK", "NO", Messages.getQuestionIcon()); if (result == Messages.OK) { initTemplate(); genStructure(pageModel); } } else { genStructure(pageModel); } } @Override public void onInitTemplate() { initTemplate(); } @Override public void onModelsReady(PageModel pageModel) { checkProjectStructure(pageModel); } private void genStructure(PageModel pageModel) { Project project = directory.getProject(); PsiFileFactory factory = PsiFileFactory.getInstance(project); PsiDirectoryFactory directoryFactory = PsiDirectoryFactory.getInstance(directory.getProject()); String packageName = directoryFactory.getQualifiedName(directory, true); FileSaver fileSaver = new IDEFileSaver(factory, directory, DartFileType.INSTANCE); fileSaver.setListener(fileName -> { int ok = Messages.showOkCancelDialog( textResources.getReplaceDialogMessage(fileName), textResources.getReplaceDialogTitle(), "OK", "NO", UIUtil.getQuestionIcon()); return ok == 0; }); final String moduleName = FileIndexFacade.getInstance(project).getModuleForFile(directory.getVirtualFile()).getName(); Map<String, Object> rootMap = new HashMap<String, Object>(); rootMap.put("ProjectName", moduleName); rootMap.put("PageType", pageModel.pageType); if (pageModel.pageType.equals(CUSTOMSCROLLVIEW)) { rootMap.put("GenerateCustomScrollView", true); } else { rootMap.put("GenerateCustomScrollView", false); } rootMap.put("PageName", pageModel.pageName); rootMap.put("ModelEntryName", pageModel.modelName); rootMap.put("GenerateListView", pageModel.genListView); rootMap.put("GenerateBottomTabBar", pageModel.genBottomTabBar); rootMap.put("GenerateAppBar", pageModel.genAppBar); rootMap.put("GenerateDrawer", pageModel.genDrawer); rootMap.put("GenerateTopTabBar", pageModel.genTopTabBar); rootMap.put("GenerateWebView", pageModel.genWebView); rootMap.put("GenerateActionButton", pageModel.genActionButton); rootMap.put("viewModelQuery", pageModel.viewModelQuery); rootMap.put("viewModelGet", pageModel.viewModelGet); rootMap.put("viewModelCreate", pageModel.viewModelCreate); rootMap.put("viewModelUpdate", pageModel.viewModelUpdate); rootMap.put("viewModelDelete", pageModel.viewModelDelete); rootMap.put("GenSliverFixedExtentList", pageModel.genSliverFixedList); rootMap.put("GenSliverGrid", pageModel.genSliverGrid); rootMap.put("GenSliverToBoxAdapter", pageModel.genSliverToBoxAdapter); rootMap.put("FabInAppBar", pageModel.genSliverFab); rootMap.put("GenSliverTabBar", pageModel.genSliverTabBar); rootMap.put("GenSliverTabView", pageModel.genSliverTabView); rootMap.put("IsCustomWidget", pageModel.isCustomWidget); if (pageModel.genActionButton) { rootMap.put("HasActionSearch", pageModel.hasActionSearch); rootMap.put("ActionList", pageModel.actionList); rootMap.put("ActionBtnCount", pageModel.actionList.size()); } else { rootMap.put("HasActionSearch", false); rootMap.put("ActionList", new ArrayList<String>()); rootMap.put("ActionBtnCount", 0); } if (!pageModel.isUIOnly) { for (ClassModel classModel : pageModel.classModels) { if (classModel.getName().equals(pageModel.modelName)) { rootMap.put("genDatabase", classModel.isGenDBModule()); if (classModel.getUniqueField() != null) { rootMap.put("clsUNName", classModel.getUniqueField()); rootMap.put("clsUNNameType", classModel.getUniqueFieldType()); } } generateModelEntry(classModel, rootMap); } generateRepository(rootMap); generateRedux(rootMap); } generateFeature(rootMap, pageModel.isCustomWidget, pageModel.genSliverTabView); } private void generateRedux(Map<String, Object> rootMap) { String path = sourcePath + "/redux"; FileUtil.generateFile(new File(path + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_actions.dart"), "actions.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(path + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_middleware.dart"), "middleware.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(path + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_reducer.dart"), "reducer.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(path + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_state.dart"), "state.dart.ftl", rootMap, cfg); writeAppState(rootMap); writeAppReducer(rootMap); writeStore(rootMap); } private void writeStore(Map<String, Object> rootMap) { String path = sourcePath + "/redux/store.dart"; String content = FileUtil.usingBufferedReader(path); StringBuilder sb = new StringBuilder(); String param = "import 'package:" + rootMap.get("ProjectName").toString().toLowerCase() + "/redux/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_middleware.dart';\n"; if (!content.contains(param)) { sb.append(param); } param = "\n ..addAll(create" + rootMap.get("ModelEntryName").toString() + "Middleware())"; if (!content.contains(param)) { int poi1 = content.indexOf("middleware: []") + "middleware: []".length(); sb.append(content.substring(0, poi1)); sb.append(param); sb.append(content.substring(poi1)); FileUtil.writeToFile(path, sb.toString(), false); } } private void writeAppReducer(Map<String, Object> rootMap) { String path = sourcePath + "/redux/app/app_reducer.dart"; String content = FileUtil.usingBufferedReader(path); StringBuilder sb = new StringBuilder(); String param = "import 'package:" + rootMap.get("ProjectName").toString().toLowerCase() + "/redux/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_reducer.dart';\n"; if (!content.contains(param)) { sb.append(param); } param = "\n " + rootMap.get("ModelEntryName").toString().toLowerCase() + "State: " + rootMap.get("ModelEntryName").toString().toLowerCase() + "Reducer(state." + rootMap.get("ModelEntryName").toString().toLowerCase() + "State, action),"; if (!content.contains(param)) { int poi1 = content.indexOf("return new AppState(") + "return new AppState(".length(); sb.append(content.substring(0, poi1)); sb.append(param); sb.append(content.substring(poi1)); FileUtil.writeToFile(path, sb.toString(), false); } } private void writeAppState(Map<String, Object> rootMap) { String path = sourcePath + "/redux/app/app_state.dart"; String content = FileUtil.usingBufferedReader(path); String param = "import 'package:" + rootMap.get("ProjectName").toString().toLowerCase() + "/redux/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_state.dart';\n"; StringBuilder sb = new StringBuilder(); if (!content.contains(param)) { sb.append(param); } param = "\n final " + rootMap.get("ModelEntryName").toString() + "State " + rootMap.get( "ModelEntryName").toString().toLowerCase() + "State;"; int poi1 = content.indexOf("class AppState {") + "class AppState {".length(); if (!content.contains(param)) { sb.append(content.substring(0, poi1)); sb.append(param); } int poi2 = content.indexOf("AppState({") + "AppState({".length(); param = "\n @required this." + rootMap.get("ModelEntryName").toString().toLowerCase() + "State,"; if (!content.contains(param)) { sb.append(content.substring(poi1, poi2)); sb.append(param); } int poi3 = content.indexOf("return AppState(") + "return AppState(".length(); param = "\n " + rootMap.get("ModelEntryName").toString().toLowerCase() + "State: " + rootMap.get("ModelEntryName").toString() + "State(\n" + " " + rootMap.get("ModelEntryName").toString().toLowerCase() + ": " + "null,\n" + " " + rootMap.get("ModelEntryName").toString().toLowerCase() + "s: Map" + "(),\n" + " page: Page(),),"; if (!content.contains(param)) { sb.append(content.substring(poi2, poi3)); sb.append(param); sb.append(content.substring(poi3)); FileUtil.writeToFile(path, sb.toString(), false); } } private void initTemplate() { final String moduleName = FileIndexFacade.getInstance(project).getModuleForFile(directory.getVirtualFile()).getName(); Map<String, Object> rootMap = new HashMap<String, Object>(); rootMap.put("ProjectName", moduleName); FileUtil.generateFile(new File(project.getBasePath() + "/pubspec.yaml"), "pubspec.yaml" + ".ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/main.dart"), "main.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/data/network_common.dart"), "network_common" + ".dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/utils/progress_dialog.dart"), "progress_dialog.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/utils/toast_utils.dart"), "toast_utils.dart" + ".ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/data/model/remote_wrap.dart"), "remote_wrap" + ".dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/data/model/choice_data.dart"), "choice_data" + ".dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/data/model/page_data.dart"), "page_data" + ".dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/trans/translations.dart"), "translations" + ".dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/redux/store.dart"), "store.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/redux/action_report.dart"), "action_report" + ".dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/redux/app/app_reducer.dart"), "app_reducer" + ".dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/redux/app/app_state.dart"), "app_state.dart" + ".ftl", rootMap, cfg); FileUtil.generateFile(new File(project.getBasePath() + "/locale/i18n_en.json"), "i18n_en" + ".json.ftl", rootMap, cfg); FileUtil.generateFile(new File(project.getBasePath() + "/locale/i18n_zh.json"), "i18n_zh" + ".json.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/data/db/database_client.dart"), "database_client.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/features/action_callback.dart"), "action_callback.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/features/settings/settings_option.dart"), "settings_option.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/features/settings/settings_option_page" + ".dart"), "settings_option_page.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/features/settings/theme.dart"), "theme.dart" + ".ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/features/settings/text_scale.dart"), "text_scale.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/features/widget/date_picker_widget.dart"), "date_picker_widget.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/features/widget/spannable_grid.dart"), "spannable_grid.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(sourcePath + "/features/widget/swipe_list_item.dart"), "swipe_list_item.dart.ftl", rootMap, cfg); Messages.showMessageDialog(project, "Project init completed!", "Initialize", Messages.getInformationIcon()); } private void generateFeature(Map<String, Object> rootMap, boolean isCustomWidget, boolean tabView) { String path = sourcePath + "/features/" + (isCustomWidget ? "customize/" : "") + rootMap.get( "PageName").toString().toLowerCase() + "/" + rootMap.get("PageName").toString().toLowerCase(); FileUtil.generateFile(new File(path + (tabView ? "_tab" : "") + "_view_model.dart"), "view_model.dart.ftl", rootMap, cfg); FileUtil.generateFile(new File(path + (tabView ? "_tab" : "") + "_view.dart"), "view.dart.ftl", rootMap, cfg); } private void generateRepository(Map<String, Object> rootMap) { String path = sourcePath + "/data"; boolean db = (boolean) rootMap.get("genDatabase"); if ((db)) { FileUtil.generateFile(new File(path + "/db/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_repository_db.dart"), "repository_db.dart.ftl", rootMap, cfg); } FileUtil.generateFile(new File(path + "/remote/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_repository.dart"), "repository.dart.ftl", rootMap, cfg); } private void generateModelEntry(ClassModel classModel, Map map) { Map<String, Object> subMap = new HashMap<String, Object>(map); subMap.remove("ModelEntryName"); subMap.put("ModelEntryName", classModel.getName()); subMap.put("genDatabase", classModel.isGenDBModule()); subMap.put("Fields", classModel.getFields()); File f = new File(sourcePath + "/data/model/" + classModel.getName().toLowerCase() + "_data.dart"); FileUtil.generateFile(f, "model_entry_data.dart.ftl", subMap, cfg); if (classModel.isGenDBModule()) { writeDatabaseClient(subMap); } } private void writeDatabaseClient(Map<String, Object> rootMap) { String path = sourcePath + "/data/db/database_client.dart"; String content = FileUtil.usingBufferedReader(path); String param = "import 'package:" + rootMap.get("ProjectName").toString().toLowerCase() + "/data" + "/model/" + rootMap.get("ModelEntryName").toString().toLowerCase() + "_data.dart';\n"; StringBuilder sb = new StringBuilder(); if (!content.contains(param)) { sb.append(param); } param = "\n d..delete(\"" + rootMap.get("ModelEntryName").toString().toLowerCase() + "\");\n" + " " + rootMap.get("ModelEntryName").toString() + ".createTable(d);"; int poi1 = content.indexOf("onUpgrade: (d, o, n) {") + "onUpgrade: (d, o, n) {".length(); if (!content.contains(param)) { sb.append(content.substring(0, poi1)); sb.append(param); } int poi2 = content.indexOf("onOpen: (d) {") + "onOpen: (d) {".length(); param = "\n " + rootMap.get("ModelEntryName").toString() + ".createTable(d);"; if (!content.contains(param)) { sb.append(content.substring(poi1, poi2)); sb.append(param); sb.append(content.substring(poi2)); FileUtil.writeToFile(path, sb.toString(), false); } } }